Skip to content

Commit

Permalink
[RemoveDIs] Handle DPValues in FastISel (#76952)
Browse files Browse the repository at this point in the history
The change is fairly mechanical:
1. Factor code from `FastISel::selectIntrinsicCall`, which converts
debug intrinsics into debug instructions, into functions (NFC).
2. Call those functions for DPValues attached to instructions too.

The test updates look the same as other RemoveDIs changes: re-run the
tests with `--try-experimental-debuginfo-iterators`, which checks the
output is identical using the new debug info format (if it has been
enabled in the cmake configuration).

Depends on #76941 (otherwise some modified tests spuriously fail).
  • Loading branch information
OCHyams committed Jan 5, 2024
1 parent 736cc0c commit 10b03e6
Show file tree
Hide file tree
Showing 74 changed files with 398 additions and 151 deletions.
14 changes: 14 additions & 0 deletions llvm/include/llvm/CodeGen/FastISel.h
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,10 @@ class FastISel {
/// Reset InsertPt to the given old insert position.
void leaveLocalValueArea(SavePoint Old);

/// Target-independent lowering of non-instruction debug info associated with
/// this instruction.
void handleDbgInfo(const Instruction *II);

protected:
explicit FastISel(FunctionLoweringInfo &FuncInfo,
const TargetLibraryInfo *LibInfo,
Expand Down Expand Up @@ -518,6 +522,16 @@ class FastISel {
return MF->getFunction().hasOptSize();
}

/// Target-independent lowering of debug information. Returns false if the
/// debug information couldn't be lowered and was instead discarded.
virtual bool lowerDbgValue(const Value *V, DIExpression *Expr,
DILocalVariable *Var, const DebugLoc &DL);

/// Target-independent lowering of debug information. Returns false if the
/// debug information couldn't be lowered and was instead discarded.
virtual bool lowerDbgDeclare(const Value *V, DIExpression *Expr,
DILocalVariable *Var, const DebugLoc &DL);

private:
/// Handle PHI nodes in successor blocks.
///
Expand Down
323 changes: 188 additions & 135 deletions llvm/lib/CodeGen/SelectionDAG/FastISel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,184 @@ bool FastISel::selectCall(const User *I) {
return lowerCall(Call);
}

void FastISel::handleDbgInfo(const Instruction *II) {
if (!II->hasDbgValues())
return;

// Clear any metadata.
MIMD = MIMetadata();

// Reverse order of debug records, because fast-isel walks through backwards.
for (DPValue &DPV : llvm::reverse(II->getDbgValueRange())) {
flushLocalValueMap();
recomputeInsertPt();

Value *V = nullptr;
if (!DPV.hasArgList())
V = DPV.getVariableLocationOp(0);

bool Res = false;
if (DPV.getType() == DPValue::LocationType::Value) {
Res = lowerDbgValue(V, DPV.getExpression(), DPV.getVariable(),
DPV.getDebugLoc());
} else {
assert(DPV.getType() == DPValue::LocationType::Declare);
if (FuncInfo.PreprocessedDPVDeclares.contains(&DPV))
continue;
Res = lowerDbgDeclare(V, DPV.getExpression(), DPV.getVariable(),
DPV.getDebugLoc());
}

if (!Res)
LLVM_DEBUG(dbgs() << "Dropping debug-info for " << DPV << "\n";);
}
}

bool FastISel::lowerDbgValue(const Value *V, DIExpression *Expr,
DILocalVariable *Var, const DebugLoc &DL) {
// This form of DBG_VALUE is target-independent.
const MCInstrDesc &II = TII.get(TargetOpcode::DBG_VALUE);
if (!V || isa<UndefValue>(V)) {
// DI is either undef or cannot produce a valid DBG_VALUE, so produce an
// undef DBG_VALUE to terminate any prior location.
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL, II, false, 0U, Var, Expr);
return true;
}
if (const auto *CI = dyn_cast<ConstantInt>(V)) {
// See if there's an expression to constant-fold.
if (Expr)
std::tie(Expr, CI) = Expr->constantFold(CI);
if (CI->getBitWidth() > 64)
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL, II)
.addCImm(CI)
.addImm(0U)
.addMetadata(Var)
.addMetadata(Expr);
else
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL, II)
.addImm(CI->getZExtValue())
.addImm(0U)
.addMetadata(Var)
.addMetadata(Expr);
return true;
}
if (const auto *CF = dyn_cast<ConstantFP>(V)) {
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL, II)
.addFPImm(CF)
.addImm(0U)
.addMetadata(Var)
.addMetadata(Expr);
return true;
}
if (const auto *Arg = dyn_cast<Argument>(V);
Arg && Expr && Expr->isEntryValue()) {
// As per the Verifier, this case is only valid for swift async Args.
assert(Arg->hasAttribute(Attribute::AttrKind::SwiftAsync));

Register Reg = getRegForValue(Arg);
for (auto [PhysReg, VirtReg] : FuncInfo.RegInfo->liveins())
if (Reg == VirtReg || Reg == PhysReg) {
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL, II, false /*IsIndirect*/,
PhysReg, Var, Expr);
return true;
}

