For translating the LLVM IR to generic machine instructions, we only need to implement how arguments and return values are handled. Again, the implementation can be simplified by using the generated code from the target description. The class we’ll create is called M88kCallLowering, and the declaration is in the GISel/M88kCallLowering.h header file:

class M88kCallLowering : public CallLowering {
public:
M88kCallLowering(const M88kTargetLowering &TLI);
bool
lowerReturn(MachineIRBuilder &MIRBuilder,
const Value *Val,
ArrayRef VRegs,
FunctionLoweringInfo &FLI,
Register SwiftErrorVReg) const override;
bool lowerFormalArguments(
MachineIRBuilder &MIRBuilder, const Function &F,
ArrayRef> VRegs,
FunctionLoweringInfo &FLI) const override;
bool enableBigEndian() const override { return true; }
};

The GlobalISel framework will call the lowerReturn() and lowerFormalArguments() methods when a function is translated. To translate a function call, you would need to override and implement the lowerCall() method as well. Please note that we also need to override enableBigEndian(). Without it, the wrong machine code would be generated.
For the implementation in the GISel/M88kCallLowering.cpp file, we need to define to support classes. The generated code from the target description tells us how a parameter is passed – for example, in a register. We need to create a subclass of ValueHandler to generate the machine instructions for it. For incoming parameters, we need to derive our class from IncomingValueHandler, as well as for the return value from OutgoingValueHandler. Both are very similar, so we’ll only look at the handler for incoming arguments:

namespace {
struct FormalArgHandler
: public CallLowering::IncomingValueHandler {
FormalArgHandler(MachineIRBuilder &MIRBuilder,
MachineRegisterInfo &MRI)
: CallLowering::IncomingValueHandler(MIRBuilder,
MRI) {}
void assignValueToReg(Register ValVReg,
Register PhysReg,
CCValAssign VA) override;
void assignValueToAddress(Register ValVReg,
Register Addr, LLT MemTy,
MachinePointerInfo &MPO,
CCValAssign &VA) override{};
Register
getStackAddress(uint64_t Size, int64_t Offset,
MachinePointerInfo &MPO,
ISD::ArgFlagsTy Flags) override {
return Register();
};
};
} // namespace

So far, we can only handle parameters passed in registers, so we must provide a dummy implementation for the other methods. The assignValueToReg() method copies the value of the incoming physical register to a virtual register, performing a truncation if necessary. All we have to do here is mark the physical register as live-in to the function, and call the superclass implementation:

void FormalArgHandler::assignValueToReg(
Register ValVReg, Register PhysReg,
CCValAssign VA) {
MIRBuilder.getMRI()->addLiveIn(PhysReg);
MIRBuilder.getMBB().addLiveIn(PhysReg);
CallLowering::IncomingValueHandler::assignValueToReg(
ValVReg, PhysReg, VA);
}

Now, we can implement the lowerFormalArgument() method:

  1. First, the parameters of the IR function are translated into instances of the ArgInfo class. The setArgFlags() and splitToValueTypes() framework methods help with copying the parameter attributes and splitting the value type in case an incoming argument needs more than one virtual register:

bool M88kCallLowering::lowerFormalArguments(
MachineIRBuilder &MIRBuilder, const Function &F,
ArrayRef> VRegs,
FunctionLoweringInfo &FLI) const {
MachineFunction &MF = MIRBuilder.getMF();
MachineRegisterInfo &MRI = MF.getRegInfo();
const auto &DL = F.getParent()->getDataLayout();
SmallVector SplitArgs;
for (const auto &[I, Arg] :
llvm::enumerate(F.args())) {
ArgInfo OrigArg{VRegs[I], Arg.getType(),
static_cast(I)};
setArgFlags(OrigArg,
I + AttributeList::FirstArgIndex, DL,
F);
splitToValueTypes(OrigArg, SplitArgs, DL,
F.getCallingConv());
}

  1. With the arguments prepared in the SplitArgs variable, we are ready to generate the machine code. This is all done by the framework code, with the help of the generated calling convention, CC_M88k, and our helper class, FormalArghandler: IncomingValueAssigner ArgAssigner(CC_M88k);
    FormalArgHandler ArgHandler(MIRBuilder, MRI);
    return determineAndHandleAssignments(
    ArgHandler, ArgAssigner, SplitArgs, MIRBuilder,
    F.getCallingConv(), F.isVarArg());
    }

Return values are handled similarly, with the main difference being that one value is returned at most. The next task is to legalize the generic machine instructions.

Leave a Reply

Your email address will not be published. Required fields are marked *