Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EraVM] Replace near_call with jump #653

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions llvm/lib/Target/EraVM/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ add_llvm_target(EraVMCodeGen
EraVMTargetTransformInfo.cpp
EraVMIndexedMemOpsPrepare.cpp
EraVMTieSelectOperands.cpp
EraVMReplaceNearCallPrepare.cpp

LINK_COMPONENTS
Analysis
Expand Down
15 changes: 15 additions & 0 deletions llvm/lib/Target/EraVM/EraVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ enum TargetOperandFlags {
/// Immediate that represents stack slot index.
MO_STACK_SLOT_IDX,

/// Immediate that represents stack slot index for return address.
/// Once we replace near_call with cheaper jump, the caller will
/// store return address in the top of the stack, later in the callee
/// we will use J_s with such slot index to finish return just like
/// the original RET does.
MO_STACK_SLOT_IDX_RET,

/// Represents return address symbol.
MO_SYM_RET_ADDRESS,
}; // enum TargetOperandFlags
Expand All @@ -68,6 +75,7 @@ class PassRegistry;

FunctionPass *createEraVMISelDag(EraVMTargetMachine &TM,
CodeGenOpt::Level OptLevel);
ModulePass *createEraVMReplaceNearCallPreparePass();
ModulePass *createEraVMLowerIntrinsicsPass();
ModulePass *createEraVMLinkRuntimePass(bool);
FunctionPass *createEraVMAllocaHoistingPass();
Expand Down Expand Up @@ -96,6 +104,7 @@ FunctionPass *createEraVMPostCodegenPreparePass();
FunctionPass *createEraVMDeadRegisterDefinitionsPass();
FunctionPass *createEraVMConditionOptimizerPass();

void initializeEraVMReplaceNearCallPreparePass(PassRegistry &);
void initializeEraVMLowerIntrinsicsPass(PassRegistry &);
void initializeEraVMAllocaHoistingPass(PassRegistry &);
void initializeEraVMBytesToCellsPass(PassRegistry &);
Expand Down Expand Up @@ -159,6 +168,12 @@ struct EraVMLowerIntrinsicsPass : PassInfoMixin<EraVMLowerIntrinsicsPass> {
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
};

struct EraVMReplaceNearCallPreparePass
: PassInfoMixin<EraVMReplaceNearCallPreparePass> {
EraVMReplaceNearCallPreparePass() = default;
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
};

} // namespace llvm

#endif
73 changes: 62 additions & 11 deletions llvm/lib/Target/EraVM/EraVMExpandPseudoInsts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@

#include "EraVM.h"

#include "EraVMInstrInfo.h"
#include "EraVMMachineFunctionInfo.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFunctionPass.h"
#include "llvm/CodeGen/MachineInstrBuilder.h"
#include "llvm/CodeGen/TargetInstrInfo.h"
#include "llvm/MC/MCContext.h"
#include "llvm/Support/Debug.h"

#include "EraVMSubtarget.h"
Expand All @@ -40,7 +45,7 @@ class EraVMExpandPseudo : public MachineFunctionPass {
StringRef getPassName() const override { return ERAVM_EXPAND_PSEUDO_NAME; }

private:
const TargetInstrInfo *TII{};
const EraVMInstrInfo *TII{};
LLVMContext *Context{};
};