LLVM_DEBUG(dbgs() << "Dropping dbg.value: expression is entry_value but "
"couldn't find a physical register\n");
return false;
}
if (auto SI = FuncInfo.StaticAllocaMap.find(dyn_cast<AllocaInst>(V));
SI != FuncInfo.StaticAllocaMap.end()) {
MachineOperand FrameIndexOp = MachineOperand::CreateFI(SI->second);
bool IsIndirect = false;
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL, II, IsIndirect, FrameIndexOp,
Var, Expr);
return true;
}
if (Register Reg = lookUpRegForValue(V)) {
// FIXME: This does not handle register-indirect values at offset 0.
if (!FuncInfo.MF->useDebugInstrRef()) {
bool IsIndirect = false;
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL, II, IsIndirect, Reg, Var,
Expr);
return true;
}
// If using instruction referencing, produce this as a DBG_INSTR_REF,
// to be later patched up by finalizeDebugInstrRefs.
SmallVector<MachineOperand, 1> MOs({MachineOperand::CreateReg(
/* Reg */ Reg, /* isDef */ false, /* isImp */ false,
/* isKill */ false, /* isDead */ false,
/* isUndef */ false, /* isEarlyClobber */ false,
/* SubReg */ 0, /* isDebug */ true)});
SmallVector<uint64_t, 2> Ops({dwarf::DW_OP_LLVM_arg, 0});
auto *NewExpr = DIExpression::prependOpcodes(Expr, Ops);
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL,
TII.get(TargetOpcode::DBG_INSTR_REF), /*IsIndirect*/ false, MOs,
Var, NewExpr);
return true;
}
return false;
}

bool FastISel::lowerDbgDeclare(const Value *Address, DIExpression *Expr,
DILocalVariable *Var, const DebugLoc &DL) {
if (!Address || isa<UndefValue>(Address)) {
LLVM_DEBUG(dbgs() << "Dropping debug info (bad/undef address)\n");
return false;
}

std::optional<MachineOperand> Op;
if (Register Reg = lookUpRegForValue(Address))
Op = MachineOperand::CreateReg(Reg, false);

// If we have a VLA that has a "use" in a metadata node that's then used
// here but it has no other uses, then we have a problem. E.g.,
//
// int foo (const int *x) {
// char a[*x];
// return 0;
// }
//
// If we assign 'a' a vreg and fast isel later on has to use the selection
// DAG isel, it will want to copy the value to the vreg. However, there are
// no uses, which goes counter to what selection DAG isel expects.
if (!Op && !Address->use_empty() && isa<Instruction>(Address) &&
(!isa<AllocaInst>(Address) ||
!FuncInfo.StaticAllocaMap.count(cast<AllocaInst>(Address))))
Op = MachineOperand::CreateReg(FuncInfo.InitializeRegForValue(Address),
false);

if (Op) {
assert(Var->isValidLocationForIntrinsic(DL) &&
"Expected inlined-at fields to agree");
if (FuncInfo.MF->useDebugInstrRef() && Op->isReg()) {
// If using instruction referencing, produce this as a DBG_INSTR_REF,
// to be later patched up by finalizeDebugInstrRefs. Tack a deref onto
// the expression, we don't have an "indirect" flag in DBG_INSTR_REF.
SmallVector<uint64_t, 3> Ops(
{dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_deref});
auto *NewExpr = DIExpression::prependOpcodes(Expr, Ops);
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL,
TII.get(TargetOpcode::DBG_INSTR_REF), /*IsIndirect*/ false, *Op,
Var, NewExpr);
return true;
}

// A dbg.declare describes the address of a source variable, so lower it
// into an indirect DBG_VALUE.
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, DL,
TII.get(TargetOpcode::DBG_VALUE), /*IsIndirect*/ true, *Op, Var,
Expr);
return true;
}

