Skip to content
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
2 changes: 1 addition & 1 deletion llvm/docs/HowToUpdateDebugInfo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ as follows:

.. code-block:: bash

$ llvm-original-di-preservation.py sample.json sample.html
$ llvm-original-di-preservation.py sample.json --report-file sample.html

Testing of original debug info preservation can be invoked from front-end level
as follows:
Expand Down
70 changes: 70 additions & 0 deletions llvm/test/tools/llvm-original-di-preservation/acceptance-test.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
RUN: not %llvm-original-di-preservation %p/Inputs/sample.json --acceptance-test | FileCheck %s
CHECK: DILocation Bugs:
CHECK-NEXT: test.ll:
CHECK-NEXT: no-name:
CHECK-NEXT: - action: not-generate
CHECK-NEXT: bb_name: no-name
CHECK-NEXT: fn_name: fn
CHECK-NEXT: instr: extractvalue
CHECK-NEXT: - action: not-generate
CHECK-NEXT: bb_name: no-name
CHECK-NEXT: fn_name: fn
CHECK-NEXT: instr: insertvalue
CHECK-NEXT: - action: not-generate
CHECK-NEXT: bb_name: no-name
CHECK-NEXT: fn_name: fn1
CHECK-NEXT: instr: insertvalue
CHECK-NEXT: - action: not-generate
CHECK-NEXT: bb_name: no-name
CHECK-NEXT: fn_name: fn1
CHECK-NEXT: instr: extractvalue
CHECK: Errors detected for:

RUN: not %llvm-original-di-preservation %p/Inputs/sample.json --acceptance-test --reduce | FileCheck %s --check-prefix=COMPRESS
COMPRESS: DILocation Bugs:
COMPRESS-NEXT: test.ll:
COMPRESS-NEXT: no-name:
COMPRESS-NEXT: - action: not-generate
COMPRESS-NEXT: bb_name: no-name
COMPRESS-NEXT: fn_name: fn
COMPRESS-NEXT: instr: extractvalue
COMPRESS-NEXT: - action: not-generate
COMPRESS-NEXT: bb_name: no-name
COMPRESS-NEXT: fn_name: fn
COMPRESS-NEXT: instr: insertvalue
COMPRESS: Errors detected for:

RUN: not %llvm-original-di-preservation %p/Inputs/origin.json --acceptance-test --reduce | FileCheck %s --check-prefix=ORIGIN
ORIGIN: DILocation Bugs:
ORIGIN-NEXT: test.ll:
ORIGIN-NEXT: LoopVectorizePass:
ORIGIN-NEXT: - action: not-generate
ORIGIN-NEXT: bb_name: no-name
ORIGIN-NEXT: fn_name: fn
ORIGIN-NEXT: instr: add
ORIGIN-NEXT: origin: |
ORIGIN-NEXT: Stack Trace 0:
ORIGIN-NEXT: #0 0x00005895d035c935 llvm::DbgLocOrigin::DbgLocOrigin(bool) /tmp/llvm-project/llvm/lib/IR/DebugLoc.cpp:22:9
ORIGIN-NEXT: #1 0x00005895d03af013 llvm::DILocAndCoverageTracking::DILocAndCoverageTracking() /tmp/llvm-project/llvm/include/llvm/IR/DebugLoc.h:90:11
ORIGIN-NEXT: #2 0x00005895d03af013 llvm::DebugLoc::DebugLoc() /tmp/llvm-project/llvm/include/llvm/IR/DebugLoc.h:133:5
ORIGIN-NEXT: #3 0x00005895d03af013 llvm::Instruction::Instruction(llvm::Type*, unsigned int, llvm::User::AllocInfo, llvm::InsertPosition) /tmp/llvm-project/llvm/lib/IR/Instruction.cpp:37:14
ORIGIN-NEXT: #4 0x00005895d06862b5 llvm::PHINode::PHINode(llvm::Type*, unsigned int, llvm::Twine const&, llvm::InsertPosition) /tmp/llvm-project/llvm/include/llvm/IR/Instructions.h:0:9
ORIGIN-NEXT: #5 0x00005895d06862b5 llvm::PHINode::Create(llvm::Type*, unsigned int, llvm::Twine const&, llvm::InsertPosition) /tmp/llvm-project/llvm/include/llvm/IR/Instructions.h:2651:9
ORIGIN-NEXT: #6 0x00005895d06862b5 llvm::InstCombinerImpl::foldPHIArgGEPIntoPHI(llvm::PHINode&) /tmp/llvm-project/llvm/lib/Transforms/InstCombine/InstCombinePHI.cpp:617:9
ORIGIN-NEXT: #7 0x00005895d0688fe0 llvm::InstCombinerImpl::visitPHINode(llvm::PHINode&) /tmp/llvm-project/llvm/lib/Transforms/InstCombine/InstCombinePHI.cpp:1456:22
ORIGIN-NEXT: #8 0x00005895d05cd21f llvm::InstCombinerImpl::run() /tmp/llvm-project/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:5327:22
ORIGIN-NEXT: #9 0x00005895d05d067e combineInstructionsOverFunction(llvm::Function&, llvm::InstructionWorklist&, llvm::AAResults*, llvm::AssumptionCache&, llvm::TargetLibraryInfo&, llvm::TargetTransformInfo&, llvm::DominatorTree&, llvm::OptimizationRemarkEmitter&, llvm::BlockFrequencyInfo*, llvm::BranchProbabilityInfo*, llvm::ProfileSummaryInfo*, llvm::InstCombineOptions const&) /tmp/llvm-project/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:5643:31
ORIGIN-NEXT: #10 0x00005895d05cf9a9 llvm::InstCombinePass::run(llvm::Function&, llvm::AnalysisManager&) /tmp/llvm-project/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:5706:8
ORIGIN-NEXT: #11 0x00005895d107d07d llvm::detail::PassModel>::run(llvm::Function&, llvm::AnalysisManager&) /tmp/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:5
ORIGIN-NEXT: #12 0x00005895d04204a7 llvm::PassManager>::run(llvm::Function&, llvm::AnalysisManager&) /tmp/llvm-project/llvm/include/llvm/IR/PassManagerImpl.h:85:8
ORIGIN-NEXT: #13 0x00005895ce4cb09d llvm::detail::PassModel>, llvm::AnalysisManager>::run(llvm::Function&, llvm::AnalysisManager&) /tmp/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:5
ORIGIN-NEXT: #14 0x00005895cfae2865 llvm::CGSCCToFunctionPassAdaptor::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /tmp/llvm-project/llvm/lib/Analysis/CGSCCPassManager.cpp:0:38
ORIGIN-NEXT: #15 0x00005895ce4cad5d llvm::detail::PassModel, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&>::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /tmp/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:5
ORIGIN-NEXT: #16 0x00005895cfade813 llvm::PassManager, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&>::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /tmp/llvm-project/llvm/lib/Analysis/CGSCCPassManager.cpp:93:12
ORIGIN-NEXT: #17 0x00005895d1e3968d llvm::detail::PassModel, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&>, llvm::AnalysisManager, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&>::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /tmp/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:5
ORIGIN-NEXT: #18 0x00005895cfae1224 llvm::DevirtSCCRepeatedPass::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /tmp/llvm-project/llvm/lib/Analysis/CGSCCPassManager.cpp:0:38
ORIGIN-NEXT: #19 0x00005895d1e5067d llvm::detail::PassModel, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&>::run(llvm::LazyCallGraph::SCC&, llvm::AnalysisManager&, llvm::LazyCallGraph&, llvm::CGSCCUpdateResult&) /tmp/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:5
ORIGIN: Errors detected for:

RUN: %llvm-original-di-preservation %p/Inputs/non-existent.json --acceptance-test | FileCheck %s --check-prefix=EMPTY
EMPTY: No errors detected for:
12 changes: 6 additions & 6 deletions llvm/test/tools/llvm-original-di-preservation/basic.test
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
RUN: %llvm-original-di-preservation %p/Inputs/sample.json %t.html | FileCheck %s
RUN: %llvm-original-di-preservation %p/Inputs/sample.json --report-html-file %t.html | FileCheck %s
RUN: diff -w %p/Inputs/expected-sample.html %t.html
CHECK: The {{.+}}.html generated.
CHECK-NOT: Skipped lines:

RUN: %llvm-original-di-preservation %p/Inputs/corrupted.json %t2.html | FileCheck %s -check-prefix=CORRUPTED
RUN: %llvm-original-di-preservation %p/Inputs/corrupted.json --report-html-file %t2.html | FileCheck %s -check-prefix=CORRUPTED
RUN: diff -w %p/Inputs/expected-skipped.html %t2.html
CORRUPTED: Skipped lines: 3
CORRUPTED: Skipped bugs: 1

RUN: %llvm-original-di-preservation -compress %p/Inputs/sample.json %t3.html | FileCheck %s -check-prefix=COMPRESSED
RUN: %llvm-original-di-preservation --reduce %p/Inputs/sample.json --report-html-file %t3.html | FileCheck %s -check-prefix=REDUCE
RUN: diff -w %p/Inputs/expected-compressed.html %t3.html
COMPRESSED: The {{.+}}.html generated.
COMPRESSED-NOT: Skipped lines:
REDUCE: The {{.+}}.html generated.
REDUCE-NOT: Skipped lines:

RUN: %llvm-original-di-preservation %p/Inputs/origin.json %t4.html | FileCheck %s
RUN: %llvm-original-di-preservation %p/Inputs/origin.json --report-html-file %t4.html | FileCheck %s
RUN: diff -w %p/Inputs/expected-origin.html %t4.html
154 changes: 121 additions & 33 deletions llvm/utils/llvm-original-di-preservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from collections import defaultdict
from collections import OrderedDict


class DILocBug:
def __init__(self, origin, action, bb_name, fn_name, instr):
self.origin = origin
Expand All @@ -20,28 +19,77 @@ def __init__(self, origin, action, bb_name, fn_name, instr):
self.fn_name = fn_name
self.instr = instr

def __str__(self):
def key(self):
return self.action + self.bb_name + self.fn_name + self.instr

def to_dict(self):
result = {
"instr": self.instr,
"fn_name": self.fn_name,
"bb_name": self.bb_name,
"action": self.action,
}
if self.origin:
result["origin"] = self.origin
return result
Comment on lines +32 to +34
Copy link
Member

Choose a reason for hiding this comment

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

No test coverage for this property ("origin")?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added in latest commit.



class DISPBug:
def __init__(self, action, fn_name):
self.action = action
self.fn_name = fn_name

def __str__(self):
def key(self):
return self.action + self.fn_name

def to_dict(self):
return {
"fn_name": self.fn_name,
"action": self.action,
}


class DIVarBug:
def __init__(self, action, name, fn_name):
self.action = action
self.name = name
self.fn_name = fn_name

def __str__(self):
def key(self):
return self.action + self.name + self.fn_name

def to_dict(self):
return {
"fn_name": self.fn_name,
"name": self.name,
"action": self.action,
}


Copy link
Member

Choose a reason for hiding this comment

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

If this becomes load-bearing, at some point we're going to have to switch over to using the "proper" yaml dumping and pretty-printing facilities. Does it make sense to do that now instead? (I don't think we need to make an all-singing-all-dancing implementation for all scripts, we can just focus on their narrow purpose).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I originally started using "proper" YAML, but this does require an extra package. Currently this (and most other utility scripts) can be run with just python, with no install commands necessary, and I consider preserving this to be a benefit worth some cost. My expectation is, this script has very straightforward and limited functionality, so there isn't too much to gain from using a proper YAML package - only very basic printing logic is required, and the YAML dumping segment is purely for user benefit (it has no expectation of being programmatically parsed).

def print_bugs_yaml(name, bugs_dict, indent=2):
def get_bug_line(indent_level: int, text: str, margin_mark: bool = False):
if margin_mark:
return "- ".rjust(indent_level * indent) + text
return " " * indent * indent_level + text

print(f"{name}:")
for bugs_file, bugs_pass_dict in sorted(iter(bugs_dict.items())):
print(get_bug_line(1, f"{bugs_file}:"))
for bugs_pass, bugs_list in sorted(iter(bugs_pass_dict.items())):
print(get_bug_line(2, f"{bugs_pass}:"))
for bug in bugs_list:
bug_dict = bug.to_dict()
first_line = True
# First item needs a '-' in the margin.
for key, val in sorted(iter(bug_dict.items())):
if "\n" in val:
# Output block text for any multiline string.
print(get_bug_line(3, f"{key}: |", first_line))
for line in val.splitlines():
print(get_bug_line(4, line))
else:
print(get_bug_line(3, f"{key}: {val}", first_line))
first_line = False

