LLVM so far has only supported the MIPS-II and above architectures. MIPS-II is pretty close to MIPS-I, the major difference
being that "load" instructions always take one extra instruction slot to propogate to registers. This patch adds support for
MIPS-I by adding hazard handling for load delay slots, alongside MIPSR6 forbidden slots and FPU slots, inserting a NOP
instruction between a load and any instruction immediately following that reads the load's destination register. I also
included a simple regression test. Since no existing tests target MIPS-I, those all still pass.
Issue ref: https://github.com/simias/psx-sdk-rs/issues/1
I also tested by building a simple demo app with Clang and running it in an emulator.
Patch by: @impiaaa
Differential Revision: https://reviews.llvm.org/D122427
///
/// Regarding compact branch hazard prevention:
///
-/// Hazards handled: forbidden slots for MIPSR6, FPU slots for MIPS3 and below.
+/// Hazards handled: forbidden slots for MIPSR6, FPU slots for MIPS3 and below,
+/// load delay slots for MIPS1.
///
/// A forbidden slot hazard occurs when a compact branch instruction is executed
/// and the adjacent instruction in memory is a control transfer instruction
bool handleSlot(Pred Predicate, Safe SafeInSlot);
bool handleForbiddenSlot();
bool handleFPUDelaySlot();
+ bool handleLoadDelaySlot();
bool handlePossibleLongBranch();
const MipsSubtarget *STI;
}
if (LastInstInFunction || !SafeInSlot(*IInSlot, *I)) {
-
MachineBasicBlock::instr_iterator Iit = I->getIterator();
if (std::next(Iit) == FI->end() ||
std::next(Iit)->getOpcode() != Mips::NOP) {
});
}
+bool MipsBranchExpansion::handleLoadDelaySlot() {
+ // Load delay slot hazards are only for MIPS1.
+ if (STI->hasMips2())
+ return false;
+
+ return handleSlot(
+ [this](auto &I) -> bool { return TII->HasLoadDelaySlot(I); },
+ [this](auto &IInSlot, auto &I) -> bool {
+ return TII->SafeInLoadDelaySlot(IInSlot, I);
+ });
+}
+
bool MipsBranchExpansion::handlePossibleLongBranch() {
if (STI->inMips16Mode() || !STI->enableLongBranchPass())
return false;
MFp = &MF;
ForceLongBranchFirstPass = ForceLongBranch;
- // Run these two at least once
+ // Run these at least once.
bool longBranchChanged = handlePossibleLongBranch();
bool forbiddenSlotChanged = handleForbiddenSlot();
bool fpuDelaySlotChanged = handleFPUDelaySlot();
+ bool loadDelaySlotChanged = handleLoadDelaySlot();
- bool Changed =
- longBranchChanged || forbiddenSlotChanged || fpuDelaySlotChanged;
+ bool Changed = longBranchChanged || forbiddenSlotChanged ||
+ fpuDelaySlotChanged || loadDelaySlotChanged;
- // Then run them alternatively while there are changes
+ // Then run them alternatively while there are changes.
while (forbiddenSlotChanged) {
longBranchChanged = handlePossibleLongBranch();
fpuDelaySlotChanged = handleFPUDelaySlot();
- if (!longBranchChanged && !fpuDelaySlotChanged)
+ loadDelaySlotChanged = handleLoadDelaySlot();
+ if (!longBranchChanged && !fpuDelaySlotChanged && !loadDelaySlotChanged)
break;
forbiddenSlotChanged = handleForbiddenSlot();
}
return true;
}
+/// Predicate for distinguishing instructions that are hazardous in a load delay
+/// slot. Consider inline assembly as unsafe as well.
+bool MipsInstrInfo::SafeInLoadDelaySlot(const MachineInstr &MIInSlot,
+ const MachineInstr &LoadMI) const {
+ if (MIInSlot.isInlineAsm())
+ return false;
+
+ return !llvm::any_of(LoadMI.defs(), [&](const MachineOperand &Op) {
+ return Op.isReg() && MIInSlot.readsRegister(Op.getReg());
+ });
+}
+
/// Predicate for distingushing instructions that have forbidden slots.
bool MipsInstrInfo::HasForbiddenSlot(const MachineInstr &MI) const {
return (MI.getDesc().TSFlags & MipsII::HasForbiddenSlot) != 0;
}
}
+/// Predicate for distingushing instructions that have load delay slots.
+bool MipsInstrInfo::HasLoadDelaySlot(const MachineInstr &MI) const {
+ switch (MI.getOpcode()) {
+ case Mips::LB:
+ case Mips::LBu:
+ case Mips::LH:
+ case Mips::LHu:
+ case Mips::LW:
+ case Mips::LWR:
+ case Mips::LWL:
+ return true;
+ default:
+ return false;
+ }
+}
+
/// Return the number of bytes of code the specified instruction may be.
unsigned MipsInstrInfo::getInstSizeInBytes(const MachineInstr &MI) const {
switch (MI.getOpcode()) {
bool SafeInFPUDelaySlot(const MachineInstr &MIInSlot,
const MachineInstr &FPUMI) const;
+ /// Predicate to determine if an instruction can go in a load delay slot.
+ bool SafeInLoadDelaySlot(const MachineInstr &MIInSlot,
+ const MachineInstr &LoadMI) const;
+
/// Predicate to determine if an instruction has a forbidden slot.
bool HasForbiddenSlot(const MachineInstr &MI) const;
/// Predicate to determine if an instruction has an FPU delay slot.
bool HasFPUDelaySlot(const MachineInstr &MI) const;
+ /// Predicate to determine if an instruction has a load delay slot.
+ bool HasLoadDelaySlot(const MachineInstr &MI) const;
+
/// Insert nop instruction when hazard condition is found
void insertNoop(MachineBasicBlock &MBB,
MachineBasicBlock::iterator MI) const override;
bool MipsSubtarget::VirtWarningPrinted = false;
bool MipsSubtarget::CRCWarningPrinted = false;
bool MipsSubtarget::GINVWarningPrinted = false;
+bool MipsSubtarget::MIPS1WarningPrinted = false;
void MipsSubtarget::anchor() {}
if (MipsArchVersion == MipsDefault)
MipsArchVersion = Mips32;
- // Don't even attempt to generate code for MIPS-I and MIPS-V. They have not
- // been tested and currently exist for the integrated assembler only.
- if (MipsArchVersion == Mips1)
- report_fatal_error("Code generation for MIPS-I is not implemented", false);
+ // MIPS-I has not been tested.
+ if (MipsArchVersion == Mips1 && !MIPS1WarningPrinted) {
+ errs() << "warning: MIPS-I support is experimental\n";
+ MIPS1WarningPrinted = true;
+ }
+
+ // Don't even attempt to generate code for MIPS-V. It has not
+ // been tested and currently exists for the integrated assembler only.
if (MipsArchVersion == Mips5)
report_fatal_error("Code generation for MIPS-V is not implemented", false);
// Used to avoid printing ginv warnings multiple times.
static bool GINVWarningPrinted;
+ // Used to avoid printing Mips1 warnings multiple times.
+ static bool MIPS1WarningPrinted;
+
// Used to avoid printing virt warnings multiple times.
static bool VirtWarningPrinted;
; RUN: | llvm-readelf -A - | FileCheck %s --check-prefix=GENERIC
; GENERIC: ISA: MIPS32
+; RUN: llc -mtriple=mips -mcpu=mips1 -filetype=obj < %s \
+; RUN: | llvm-readelf -A - | FileCheck %s --check-prefix=MIPS1
+; MIPS1: ISA: MIPS1
; RUN: llc -mtriple=mips -mcpu=mips2 -filetype=obj < %s \
; RUN: | llvm-readelf -A - | FileCheck %s --check-prefix=MIPS2
; MIPS2: ISA: MIPS2
; Check that we reject CPUs that are not implemented.
-; RUN: not --crash llc < %s -o /dev/null -mtriple=mips -mcpu=mips1 2>&1 \
-; RUN: | FileCheck %s --check-prefix=ERROR
; RUN: not --crash llc < %s -o /dev/null -mtriple=mips64 -mcpu=mips5 2>&1 \
; RUN: | FileCheck %s --check-prefix=ERROR
--- /dev/null
+; RUN: llc < %s -mtriple=mips -mcpu=mips1 | FileCheck %s -check-prefixes=ALL,MIPS1
+; RUN: llc < %s -mtriple=mips -mcpu=mips2 | FileCheck %s -check-prefixes=ALL,MIPS2
+; RUN: llc < %s -mtriple=mips -mcpu=mips32r2 | FileCheck %s -check-prefixes=ALL,MIPS32
+target datalayout = "e-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64"
+target triple = "mipsel-unknown-unknown-elf"
+
+; Function Attrs: noinline nounwind optnone
+define dso_local i32 @add_two_pointers(i32* %a, i32* %b) #0 {
+entry:
+; ALL-LABEL: add_two_pointers:
+ %a.addr = alloca i32*, align 4
+ %b.addr = alloca i32*, align 4
+ store i32* %a, i32** %a.addr, align 4
+ store i32* %b, i32** %b.addr, align 4
+ %0 = load i32*, i32** %a.addr, align 4
+ %1 = load i32, i32* %0, align 4
+ ; ALL: lw $1, 4($fp)
+ ; MIPS1: nop
+ ; MIPS2-NOT: nop
+ ; MIPS32-NOT: nop
+ ; ALL: lw $1, 0($1)
+ %2 = load i32*, i32** %b.addr, align 4
+ %3 = load i32, i32* %2, align 4
+ ; ALL: lw $2, 0($fp)
+ ; MIPS1: nop
+ ; MIPS2-NOT: nop
+ ; MIPS32-NOT: nop
+ ; ALL: lw $2, 0($2)
+ %add = add nsw i32 %1, %3
+ ret i32 %add
+ ; ALL: lw $ra, 12($sp)
+ ; MIPS1: nop
+ ; MIPS2-NOT: nop
+ ; MIPS32-NOT: nop
+ ; ALL: jr $ra
+}
+
+attributes #0 = { noinline nounwind optnone "frame-pointer"="all" "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="-noabicalls" }
+
+!llvm.module.flags = !{!0, !1}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 7, !"frame-pointer", i32 2}
+