-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[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
Conversation
When using the inline asm constraint string "rm" (or "g"), we generally would like the compiler to choose "r", but it is permitted to choose "m" if there's register pressure. This is distinct from "r" in which the register is not permitted to be spilled to the stack. The decision of which to use must be made at some point. Currently, the instruction selection frameworks (ISELs) make the choice, and the register allocators had better be able to handle the result. Steal a bit from Storage when using registers operands to disambiguate between the two cases. Add helpers/getters/setters, and print in MIR when such a register is spillable. The getter will later be used by the register allocation frameworks (and asserted by the ISELs) while the setters will be used by the instruction selection frameworks.
@llvm/pr-subscribers-llvm-ir Author: Nick Desaulniers (nickdesaulniers) ChangesWhen using the inline asm constraint string "rm" (or "g"), we generally The decision of which to use must be made at some point. Currently, the Steal a bit from Storage when using registers operands to disambiguate The getter will later be used by the register allocation frameworks (and Full diff: https://github.com/llvm/llvm-project/pull/70738.diff 4 Files Affected:
diff --git a/llvm/include/llvm/CodeGen/MachineInstr.h b/llvm/include/llvm/CodeGen/MachineInstr.h
index 4877f43e8578d1c..93e8ff389d65673 100644
--- a/llvm/include/llvm/CodeGen/MachineInstr.h
+++ b/llvm/include/llvm/CodeGen/MachineInstr.h
@@ -1364,6 +1364,9 @@ class MachineInstr
return getOpcode() == TargetOpcode::INLINEASM ||
getOpcode() == TargetOpcode::INLINEASM_BR;
}
+ /// Returns true if the memory operand can be folded. Does so by checking the
+ /// InlineAsm::Flag immediate operand at OpId - 1.
+ bool mayFoldInlineAsmMemOp(unsigned OpId) const;
bool isStackAligningInlineAsm() const;
InlineAsm::AsmDialect getInlineAsmDialect() const;
diff --git a/llvm/include/llvm/IR/InlineAsm.h b/llvm/include/llvm/IR/InlineAsm.h
index 969ad42816a7e52..2d395a53608b0b7 100644
--- a/llvm/include/llvm/IR/InlineAsm.h
+++ b/llvm/include/llvm/IR/InlineAsm.h
@@ -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.
+ // (RegMayBeSpilled)
+ // 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+RegMayBeSpilled) 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 RegMayBeSpilled = Bitfield::Element<bool, 30, 1>;
using IsMatched = Bitfield::Element<bool, 31, 1>;
@@ -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 spill
+ /// ("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 setRegMayBeSpilled(bool B) {
+ assert((isRegDefKind() || isRegDefEarlyClobberKind() || isRegUseKind()) &&
+ "Must be reg");
+ Bitfield::set<RegMayBeSpilled>(Storage, B);
+ }
+ bool getRegMayBeSpilled() const {
+ assert((isRegDefKind() || isRegDefEarlyClobberKind() || isRegUseKind()) &&
+ "Must be reg");
+ return Bitfield::get<RegMayBeSpilled>(Storage);
+ }
};
static std::vector<StringRef> getExtraInfoNames(unsigned ExtraInfo) {
diff --git a/llvm/lib/CodeGen/MachineInstr.cpp b/llvm/lib/CodeGen/MachineInstr.cpp
index 048563cc2bcc4e4..92c789e85a205b4 100644
--- a/llvm/lib/CodeGen/MachineInstr.cpp
+++ b/llvm/lib/CodeGen/MachineInstr.cpp
@@ -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.getRegMayBeSpilled()) {
+ OS << " spillable";
+ }
+
OS << ']';
// Compute the index of the next operand descriptor.
@@ -2526,3 +2532,20 @@ void MachineInstr::insert(mop_iterator InsertBefore,
tieOperands(Tie1, Tie2);
}
}
+
+bool MachineInstr::mayFoldInlineAsmMemOp(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.getRegMayBeSpilled();
+ return false;
+}
diff --git a/llvm/lib/CodeGen/TargetInstrInfo.cpp b/llvm/lib/CodeGen/TargetInstrInfo.cpp
index fe7efb73a2dce83..bcf9105ea64ca96 100644
--- a/llvm/lib/CodeGen/TargetInstrInfo.cpp
+++ b/llvm/lib/CodeGen/TargetInstrInfo.cpp
@@ -1639,6 +1639,10 @@ std::string TargetInstrInfo::createMIROperandComment(
if (F.isUseOperandTiedToDef(TiedTo))
OS << " tiedto:$" << TiedTo;
+ if ((F.isRegDefKind() || F.isRegDefEarlyClobberKind() || F.isRegUseKind()) &&
+ F.getRegMayBeSpilled())
+ OS << " spillable";
+
return OS.str();
}
|
Note to reviewers: I'm trying to break up my series into more reviewable pieces. Please let know if you'd prefer to have everything all in one PR. I'm going to try my hand at a stacked PR (though I'm skeptical of that) |
The name "spillable" seems odd to me. I usually only talk about registers being spilled, for an operand I would not use this term... |
perhaps "foldable" is better? "memory foldable" ? "may spill", etc?
Only |
I would say those operands reference a register. But typically multiple operands use the same registers and the act of spilling affects all operands referencing that (virtual-)register... |
For reference, here's the output from ISEL (from the complete series):
So for an INLINEASM's first MachineOperand is the string literal of asm to send straight through to the backend. So "given
What would you call it then? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this context, I don't have an issue with calling this spillable as it matches the LiveInterval
naming markNotSpillable
.
I let you guys sort out the naming, but this LGTM otherwise.
In other words, don't merge before @MatzeB and you agree.
Calling a I'd be fine with |
For the sake of brevity, can we go with just |
Fine with me too. |
Done in 14d69cf PTAL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
if ((F.isRegDefKind() || F.isRegDefEarlyClobberKind() || | ||
F.isRegUseKind()) && | ||
F.getRegMayBeFolded()) { | ||
OS << " foldable"; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
When using the inline asm constraint string "rm" (or "g"), we generally
would like the compiler to choose "r", but it is permitted to choose "m"
if there's register pressure. This is distinct from "r" in which the
register is not permitted to be spilled to the stack.
The decision of which to use must be made at some point. Currently, the
instruction selection frameworks (ISELs) make the choice, and the
register allocators had better be able to handle the result.
Steal a bit from Storage when using registers operands to disambiguate
between the two cases. Add helpers/getters/setters, and print in MIR
when such a register is spillable.
The getter will later be used by the register allocation frameworks (and
asserted by the ISELs) while the setters will be used by the instruction
selection frameworks.
Link: #20571