# Report the bugs in form of html.
def generate_html_report(
Expand Down Expand Up @@ -430,9 +478,16 @@ def get_json_chunk(file, start, size):
# Parse the program arguments.
def parse_program_args(parser):
parser.add_argument("file_name", type=str, help="json file to process")
parser.add_argument("html_file", type=str, help="html file to output data")
parser.add_argument(
"-compress", action="store_true", help="create reduced html report"
parser.add_argument("--reduce", action="store_true", help="create reduced report")

report_type_group = parser.add_mutually_exclusive_group(required=True)
report_type_group.add_argument(
"--report-html-file", type=str, help="output HTML file for the generated report"
)
report_type_group.add_argument(
"--acceptance-test",
action="store_true",
help="if set, produce terminal-friendly output and return 0 iff the input file is empty or does not exist",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Okay, it does make sense.

)

return parser.parse_args()
Expand All @@ -442,10 +497,22 @@ def Main():
parser = argparse.ArgumentParser()
opts = parse_program_args(parser)

if not opts.html_file.endswith(".html"):
if opts.report_html_file is not None and not opts.report_html_file.endswith(
".html"
):
print("error: The output file must be '.html'.")
sys.exit(1)

if opts.acceptance_test:
if os.path.isdir(opts.file_name):
print(f"error: Directory passed as input file: '{opts.file_name}'")
sys.exit(1)
if not os.path.exists(opts.file_name):
# We treat an empty input file as a success, as debugify will generate an output file iff any errors are
# found, meaning we expect 0 errors to mean that the expected file does not exist.
print(f"No errors detected for: {opts.file_name}")
sys.exit(0)

# Use the defaultdict in order to make multidim dicts.
di_location_bugs = defaultdict(lambda: defaultdict(list))
di_subprogram_bugs = defaultdict(lambda: defaultdict(list))
Expand Down Expand Up @@ -489,9 +556,9 @@ def Main():
skipped_lines += 1
continue

di_loc_bugs = di_location_bugs[bugs_file][bugs_pass]
di_sp_bugs = di_subprogram_bugs[bugs_file][bugs_pass]
di_var_bugs = di_variable_bugs[bugs_file][bugs_pass]
di_loc_bugs = di_location_bugs.get("bugs_file", {}).get("bugs_pass", [])
di_sp_bugs = di_subprogram_bugs.get("bugs_file", {}).get("bugs_pass", [])
di_var_bugs = di_variable_bugs.get("bugs_file", {}).get("bugs_pass", [])

# Omit duplicated bugs.
di_loc_set = set()
Expand All @@ -515,9 +582,9 @@ def Main():
skipped_bugs += 1
continue
di_loc_bug = DILocBug(origin, action, bb_name, fn_name, instr)
if not str(di_loc_bug) in di_loc_set:
di_loc_set.add(str(di_loc_bug))
if opts.compress:
if not di_loc_bug.key() in di_loc_set:
di_loc_set.add(di_loc_bug.key())
if opts.reduce:
pass_instr = bugs_pass + instr
if not pass_instr in di_loc_pass_instr_set:
di_loc_pass_instr_set.add(pass_instr)
Expand All @@ -538,9 +605,9 @@ def Main():
skipped_bugs += 1
continue
di_sp_bug = DISPBug(action, name)
if not str(di_sp_bug) in di_sp_set:
di_sp_set.add(str(di_sp_bug))
if opts.compress:
if not di_sp_bug.key() in di_sp_set:
di_sp_set.add(di_sp_bug.key())
if opts.reduce:
pass_fn = bugs_pass + name
if not pass_fn in di_sp_pass_fn_set:
di_sp_pass_fn_set.add(pass_fn)
Expand All @@ -562,9 +629,9 @@ def Main():
skipped_bugs += 1
continue
di_var_bug = DIVarBug(action, name, fn_name)
if not str(di_var_bug) in di_var_set:
di_var_set.add(str(di_var_bug))
if opts.compress:
if not di_var_bug.key() in di_var_set:
di_var_set.add(di_var_bug.key())
if opts.reduce:
pass_var = bugs_pass + name
if not pass_var in di_var_pass_var_set:
di_var_pass_var_set.add(pass_var)
Expand All @@ -582,19 +649,40 @@ def Main():
skipped_bugs += 1
continue

di_location_bugs[bugs_file][bugs_pass] = di_loc_bugs
di_subprogram_bugs[bugs_file][bugs_pass] = di_sp_bugs
di_variable_bugs[bugs_file][bugs_pass] = di_var_bugs

generate_html_report(
di_location_bugs,
di_subprogram_bugs,
di_variable_bugs,
di_location_bugs_summary,
di_sp_bugs_summary,
di_var_bugs_summary,
opts.html_file,
)
if di_loc_bugs:
di_location_bugs[bugs_file][bugs_pass] = di_loc_bugs
if di_sp_bugs:
di_subprogram_bugs[bugs_file][bugs_pass] = di_sp_bugs
if di_var_bugs:
di_variable_bugs[bugs_file][bugs_pass] = di_var_bugs

if opts.report_html_file is not None:
generate_html_report(
di_location_bugs,
di_subprogram_bugs,
di_variable_bugs,
di_location_bugs_summary,
di_sp_bugs_summary,
di_var_bugs_summary,
opts.report_html_file,
)
else:
# Pretty(ish) print the detected bugs, but check if any exist first so that we don't print an empty dict.
if di_location_bugs:
print_bugs_yaml("DILocation Bugs", di_location_bugs)
if di_subprogram_bugs:
print_bugs_yaml("DISubprogram Bugs", di_subprogram_bugs)
if di_variable_bugs:
print_bugs_yaml("DIVariable Bugs", di_variable_bugs)

if opts.acceptance_test:
if any((di_location_bugs, di_subprogram_bugs, di_variable_bugs)):
# Add a newline gap after printing at least one error.
print()
print(f"Errors detected for: {opts.file_name}")
sys.exit(1)
else:
print(f"No errors detected for: {opts.file_name}")

if skipped_lines > 0:
print("Skipped lines: " + str(skipped_lines))
Expand Down