Expand All @@ -59,6 +64,7 @@ bool EraVMExpandPseudo::runOnMachineFunction(MachineFunction &MF) {
TII = MF.getSubtarget<EraVMSubtarget>().getInstrInfo();
assert(TII && "TargetInstrInfo must be a valid object");

MachineFrameInfo &MFI = MF.getFrameInfo();
Context = &MF.getFunction().getContext();

std::vector<MachineInstr *> PseudoInst;
Expand Down Expand Up @@ -95,16 +101,40 @@ bool EraVMExpandPseudo::runOnMachineFunction(MachineFunction &MF) {
// emit calls (Should we reinforce it?) so this route is needed. If a
// call is generated, it is incomplete as it misses EH label info, pad
// 0 instead.
Register ABIReg = MI.getOperand(0).getReg();
BuildMI(*MI.getParent(), &MI, MI.getDebugLoc(),
TII->get(EraVM::NEAR_CALL))
.addReg(ABIReg)
.add(MI.getOperand(1))
.addExternalSymbol(
"DEFAULT_UNWIND") // Linker inserts a basic block
// which bubbles up the exception.
.addImm(EraVMCC::COND_NONE)
.copyImplicitOps(MI);
const auto *Callee = dyn_cast<Function>(MI.getOperand(1).getGlobal());
if (!Callee->hasFnAttribute("UseJumpReturn")) {
Register ABIReg = MI.getOperand(0).getReg();
BuildMI(*MI.getParent(), &MI, MI.getDebugLoc(),
TII->get(EraVM::NEAR_CALL))
.addReg(ABIReg)
.add(MI.getOperand(1))
.addExternalSymbol(
"DEFAULT_UNWIND") // Linker inserts a basic block
// which bubbles up the exception.
.addImm(EraVMCC::COND_NONE)
.copyImplicitOps(MI);
} else {
MachineInstr *NewMI =
BuildMI(*MI.getParent(), &MI, MI.getDebugLoc(),
TII->get(EraVM::JCALL))
.add(MI.getOperand(1))
.copyImplicitOps(MI);

MCSymbol *RetSym =
MF.getContext().createTempSymbol("JCALL_FUNCTION_RET", true);
NewMI->setPostInstrSymbol(MF, RetSym);

// Add instruction to store return address onto the top of the
// stack.
BuildMI(MBB, NewMI, MI.getDebugLoc(), TII->get(EraVM::ADDcrs_s))
.addSym(RetSym, EraVMII::MO_SYM_RET_ADDRESS)
.addImm(0 /* RetSymOffset */)
.addReg(EraVM::R0)
.addReg(EraVM::SP)
.addImm(0 /* AMBase2 */)
.addImm(-1 /* StackOffset */)
.addImm(EraVMCC::COND_NONE);
}
}

PseudoInst.push_back(&MI);
Expand Down Expand Up @@ -153,6 +183,27 @@ bool EraVMExpandPseudo::runOnMachineFunction(MachineFunction &MF) {
.addReg(EraVM::R0)
.addImm(EraVMCC::COND_NONE);
PseudoInst.push_back(&MI);
} else if (MI.getOpcode() == EraVM::RET) {
if (MF.getFunction().hasFnAttribute("UseJumpReturn")) {
MachineInstr *NewMI = BuildMI(*MI.getParent(), &MI, MI.getDebugLoc(),
TII->get(EraVM::J_s))
.addReg(EraVM::SP)
.addImm(0)
.addImm(-1)
.copyImplicitOps(MI);

// Distinguish this jump is specific for return.
NewMI->getOperand(2).setTargetFlags(EraVMII::MO_STACK_SLOT_IDX_RET);

if (MFI.getStackSize()) {
MachineInstr *SPRestoreMI =
TII->insertDecSP(*MI.getParent(), NewMI, MI.getDebugLoc(),
MFI.getStackSize() / 32);
SPRestoreMI->setFlag(MachineInstr::FrameDestroy);
}

PseudoInst.push_back(&MI);
}
}
}

Expand Down
9 changes: 9 additions & 0 deletions llvm/lib/Target/EraVM/EraVMFrameLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ void EraVMFrameLowering::emitPrologue(MachineFunction &MF,
MachineBasicBlock::iterator MBBI = MBB.begin();
DebugLoc DL = MBBI != MBB.end() ? MBBI->getDebugLoc() : DebugLoc();

// If current function has at least one near_call which can be replaced
// with jump, we reserve the top of the stack for saving return address.
// Even if we have more than one qualified near_call, one TOP slot is
// good enough.
if (MF.getFunction().hasFnAttribute("HasJumpCall")) {
MFI.CreateFixedObject(32, 0, true);
MFI.setStackSize(MFI.getStackSize() + 32);
}

uint64_t NumCells = MFI.getStackSize() / 32;

if (NumCells)
Expand Down
29 changes: 29 additions & 0 deletions llvm/lib/Target/EraVM/EraVMInstrInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,15 @@ static void fixupStackAccessOffsetPostOutline(MachineBasicBlock::iterator Start,
auto &StackSlotOP = MI.getOperand(1);
StackSlotOP.setImm(StackSlotOP.getImm() - FixupOffset);
}

// This instruction is used to restore SP after we replace normal RET with
// jump. The caller of outlined function will allocate a new stack slot
// in its stack, so here the amount to restore SP needs to be udpated.
if (MI.getOpcode() == EraVM::NOPsrr &&
MI.getFlag(MachineInstr::FrameDestroy)) {
auto &StackAdjustOP = MI.getOperand(3);
StackAdjustOP.setImm(StackAdjustOP.getImm() - FixupOffset);
}
}
}

Expand All @@ -858,6 +867,26 @@ void EraVMInstrInfo::fixupStackPostOutline(MachineFunction &MF) const {
} else {
auto EntryBB = MF.begin();
insertIncSP(*EntryBB, EntryBB->begin(), DebugLoc(), 1 /* StackSlotSize */);

// This ELSE branch means the caller of outlined function doesn't use any
// stack originally, now in order to call newly created outlined function,
// the above SP increasing instruction will create a stack slot for being
// used to store return address before calling into outlined function.
//
// So if this caller is using jump to return, we need to insert another
// instruction to decrease (or restore) the SP before we use jump to
// return. The decrease amount is set to 0, the later fixupStackAccessOffset
// function will fix it to right value.
for (MachineBasicBlock &MBB : MF) {
for (auto &MI : MBB) {
if (MI.getOpcode() == EraVM::J_s &&
MI.getOperand(2).getTargetFlags() ==
EraVMII::MO_STACK_SLOT_IDX_RET) {
MachineInstr *SPRestoreMI = insertDecSP(MBB, MI, MI.getDebugLoc(), 0);
SPRestoreMI->setFlag(MachineInstr::FrameDestroy);
}
}
}
}

