[DebugInstrRef] Support recording of instruction reference substitutions
Add a table recording "substitutions" between pairs of <instruction, operand> numbers, from old pairs to new pairs. Post-isel optimizations are able to record the outcome of an optimization in this way. For example, if there were a divide instruction that generated the quotient and remainder, and it were replaced by one that only generated the quotient: $rax, $rcx = DIV-AND-REMAINDER $rdx, $rsi, debug-instr-num 1 DBG_INSTR_REF 1, 0 DBG_INSTR_REF 1, 1 Became: $rax = DIV $rdx, $rsi, debug-instr-num 2 DBG_INSTR_REF 1, 0 DBG_INSTR_REF 1, 1 We could enter a substitution from <1, 0> to <2, 0>, and no substitution for <1, 1> as it's no longer generated. This approach means that if an instruction or value is deleted once we've left SSA form, all variables that used the value implicitly become "optimized out", something that isn't true of the current DBG_VALUE approach. Differential Revision: https://reviews.llvm.org/D85749
This commit is contained in:
@@ -441,6 +441,36 @@ template <> struct MappingTraits<CallSiteInfo> {
|
||||
static const bool flow = true;
|
||||
};
|
||||
|
||||
/// Serializable representation of debug value substitutions.
|
||||
struct DebugValueSubstitution {
|
||||
unsigned SrcInst;
|
||||
unsigned SrcOp;
|
||||
unsigned DstInst;
|
||||
unsigned DstOp;
|
||||
|
||||
bool operator==(const DebugValueSubstitution &Other) const {
|
||||
return std::tie(SrcInst, SrcOp, DstInst, DstOp) ==
|
||||
std::tie(Other.SrcInst, Other.SrcOp, Other.DstInst, Other.DstOp);
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct MappingTraits<DebugValueSubstitution> {
|
||||
static void mapping(IO &YamlIO, DebugValueSubstitution &Sub) {
|
||||
YamlIO.mapRequired("srcinst", Sub.SrcInst);
|
||||
YamlIO.mapRequired("srcop", Sub.SrcOp);
|
||||
YamlIO.mapRequired("dstinst", Sub.DstInst);
|
||||
YamlIO.mapRequired("dstop", Sub.DstOp);
|
||||
}
|
||||
|
||||
static const bool flow = true;
|
||||
};
|
||||
} // namespace yaml
|
||||
} // namespace llvm
|
||||
|
||||
LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::yaml::DebugValueSubstitution)
|
||||
|
||||
namespace llvm {
|
||||
namespace yaml {
|
||||
struct MachineConstantPoolValue {
|
||||
UnsignedValue ID;
|
||||
StringValue Value;
|
||||
@@ -625,6 +655,7 @@ struct MachineFunction {
|
||||
std::vector<MachineConstantPoolValue> Constants; /// Constant pool.
|
||||
std::unique_ptr<MachineFunctionInfo> MachineFuncInfo;
|
||||
std::vector<CallSiteInfo> CallSitesInfo;
|
||||
std::vector<DebugValueSubstitution> DebugValueSubstitutions;
|
||||
MachineJumpTable JumpTableInfo;
|
||||
BlockStringValue Body;
|
||||
};
|
||||
@@ -653,6 +684,8 @@ template <> struct MappingTraits<MachineFunction> {
|
||||
std::vector<MachineStackObject>());
|
||||
YamlIO.mapOptional("callSites", MF.CallSitesInfo,
|
||||
std::vector<CallSiteInfo>());
|
||||
YamlIO.mapOptional("debugValueSubstitutions", MF.DebugValueSubstitutions,
|
||||
std::vector<DebugValueSubstitution>());
|
||||
YamlIO.mapOptional("constants", MF.Constants,
|
||||
std::vector<MachineConstantPoolValue>());
|
||||
YamlIO.mapOptional("machineFunctionInfo", MF.MachineFuncInfo);
|
||||
|
||||
@@ -440,6 +440,26 @@ public:
|
||||
/// you're deserializing this data.
|
||||
void setDebugInstrNumberingCount(unsigned Num);
|
||||
|
||||
/// Pair of instruction number and operand number.
|
||||
using DebugInstrOperandPair = std::pair<unsigned, unsigned>;
|
||||
|
||||
/// Substitution map: from one <inst,operand> pair to another. Used to
|
||||
/// record changes in where a value is defined, so that debug variable
|
||||
/// locations can find it later.
|
||||
std::map<DebugInstrOperandPair, DebugInstrOperandPair>
|
||||
DebugValueSubstitutions;
|
||||
|
||||
/// Create a substitution between one <instr,operand> value to a different,
|
||||
/// new value.
|
||||
void makeDebugValueSubstitution(DebugInstrOperandPair, DebugInstrOperandPair);
|
||||
|
||||
/// Create substitutions for any tracked values in \p Old, to point at
|
||||
/// \p New. Needed when we re-create an instruction during optimization,
|
||||
/// which has the same signature (i.e., def operands in the same place) but
|
||||
/// a modified instruction type, flags, or otherwise. An example: X86 moves
|
||||
/// are sometimes transformed into equivalent LEAs.
|
||||
void substituteDebugValuesForInst(const MachineInstr &Old, MachineInstr &New);
|
||||
|
||||
MachineFunction(Function &F, const LLVMTargetMachine &Target,
|
||||
const TargetSubtargetInfo &STI, unsigned FunctionNum,
|
||||
MachineModuleInfo &MMI);
|
||||
|
||||
@@ -405,17 +405,22 @@ bool MIRParserImpl::initializeCallSiteInfo(
|
||||
return false;
|
||||
}
|
||||
|
||||
void MIRParserImpl::setupDebugValueTracking(MachineFunction &MF,
|
||||
PerFunctionMIParsingState &PFS, const yaml::MachineFunction &YamlMF) {
|
||||
// For now, we only compute the value of the "next instruction number"
|
||||
// field.
|
||||
void MIRParserImpl::setupDebugValueTracking(
|
||||
MachineFunction &MF, PerFunctionMIParsingState &PFS,
|
||||
const yaml::MachineFunction &YamlMF) {
|
||||
// Compute the value of the "next instruction number" field.
|
||||
unsigned MaxInstrNum = 0;
|
||||
for (auto &MBB : MF)
|
||||
for (auto &MI : MBB)
|
||||
MaxInstrNum = std::max((unsigned)MI.peekDebugInstrNum(), MaxInstrNum);
|
||||
MF.setDebugInstrNumberingCount(MaxInstrNum);
|
||||
}
|
||||
|
||||
// Load any substitutions.
|
||||
for (auto &Sub : YamlMF.DebugValueSubstitutions) {
|
||||
MF.makeDebugValueSubstitution(std::make_pair(Sub.SrcInst, Sub.SrcOp),
|
||||
std::make_pair(Sub.DstInst, Sub.DstOp));
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
MIRParserImpl::initializeMachineFunction(const yaml::MachineFunction &YamlMF,
|
||||
|
||||
@@ -220,6 +220,10 @@ void MIRPrinter::print(const MachineFunction &MF) {
|
||||
convert(MST, YamlMF.FrameInfo, MF.getFrameInfo());
|
||||
convertStackObjects(YamlMF, MF, MST);
|
||||
convertCallSiteObjects(YamlMF, MF, MST);
|
||||
for (auto &Sub : MF.DebugValueSubstitutions)
|
||||
YamlMF.DebugValueSubstitutions.push_back({Sub.first.first, Sub.first.second,
|
||||
Sub.second.first,
|
||||
Sub.second.second});
|
||||
if (const auto *ConstantPool = MF.getConstantPool())
|
||||
convert(YamlMF, *ConstantPool);
|
||||
if (const auto *JumpTableInfo = MF.getJumpTableInfo())
|
||||
|
||||
@@ -947,6 +947,38 @@ void MachineFunction::setDebugInstrNumberingCount(unsigned Num) {
|
||||
DebugInstrNumberingCount = Num;
|
||||
}
|
||||
|
||||
void MachineFunction::makeDebugValueSubstitution(DebugInstrOperandPair A,
|
||||
DebugInstrOperandPair B) {
|
||||
auto Result = DebugValueSubstitutions.insert(std::make_pair(A, B));
|
||||
(void)Result;
|
||||
assert(Result.second && "Substitution for an already substituted value?");
|
||||
}
|
||||
|
||||
void MachineFunction::substituteDebugValuesForInst(const MachineInstr &Old,
|
||||
MachineInstr &New) {
|
||||
// If the Old instruction wasn't tracked at all, there is no work to do.
|
||||
unsigned OldInstrNum = Old.peekDebugInstrNum();
|
||||
if (!OldInstrNum)
|
||||
return;
|
||||
|
||||
// Iterate over all operands looking for defs to create substitutions for.
|
||||
// Avoid creating new instr numbers unless we create a new substitution.
|
||||
// While this has no functional effect, it risks confusing someone reading
|
||||
// MIR output.
|
||||
for (unsigned int I = 0; I < Old.getNumOperands(); ++I) {
|
||||
const auto &OldMO = Old.getOperand(I);
|
||||
auto &NewMO = Old.getOperand(I);
|
||||
|
||||
if (!OldMO.isReg() || !OldMO.isDef())
|
||||
continue;
|
||||
assert(NewMO.isDef());
|
||||
|
||||
unsigned NewInstrNum = New.getDebugInstrNum();
|
||||
makeDebugValueSubstitution(std::make_pair(OldInstrNum, I),
|
||||
std::make_pair(NewInstrNum, I));
|
||||
}
|
||||
}
|
||||
|
||||
/// \}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
26
llvm/test/DebugInfo/MIR/InstrRef/substitusions-roundtrip.mir
Normal file
26
llvm/test/DebugInfo/MIR/InstrRef/substitusions-roundtrip.mir
Normal file
@@ -0,0 +1,26 @@
|
||||
# RUN: llc %s -march=x86-64 -run-pass=machineverifier \
|
||||
# RUN: -experimental-debug-variable-locations -o - 2>&1 | FileCheck %s
|
||||
#
|
||||
# REQUIRES: x86-registered-target
|
||||
#
|
||||
# CHECK: debugValueSubstitutions:
|
||||
# CHECK-NEXT: - { srcinst: 1, srcop: 0, dstinst: 2, dstop: 0 }
|
||||
#
|
||||
# CHECK: MOV64rr $rdi, debug-instr-number 2
|
||||
# CHECK-NEXT: DBG_INSTR_REF 1, 0
|
||||
---
|
||||
name: test
|
||||
tracksRegLiveness: true
|
||||
liveins:
|
||||
- { reg: '$rdi', virtual-reg: '' }
|
||||
debugValueSubstitutions:
|
||||
- { srcinst: 1, srcop: 0, dstinst: 2, dstop: 0 }
|
||||
body: |
|
||||
bb.0:
|
||||
liveins: $rdi, $rax
|
||||
$rbp = MOV64rr $rdi, debug-instr-number 2
|
||||
DBG_INSTR_REF 1, 0
|
||||
dead $rcx = MOV64ri 0
|
||||
CMP64ri8 renamable $rax, 1, implicit-def $eflags
|
||||
RETQ $rax
|
||||
...
|
||||
Reference in New Issue
Block a user