Skip to content

[InlineAsm] Steal a bit to denote a register is foldable #70738

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

Merged
merged 4 commits into from
Nov 3, 2023
Merged
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
4 changes: 4 additions & 0 deletions llvm/include/llvm/CodeGen/MachineInstr.h
Original file line number Diff line number Diff line change
Expand Up @@ -1364,6 +1364,10 @@ class MachineInstr
return getOpcode() == TargetOpcode::INLINEASM ||
getOpcode() == TargetOpcode::INLINEASM_BR;
}
/// Returns true if the register operand can be folded with a load or store
/// into a frame index. Does so by checking the InlineAsm::Flag immediate
/// operand at OpId - 1.
bool mayFoldInlineAsmRegOp(unsigned OpId) const;

bool isStackAligningInlineAsm() const;
InlineAsm::AsmDialect getInlineAsmDialect() const;
Expand Down
35 changes: 30 additions & 5 deletions llvm/include/llvm/IR/InlineAsm.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,18 +291,23 @@ class InlineAsm final : public Value {
// Bits 30-16 - A ConstraintCode:: value indicating the original
// constraint code. (MemConstraintCode)
// Else:
// Bits 30-16 - The register class ID to use for the operand. (RegClass)
// Bits 29-16 - The register class ID to use for the operand. (RegClass)
// Bit 30 - If the register is permitted to be spilled.
// (RegMayBeFolded)
// Defaults to false "r", may be set for constraints like
// "rm" (or "g").
//
// As such, MatchedOperandNo, MemConstraintCode, and RegClass are views of
// the same slice of bits, but are mutually exclusive depending on the
// fields IsMatched then KindField.
// As such, MatchedOperandNo, MemConstraintCode, and
// (RegClass+RegMayBeFolded) are views of the same slice of bits, but are
// mutually exclusive depending on the fields IsMatched then KindField.
class Flag {
uint32_t Storage;
using KindField = Bitfield::Element<Kind, 0, 3, Kind::Func>;
using NumOperands = Bitfield::Element<unsigned, 3, 13>;
using MatchedOperandNo = Bitfield::Element<unsigned, 16, 15>;
using MemConstraintCode = Bitfield::Element<ConstraintCode, 16, 15, ConstraintCode::Max>;
using RegClass = Bitfield::Element<unsigned, 16, 15>;
using RegClass = Bitfield::Element<unsigned, 16, 14>;
using RegMayBeFolded = Bitfield::Element<bool, 30, 1>;
using IsMatched = Bitfield::Element<bool, 31, 1>;


Expand Down Expand Up @@ -413,6 +418,26 @@ class InlineAsm final : public Value {
"Flag is not a memory or function constraint!");
Bitfield::set<MemConstraintCode>(Storage, ConstraintCode::Unknown);
}

/// Set a bit to denote that while this operand is some kind of register
/// (use, def, ...), a memory flag did appear in the original constraint
/// list. This is set by the instruction selection framework, and consumed
/// by the register allocator. While the register allocator is generally
/// responsible for spilling registers, we need to be able to distinguish
/// between registers that the register allocator has permission to fold
/// ("rm") vs ones it does not ("r"). This is because the inline asm may use
/// instructions which don't support memory addressing modes for that
/// operand.
void setRegMayBeFolded(bool B) {
assert((isRegDefKind() || isRegDefEarlyClobberKind() || isRegUseKind()) &&
"Must be reg");
Bitfield::set<RegMayBeFolded>(Storage, B);
}
bool getRegMayBeFolded() const {
assert((isRegDefKind() || isRegDefEarlyClobberKind() || isRegUseKind()) &&
"Must be reg");
return Bitfield::get<RegMayBeFolded>(Storage);
}
};

static std::vector<StringRef> getExtraInfoNames(unsigned ExtraInfo) {
Expand Down
23 changes: 23 additions & 0 deletions llvm/lib/CodeGen/MachineInstr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1792,6 +1792,12 @@ void MachineInstr::print(raw_ostream &OS, ModuleSlotTracker &MST,
if (F.isUseOperandTiedToDef(TiedTo))
OS << " tiedto:$" << TiedTo;

if ((F.isRegDefKind() || F.isRegDefEarlyClobberKind() ||
F.isRegUseKind()) &&
F.getRegMayBeFolded()) {
OS << " foldable";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this require new MIR printer/parser support and corresponding tests?

Copy link
Member Author

@nickdesaulniers nickdesaulniers Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a part of MIR I kind of curious about. Maybe you can double check my understanding?

(For the entire complete series I have locally) When I use llc -stop-before or -stop-after to generate the starting point of a new MIR test, foldable is printed as a comment, using my change to TargetInstrInfo::createMIROperandComment below. Example:

INLINEASM &"", 0 /* attdialect */, 1076101130 /* regdef:GR32 spillable */, def %0

But if I use llc -print-after-all (or -print-after=finalize-isel), it's printed via this method.

INLINEASM &"" [attdialect], $0:[regdef:GR32 spillable], %0:gr32

(Note that the MachineOperand is a 32b immediate encoding an InlineAsm::Flag, hence the value 1076101130 which we pretty print as $0:[regdef:GR32 spillable] to be somewhat more understandable).

So for the purposes of testing parsing MIR, I think the answer to the question of "does this need a parser test" is "no" because foldable is only ever printed inside a comment, which is what we'd need to be able to parse for MIR tests. IIUC, it seems the parser consumes the result of TargetInstrInfo::createMIROperandComment and not MachineInstr::print as far as this is concerned (and that result is a comment).

For the purposes of testing printing MIR, nothing will be setting this bit until the instruction selection frameworks later in the series. For those, I will add MIR tests that they use the above MachineInstr::print method to print, but IIUC it seems that you need to use -print-after with a pass that sets this bit in order to observe the result of the above method.

So can the printing test wait until then? Or is there some other way to provide code coverage of MachineInstr::print before then that I'm not thinking of? I could write a unittest if this is really necessary, but there doesn't seem to be much unittests for MIR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So right, it doesn't require MIR testing. This is one of the areas where the debug output and MIR are not identical but ideally would be. The magic number with comments in inline asm operands are a pain point for MIR test maintenance

}

OS << ']';

// Compute the index of the next operand descriptor.
Expand Down Expand Up @@ -2526,3 +2532,20 @@ void MachineInstr::insert(mop_iterator InsertBefore,
tieOperands(Tie1, Tie2);
}
}

bool MachineInstr::mayFoldInlineAsmRegOp(unsigned OpId) const {
assert(OpId && "expected non-zero operand id");
assert(isInlineAsm() && "should only be used on inline asm");

if (!getOperand(OpId).isReg())
return false;

const MachineOperand &MD = getOperand(OpId - 1);
if (!MD.isImm())
return false;

InlineAsm::Flag F(MD.getImm());
if (F.isRegUseKind() || F.isRegDefKind() || F.isRegDefEarlyClobberKind())
return F.getRegMayBeFolded();
return false;
}
4 changes: 4 additions & 0 deletions llvm/lib/CodeGen/TargetInstrInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,10 @@ std::string TargetInstrInfo::createMIROperandComment(
if (F.isUseOperandTiedToDef(TiedTo))
OS << " tiedto:$" << TiedTo;

if ((F.isRegDefKind() || F.isRegDefEarlyClobberKind() || F.isRegUseKind()) &&
F.getRegMayBeFolded())
OS << " foldable";

return OS.str();
}

Expand Down