// We can't yet handle anything else here because it would require
// generating code, thus altering codegen because of debug info.
LLVM_DEBUG(
dbgs() << "Dropping debug info (no materialized reg for address)\n");
return false;
}

bool FastISel::selectIntrinsicCall(const IntrinsicInst *II) {
switch (II->getIntrinsicID()) {
default:
Expand Down Expand Up @@ -1209,153 +1387,28 @@ bool FastISel::selectIntrinsicCall(const IntrinsicInst *II) {
return true;

const Value *Address = DI->getAddress();
if (!Address || isa<UndefValue>(Address)) {
LLVM_DEBUG(dbgs() << "Dropping debug info for " << *DI
<< " (bad/undef address)\n");
return true;
}
if (!lowerDbgDeclare(Address, DI->getExpression(), DI->getVariable(),
MIMD.getDL()))
LLVM_DEBUG(dbgs() << "Dropping debug info for " << *DI);

std::optional<MachineOperand> Op;
if (Register Reg = lookUpRegForValue(Address))
Op = MachineOperand::CreateReg(Reg, false);

// If we have a VLA that has a "use" in a metadata node that's then used
// here but it has no other uses, then we have a problem. E.g.,
//
// int foo (const int *x) {
// char a[*x];
// return 0;
// }
//
// If we assign 'a' a vreg and fast isel later on has to use the selection
// DAG isel, it will want to copy the value to the vreg. However, there are
// no uses, which goes counter to what selection DAG isel expects.
if (!Op && !Address->use_empty() && isa<Instruction>(Address) &&
(!isa<AllocaInst>(Address) ||
!FuncInfo.StaticAllocaMap.count(cast<AllocaInst>(Address))))
Op = MachineOperand::CreateReg(FuncInfo.InitializeRegForValue(Address),
false);

if (Op) {
assert(DI->getVariable()->isValidLocationForIntrinsic(MIMD.getDL()) &&
"Expected inlined-at fields to agree");
if (FuncInfo.MF->useDebugInstrRef() && Op->isReg()) {
// If using instruction referencing, produce this as a DBG_INSTR_REF,
// to be later patched up by finalizeDebugInstrRefs. Tack a deref onto
// the expression, we don't have an "indirect" flag in DBG_INSTR_REF.
SmallVector<uint64_t, 3> Ops(
{dwarf::DW_OP_LLVM_arg, 0, dwarf::DW_OP_deref});
auto *NewExpr = DIExpression::prependOpcodes(DI->getExpression(), Ops);
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD.getDL(),
TII.get(TargetOpcode::DBG_INSTR_REF), /*IsIndirect*/ false, *Op,
DI->getVariable(), NewExpr);
} else {
// A dbg.declare describes the address of a source variable, so lower it
// into an indirect DBG_VALUE.
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD.getDL(),
TII.get(TargetOpcode::DBG_VALUE), /*IsIndirect*/ true, *Op,
DI->getVariable(), DI->getExpression());
}
} else {
// We can't yet handle anything else here because it would require
// generating code, thus altering codegen because of debug info.
LLVM_DEBUG(dbgs() << "Dropping debug info for " << *DI
<< " (no materialized reg for address)\n");
}
return true;
}
case Intrinsic::dbg_value: {
// This form of DBG_VALUE is target-independent.
const DbgValueInst *DI = cast<DbgValueInst>(II);
const MCInstrDesc &II = TII.get(TargetOpcode::DBG_VALUE);
const Value *V = DI->getValue();
DIExpression *Expr = DI->getExpression();
DILocalVariable *Var = DI->getVariable();
if (DI->hasArgList())
// Signal that we don't have a location for this.
V = nullptr;

assert(Var->isValidLocationForIntrinsic(MIMD.getDL()) &&
"Expected inlined-at fields to agree");
if (!V || isa<UndefValue>(V) || DI->hasArgList()) {
// DI is either undef or cannot produce a valid DBG_VALUE, so produce an
// undef DBG_VALUE to terminate any prior location.
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD.getDL(), II, false, 0U,
Var, Expr);
return true;
}
if (const auto *CI = dyn_cast<ConstantInt>(V)) {
// See if there's an expression to constant-fold.
if (Expr)
std::tie(Expr, CI) = Expr->constantFold(CI);
if (CI->getBitWidth() > 64)
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD, II)
.addCImm(CI)
.addImm(0U)
.addMetadata(Var)
.addMetadata(Expr);
else
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD, II)
.addImm(CI->getZExtValue())
.addImm(0U)
.addMetadata(Var)
.addMetadata(Expr);
return true;
}
if (const auto *CF = dyn_cast<ConstantFP>(V)) {
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD, II)
.addFPImm(CF)
.addImm(0U)
.addMetadata(Var)
.addMetadata(Expr);
return true;
}
if (const auto *Arg = dyn_cast<Argument>(V);
Arg && Expr && Expr->isEntryValue()) {
// As per the Verifier, this case is only valid for swift async Args.
assert(Arg->hasAttribute(Attribute::AttrKind::SwiftAsync));

Register Reg = getRegForValue(Arg);
for (auto [PhysReg, VirtReg] : FuncInfo.RegInfo->liveins())
if (Reg == VirtReg || Reg == PhysReg) {
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD.getDL(), II,
false /*IsIndirect*/, PhysReg, Var, Expr);
return true;
}

