Skip to content

Commit

Permalink
[EVM] Introduce pseudo jumps, call and ret instructions
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
vladimirradosavljevic committed Dec 9, 2024
1 parent 6c9275d commit 18eab55
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 174 deletions.
63 changes: 31 additions & 32 deletions llvm/lib/Target/EVM/EVMAsmPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,39 +46,15 @@ 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;

private:
void emitLinkerSymbol(const MachineInstr *MI);
};
} // end of anonymous namespace

void EVMAsmPrinter::SetupMachineFunction(MachineFunction &MF) {
// Unbundle <push_label, jump> 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();

Expand All @@ -105,8 +81,37 @@ 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) {
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;
}
case EVM::DATASIZE_S:
case EVM::DATAOFFSET_S:
emitLinkerSymbol(MI);
return;
}
Expand All @@ -116,12 +121,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::emitLinkerSymbol(const MachineInstr *MI) {
MCSymbol *LinkerSymbol = MI->getOperand(0).getMCSymbol();
StringRef LinkerSymbolName = LinkerSymbol->getName();
Expand Down
81 changes: 24 additions & 57 deletions llvm/lib/Target/EVM/EVMAssembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -165,19 +156,18 @@ 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);
LLVM_DEBUG(dumpInst(&*CurMIIt));
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);
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -233,8 +228,7 @@ void EVMAssembly::finalize() {
SmallVector<MachineInstr *, 128> 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);
}
}
Expand All @@ -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 <PUSH_LABEL, JUMP> 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();
}
}
}
}
4 changes: 1 addition & 3 deletions llvm/lib/Target/EVM/EVMAssembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,14 @@ 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);

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
Expand Down
14 changes: 12 additions & 2 deletions llvm/lib/Target/EVM/EVMInstrInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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), []>;
}


//===----------------------------------------------------------------------===//
Expand Down
2 changes: 1 addition & 1 deletion llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
47 changes: 13 additions & 34 deletions llvm/lib/Target/EVM/EVMStackify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<Register> ReturnRegs;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 <PUSH_LABEL, JUMP> 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 {
Expand Down
Loading

0 comments on commit 18eab55

Please sign in to comment.