Register-based `DBG_VALUE` should have been converted into either local-based or stack-operand-based, so at this point they don't contain any information. This CL nullifies them, i.e., turning them info `DBG_VALUE $noreg, $noreg, ...`, which makes the variable appear as "optimized out". It is not safe to simply remove these instruction because at this point, because the fact that there is a `DBG_VALUE` here means the location this variable resides has changed to somewhere else; we've lost that information where that was. --- The below is not really about the CL itself, but a pre-existing bug in `llvm-locstats` variable coverage we are going to be affected after this CL. Feel free to skip if you don't have time. This CL has an unexpected side effect of increasing variable coverage reported by `llvm-locstats`, due to a bug mentioned in https://lists.llvm.org/pipermail/llvm-dev/2021-January/148139.html. Currently inlined functions' formal parameters that don't have any coverage, including those who only have `DBG_VALUE $noreg, $noreg`s associated with them, don't generate `DW_TAG_formal_parameter` DIEs, and they don't get into consideration in `llvm-dwarf --statistics` and `llvm-locstats`. This is a known bug mentioned in https://lists.llvm.org/pipermail/llvm-dev/2021-January/148139.html. For example, this is a snippet of `llvm-dwarfdump` output of `wc`, where `isword` is inlined into another function. `isword` has a formal parameter `c`, which doesn't have any coverage in this inlined area, so its `DW_TAG_formal_parameter` was not generated: ``` 0x0000018d: DW_TAG_inlined_subroutine DW_AT_abstract_origin (0x0000012c "isword") DW_AT_low_pc (0x00000166) DW_AT_high_pc (0x0000016d) DW_AT_call_file ("/usr/local/google/home/aheejin/test/dwarf-verify/wc/wc.c") DW_AT_call_line (100) DW_AT_call_column (0x0a) ``` But our dangling-register-based formal parameters currently generate `DW_TAG_formal_parameter` DIEs, even though they don't have any meaningful coverage, and this happened to generate correct statistics, because it correctly sees this `c` doesn't have any coverage in this inlined area. So our current `llvm-dwarfdump` (before this CL) is like: ``` 0x0000018d: DW_TAG_inlined_subroutine DW_AT_abstract_origin (0x0000012c "isword") DW_AT_low_pc (0x00000166) DW_AT_high_pc (0x0000016d) DW_AT_call_file ("/usr/local/google/home/aheejin/test/dwarf-verify/wc/wc.c") DW_AT_call_line (100) DW_AT_call_column (0x0a) 0x0000019a: DW_TAG_formal_parameter DW_AT_abstract_origin (0x00000134 "c") ``` Note that this `DW_TAG_formal_parameter` doesn't have any `DW_AT_location` attribute under it, meaning it doesn't have any coverage. On the other hand, if the `DW_TAG_formal_parameter` is not generated, `llvm-dwarfdump --statistics` wouldn't even know about the parameter's existence, and doesn't include it in the coverage computation, making the overall coverage (incorrectly) go up. `DBG_VALUE $noreg, $noreg` used to generate this empty `DW_TAG_formal_parameter`, but it changed for consistency in D95617. It looks this bug in `llvm-dwarf --statistics` (and `llvm-locstats`, which uses `llvm-dwarf --statistics`) has not been fixed so far. So we get the coverage that's little incorrectly higher than our actual coverage. But this bug apparently affects every target in LLVM. Reviewed By: dschuff Differential Revision: https://reviews.llvm.org/D139675
162 lines
6.5 KiB
C++
162 lines
6.5 KiB
C++
//===-- WebAssemblyDebugFixup.cpp - Debug Fixup ------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
///
|
|
/// \file
|
|
/// Several prior passes may "stackify" registers, here we ensure any references
|
|
/// in such registers in debug_value instructions become stack relative also.
|
|
/// This is done in a separate pass such that not all previous passes need to
|
|
/// track stack depth when values get stackified.
|
|
///
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "MCTargetDesc/WebAssemblyMCTargetDesc.h"
|
|
#include "Utils/WebAssemblyUtilities.h"
|
|
#include "WebAssembly.h"
|
|
#include "WebAssemblyMachineFunctionInfo.h"
|
|
#include "WebAssemblySubtarget.h"
|
|
#include "llvm/ADT/SCCIterator.h"
|
|
#include "llvm/CodeGen/MachineFrameInfo.h"
|
|
#include "llvm/CodeGen/MachineFunction.h"
|
|
#include "llvm/CodeGen/MachineInstrBuilder.h"
|
|
#include "llvm/CodeGen/MachineLoopInfo.h"
|
|
#include "llvm/CodeGen/MachineRegisterInfo.h"
|
|
#include "llvm/CodeGen/Passes.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
using namespace llvm;
|
|
|
|
#define DEBUG_TYPE "wasm-debug-fixup"
|
|
|
|
namespace {
|
|
class WebAssemblyDebugFixup final : public MachineFunctionPass {
|
|
StringRef getPassName() const override { return "WebAssembly Debug Fixup"; }
|
|
|
|
void getAnalysisUsage(AnalysisUsage &AU) const override {
|
|
AU.setPreservesCFG();
|
|
MachineFunctionPass::getAnalysisUsage(AU);
|
|
}
|
|
|
|
bool runOnMachineFunction(MachineFunction &MF) override;
|
|
|
|
public:
|
|
static char ID; // Pass identification, replacement for typeid
|
|
WebAssemblyDebugFixup() : MachineFunctionPass(ID) {}
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
char WebAssemblyDebugFixup::ID = 0;
|
|
INITIALIZE_PASS(
|
|
WebAssemblyDebugFixup, DEBUG_TYPE,
|
|
"Ensures debug_value's that have been stackified become stack relative",
|
|
false, false)
|
|
|
|
FunctionPass *llvm::createWebAssemblyDebugFixup() {
|
|
return new WebAssemblyDebugFixup();
|
|
}
|
|
|
|
// At this very end of the compilation pipeline, if any DBG_VALUEs with
|
|
// registers remain, it means they are dangling info which we failed to update
|
|
// when their corresponding def instruction was transformed/moved/splitted etc.
|
|
// Because Wasm cannot access values in LLVM virtual registers in the debugger,
|
|
// these dangling DBG_VALUEs in effect kill the effect of any previous DBG_VALUE
|
|
// associated with the variable, which will appear as "optimized out".
|
|
static void nullifyDanglingDebugValues(MachineBasicBlock &MBB,
|
|
const TargetInstrInfo *TII) {
|
|
for (auto &MI : llvm::make_early_inc_range(MBB)) {
|
|
if (MI.isDebugValue() && MI.getDebugOperand(0).isReg() &&
|
|
!MI.isUndefDebugValue()) {
|
|
LLVM_DEBUG(dbgs() << "Warning: dangling DBG_VALUE nullified: " << MI
|
|
<< "\n");
|
|
MI.getDebugOperand(0).setReg(Register());
|
|
}
|
|
}
|
|
}
|
|
|
|
bool WebAssemblyDebugFixup::runOnMachineFunction(MachineFunction &MF) {
|
|
LLVM_DEBUG(dbgs() << "********** Debug Fixup **********\n"
|
|
"********** Function: "
|
|
<< MF.getName() << '\n');
|
|
|
|
WebAssemblyFunctionInfo &MFI = *MF.getInfo<WebAssemblyFunctionInfo>();
|
|
const auto *TII = MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
|
|
|
|
struct StackElem {
|
|
unsigned Reg;
|
|
MachineInstr *DebugValue;
|
|
};
|
|
std::vector<StackElem> Stack;
|
|
for (MachineBasicBlock &MBB : MF) {
|
|
// We may insert into this list.
|
|
for (auto MII = MBB.begin(); MII != MBB.end(); ++MII) {
|
|
MachineInstr &MI = *MII;
|
|
if (MI.isDebugValue()) {
|
|
auto &MO = MI.getOperand(0);
|
|
// Also check if not a $noreg: likely a DBG_VALUE we just inserted.
|
|
if (MO.isReg() && MO.getReg().isValid() &&
|
|
MFI.isVRegStackified(MO.getReg())) {
|
|
// Found a DBG_VALUE with a stackified register we will
|
|
// change into a stack operand.
|
|
// Search for register rather than assume it is on top (which it
|
|
// typically is if it appears right after the def), since
|
|
// DBG_VALUE's may shift under some circumstances.
|
|
for (auto &Elem : reverse(Stack)) {
|
|
if (MO.getReg() == Elem.Reg) {
|
|
auto Depth = static_cast<unsigned>(&Elem - &Stack[0]);
|
|
LLVM_DEBUG(dbgs() << "Debug Value VReg " << MO.getReg()
|
|
<< " -> Stack Relative " << Depth << "\n");
|
|
MO.ChangeToTargetIndex(WebAssembly::TI_OPERAND_STACK, Depth);
|
|
// Save the DBG_VALUE instruction that defined this stackified
|
|
// variable since later we need it to construct another one on
|
|
// pop.
|
|
Elem.DebugValue = &MI;
|
|
break;
|
|
}
|
|
}
|
|
// If the Reg was not found, we have a DBG_VALUE outside of its
|
|
// def-use range, and we leave it unmodified as reg, which means
|
|
// it will be culled later.
|
|
}
|
|
} else {
|
|
// Track stack depth.
|
|
for (MachineOperand &MO : reverse(MI.explicit_uses())) {
|
|
if (MO.isReg() && MFI.isVRegStackified(MO.getReg())) {
|
|
auto Prev = Stack.back();
|
|
Stack.pop_back();
|
|
assert(Prev.Reg == MO.getReg() &&
|
|
"WebAssemblyDebugFixup: Pop: Register not matched!");
|
|
// We should not put a DBG_VALUE after a terminator; debug ranges
|
|
// are terminated at the end of a BB anyway.
|
|
if (Prev.DebugValue && !MI.isTerminator()) {
|
|
// This stackified reg is a variable that started life at
|
|
// Prev.DebugValue, so now that we're popping it we must insert
|
|
// a $noreg DBG_VALUE for the variable to end it, right after
|
|
// the current instruction.
|
|
BuildMI(*Prev.DebugValue->getParent(), std::next(MII),
|
|
Prev.DebugValue->getDebugLoc(),
|
|
TII->get(WebAssembly::DBG_VALUE), false, Register(),
|
|
Prev.DebugValue->getOperand(2).getMetadata(),
|
|
Prev.DebugValue->getOperand(3).getMetadata());
|
|
}
|
|
}
|
|
}
|
|
for (MachineOperand &MO : MI.defs()) {
|
|
if (MO.isReg() && MFI.isVRegStackified(MO.getReg())) {
|
|
Stack.push_back({MO.getReg(), nullptr});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert(Stack.empty() &&
|
|
"WebAssemblyDebugFixup: Stack not empty at end of basic block!");
|
|
|
|
nullifyDanglingDebugValues(MBB, TII);
|
|
}
|
|
|
|
return true;
|
|
}
|