// Adjust instructions which use SP in this function.
Expand Down
128 changes: 128 additions & 0 deletions llvm/lib/Target/EraVM/EraVMReplaceNearCallPrepare.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//===-- EraVMReplaceNearCallPrepare.cpp - Call by Jump ----------*- 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 will check functions in current unit to see whether:
// * a function can use jump to return
// * a function has a near_call which can be replaced with jump
//
// If a function is called via INVOKE then it can't use jump to return.
// If a callee of a near_call can't use jump to return, then current near_call
// can't be replaced with jump.
//
// The final results will be added to function as attributes for being used in
// subsequent passes:
// * UseJumpReturn: current function hasn't been called via INVOKE at all in
// current unit and thus can return via jump.
// * HasJumpCall: current function has at least one near_call which can be
// replaced with jump, the later frameLowering should reserve
// the top of stack for storing return address.
//===----------------------------------------------------------------------===//

#include "EraVM.h"
#include "EraVMMachineFunctionInfo.h"
#include "llvm/CodeGen/MachineFunction.h"
#include "llvm/CodeGen/MachineFrameInfo.h"
#include "llvm/CodeGen/MachineModuleInfo.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/Instructions.h"
#include "llvm/Pass.h"

#define DEBUG_TYPE "eravm-replace-near-call-prepare"

using namespace llvm;

#define ERAVM_REPLACE_NEAR_CALL_PREPARE_NAME \
"EraVM replace near_call prepare pass"

namespace {

class EraVMReplaceNearCallPrepare final : public ModulePass {
public:
static char ID; // Pass ID
EraVMReplaceNearCallPrepare() : ModulePass(ID) {}

StringRef getPassName() const override {
return ERAVM_REPLACE_NEAR_CALL_PREPARE_NAME;
}

void getAnalysisUsage(AnalysisUsage &AU) const override {
AU.addRequired<MachineModuleInfoWrapperPass>();
AU.addPreserved<MachineModuleInfoWrapperPass>();
AU.setPreservesAll();
ModulePass::getAnalysisUsage(AU);
}

bool runOnModule(Module &M) override;
};

} // end anonymous namespace

static bool runImpl(Module &M, MachineModuleInfo *MMI) {
bool Changed = false;

// Check whether current function is called via INVOKE, if none, then
// it can use jump to return.
for (auto &F : M) {
if (F.empty() || F.isDeclaration() || F.getName() == "__entry" ||
F.getName() == "__cxa_throw")
continue;

if (any_of(F.uses(),
[](const Use &U) { return isa<InvokeInst>(U.getUser()); }))
continue;

if (any_of(F.uses(), [MMI](const Use &U) {
auto &CallerFunc = *cast<Instruction>(U.getUser())->getFunction();
auto *CallerMF = MMI->getMachineFunction(CallerFunc);
return !CallerMF->getFrameInfo().getNumObjects();
}))
continue;

F.addFnAttr("UseJumpReturn");
Changed = true;
}

// Check all the CallInst, if the callee can use jump to return,
// then the function including CallInst has a near_call which
// can be replaced with jump, add HasJumpCall attribute for being
// used by later frameLowering pass to reserve the top of the stack
// for storing the return address.
for (auto &F : M) {
if (!F.hasFnAttribute("UseJumpReturn"))
continue;

for (auto I = F.user_begin(), E = F.user_end(); I != E; I++) {
if (auto *II = dyn_cast<CallInst>(*I)) {
Function *Caller = II->getParent()->getParent();
Caller->addFnAttr("HasJumpCall");
Changed = true;
}
}
}

return Changed;
}

bool EraVMReplaceNearCallPrepare::runOnModule(Module &M) {
if (skipModule(M))
return false;
MachineModuleInfo *MMI =
&getAnalysis<MachineModuleInfoWrapperPass>().getMMI();

return runImpl(M, MMI);
}

char EraVMReplaceNearCallPrepare::ID = 0;

INITIALIZE_PASS(EraVMReplaceNearCallPrepare, "eravm-replace-near-call-prepare",
ERAVM_REPLACE_NEAR_CALL_PREPARE_NAME, false,
false)

ModulePass *llvm::createEraVMReplaceNearCallPreparePass() {
return new EraVMReplaceNearCallPrepare;
}
Loading
Loading