LLVM_DEBUG(dbgs() << "Dropping dbg.value: expression is entry_value but "
"couldn't find a physical register\n"
<< *DI << "\n");
return true;
}
if (auto SI = FuncInfo.StaticAllocaMap.find(dyn_cast<AllocaInst>(V));
SI != FuncInfo.StaticAllocaMap.end()) {
MachineOperand FrameIndexOp = MachineOperand::CreateFI(SI->second);
bool IsIndirect = false;
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD.getDL(), II, IsIndirect,
FrameIndexOp, Var, Expr);
return true;
}
if (Register Reg = lookUpRegForValue(V)) {
// FIXME: This does not handle register-indirect values at offset 0.
if (!FuncInfo.MF->useDebugInstrRef()) {
bool IsIndirect = false;
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD.getDL(), II, IsIndirect,
Reg, Var, Expr);
return true;
}
// If using instruction referencing, produce this as a DBG_INSTR_REF,
// to be later patched up by finalizeDebugInstrRefs.
SmallVector<MachineOperand, 1> MOs({MachineOperand::CreateReg(
/* Reg */ Reg, /* isDef */ false, /* isImp */ false,
/* isKill */ false, /* isDead */ false,
/* isUndef */ false, /* isEarlyClobber */ false,
/* SubReg */ 0, /* isDebug */ true)});
SmallVector<uint64_t, 2> Ops({dwarf::DW_OP_LLVM_arg, 0});
auto *NewExpr = DIExpression::prependOpcodes(Expr, Ops);
BuildMI(*FuncInfo.MBB, FuncInfo.InsertPt, MIMD.getDL(),
TII.get(TargetOpcode::DBG_INSTR_REF), /*IsIndirect*/ false, MOs,
Var, NewExpr);
return true;
}
// We don't know how to handle other cases, so we drop.
LLVM_DEBUG(dbgs() << "Dropping debug info for " << *DI << "\n");
if (!lowerDbgValue(V, Expr, Var, MIMD.getDL()))
LLVM_DEBUG(dbgs() << "Dropping debug info for " << *DI << "\n");

return true;
}
case Intrinsic::dbg_label: {
Expand Down

0 comments on commit 10b03e6

Please sign in to comment.