Skip to content

Commit b7c14b6

Browse files
authored
[Debugify] Add 'acceptance-test' mode for the debugify report script (#147574)
For the purposes of setting up CI that makes use of debugify, this patch adds an alternative mode for the llvm-original-di-preservation.py script, which produces terminal-friendly(-ish) YAML output instead of an HTML report, and sets the return code to 1 if the input file contains errors, or 0 if the input file contains no errors or does not exist, making it simple to use it in CI. This introduces a small change in existing usage, in that the path for the HTML report file is now passed with `--report-file <path>` rather than as a positional argument; I could make the argparse logic work without this change, but I believe that is simpler to understand this way, and to my knowledge debugify isn't currently being used in automated environments where changing this might cause issues. As a small change while passing by, I also changed `-compress` to `--compress`, for consistency. As a note for reviewers, the reason that we treat a non-existent input file as a pass is that this is actually the expected state: we use clang to compile numerous files, passing a filepath for debugify errors. Any errors found by debugify will be written to this file; if none are found, the file is untouched. This is also mentioned in a code comment, but I think it useful to state upfront. Finally, the justification for adding a new mode to this script instead of adding a separate script for the separate functionality is that this script understands debugify's output, and performs some deduplication that is useful for clarifying the resulting output. Writing a new script would require duplicating logic unnecessarily, and risks the scripts falling out-of-sync if changes are made to debugify's output.
1 parent b1fca54 commit b7c14b6

File tree

4 files changed

+198
-40
lines changed

4 files changed

+198
-40
lines changed

llvm/docs/HowToUpdateDebugInfo.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ as follows:
504504

505505
.. code-block:: bash
506506
507-
$ llvm-original-di-preservation.py sample.json sample.html
507+
$ llvm-original-di-preservation.py sample.json --report-file sample.html
508508
509509
Testing of original debug info preservation can be invoked from front-end level
510510
as follows:
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
RUN: not %llvm-original-di-preservation %p/Inputs/sample.json --acceptance-test | FileCheck %s
2+
CHECK: DILocation Bugs:
3+
CHECK-NEXT: test.ll:
4+
CHECK-NEXT: no-name:
5+
CHECK-NEXT: - action: not-generate
6+
CHECK-NEXT: bb_name: no-name
7+
CHECK-NEXT: fn_name: fn
8+
CHECK-NEXT: instr: extractvalue
9+
CHECK-NEXT: - action: not-generate
10+
CHECK-NEXT: bb_name: no-name
11+
CHECK-NEXT: fn_name: fn
12+
CHECK-NEXT: instr: insertvalue
13+
CHECK-NEXT: - action: not-generate
14+
CHECK-NEXT: bb_name: no-name
15+
CHECK-NEXT: fn_name: fn1
16+
CHECK-NEXT: instr: insertvalue
17+
CHECK-NEXT: - action: not-generate
18+
CHECK-NEXT: bb_name: no-name
19+
CHECK-NEXT: fn_name: fn1
20+
CHECK-NEXT: instr: extractvalue
21+
CHECK: Errors detected for:
22+
23+
RUN: not %llvm-original-di-preservation %p/Inputs/sample.json --acceptance-test --reduce | FileCheck %s --check-prefix=COMPRESS
24+
COMPRESS: DILocation Bugs:
25+
COMPRESS-NEXT: test.ll:
26+
COMPRESS-NEXT: no-name:
27+
COMPRESS-NEXT: - action: not-generate
28+
COMPRESS-NEXT: bb_name: no-name
29+
COMPRESS-NEXT: fn_name: fn
30+
COMPRESS-NEXT: instr: extractvalue
31+
COMPRESS-NEXT: - action: not-generate
32+
COMPRESS-NEXT: bb_name: no-name
33+
COMPRESS-NEXT: fn_name: fn
34+
COMPRESS-NEXT: instr: insertvalue
35+
COMPRESS: Errors detected for:
36+
37+
RUN: not %llvm-original-di-preservation %p/Inputs/origin.json --acceptance-test --reduce | FileCheck %s --check-prefix=ORIGIN
38+
ORIGIN: DILocation Bugs:
39+
ORIGIN-NEXT: test.ll:
40+
ORIGIN-NEXT: LoopVectorizePass:
41+
ORIGIN-NEXT: - action: not-generate
42+
ORIGIN-NEXT: bb_name: no-name
43+
ORIGIN-NEXT: fn_name: fn
44+
ORIGIN-NEXT: instr: add
45+
ORIGIN-NEXT: origin: |
46+
ORIGIN-NEXT: Stack Trace 0:
47+
ORIGIN-NEXT: #0 0x00005895d035c935 llvm::DbgLocOrigin::DbgLocOrigin(bool) /tmp/llvm-project/llvm/lib/IR/DebugLoc.cpp:22:9
48+
ORIGIN-NEXT: #1 0x00005895d03af013 llvm::DILocAndCoverageTracking::DILocAndCoverageTracking() /tmp/llvm-project/llvm/include/llvm/IR/DebugLoc.h:90:11
49+
ORIGIN-NEXT: #2 0x00005895d03af013 llvm::DebugLoc::DebugLoc() /tmp/llvm-project/llvm/include/llvm/IR/DebugLoc.h:133:5
50+
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
51+
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
52+
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
53+
ORIGIN-NEXT: #6 0x00005895d06862b5 llvm::InstCombinerImpl::foldPHIArgGEPIntoPHI(llvm::PHINode&) /tmp/llvm-project/llvm/lib/Transforms/InstCombine/InstCombinePHI.cpp:617:9
54+
ORIGIN-NEXT: #7 0x00005895d0688fe0 llvm::InstCombinerImpl::visitPHINode(llvm::PHINode&) /tmp/llvm-project/llvm/lib/Transforms/InstCombine/InstCombinePHI.cpp:1456:22
55+
ORIGIN-NEXT: #8 0x00005895d05cd21f llvm::InstCombinerImpl::run() /tmp/llvm-project/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:5327:22
56+
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
57+
ORIGIN-NEXT: #10 0x00005895d05cf9a9 llvm::InstCombinePass::run(llvm::Function&, llvm::AnalysisManager&) /tmp/llvm-project/llvm/lib/Transforms/InstCombine/InstructionCombining.cpp:5706:8
58+
ORIGIN-NEXT: #11 0x00005895d107d07d llvm::detail::PassModel>::run(llvm::Function&, llvm::AnalysisManager&) /tmp/llvm-project/llvm/include/llvm/IR/PassManagerInternal.h:91:5
59+
ORIGIN-NEXT: #12 0x00005895d04204a7 llvm::PassManager>::run(llvm::Function&, llvm::AnalysisManager&) /tmp/llvm-project/llvm/include/llvm/IR/PassManagerImpl.h:85:8
60+
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
61+
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
62+
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
63+
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
64+
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
65+
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
66+
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
67+
ORIGIN: Errors detected for:
68+
69+
RUN: %llvm-original-di-preservation %p/Inputs/non-existent.json --acceptance-test | FileCheck %s --check-prefix=EMPTY
70+
EMPTY: No errors detected for:
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
RUN: %llvm-original-di-preservation %p/Inputs/sample.json %t.html | FileCheck %s
1+
RUN: %llvm-original-di-preservation %p/Inputs/sample.json --report-html-file %t.html | FileCheck %s
22
RUN: diff -w %p/Inputs/expected-sample.html %t.html
33
CHECK: The {{.+}}.html generated.
44
CHECK-NOT: Skipped lines:
55

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

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

16-
RUN: %llvm-original-di-preservation %p/Inputs/origin.json %t4.html | FileCheck %s
16+
RUN: %llvm-original-di-preservation %p/Inputs/origin.json --report-html-file %t4.html | FileCheck %s
1717
RUN: diff -w %p/Inputs/expected-origin.html %t4.html

llvm/utils/llvm-original-di-preservation.py

Lines changed: 121 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from collections import defaultdict
1212
from collections import OrderedDict
1313

14-
1514
class DILocBug:
1615
def __init__(self, origin, action, bb_name, fn_name, instr):
1716
self.origin = origin
@@ -20,28 +19,77 @@ def __init__(self, origin, action, bb_name, fn_name, instr):
2019
self.fn_name = fn_name
2120
self.instr = instr
2221

23-
def __str__(self):
22+
def key(self):
2423
return self.action + self.bb_name + self.fn_name + self.instr
2524

25+
def to_dict(self):
26+
result = {
27+
"instr": self.instr,
28+
"fn_name": self.fn_name,
29+
"bb_name": self.bb_name,
30+
"action": self.action,
31+
}
32+
if self.origin:
33+
result["origin"] = self.origin
34+
return result
35+
2636

2737
class DISPBug:
2838
def __init__(self, action, fn_name):
2939
self.action = action
3040
self.fn_name = fn_name
3141

32-
def __str__(self):
42+
def key(self):
3343
return self.action + self.fn_name
3444

45+
def to_dict(self):
46+
return {
47+
"fn_name": self.fn_name,
48+
"action": self.action,
49+
}
50+
3551

3652
class DIVarBug:
3753
def __init__(self, action, name, fn_name):
3854
self.action = action
3955
self.name = name
4056
self.fn_name = fn_name
4157

42-
def __str__(self):
58+
def key(self):
4359
return self.action + self.name + self.fn_name
4460

61+
def to_dict(self):
62+
return {
63+
"fn_name": self.fn_name,
64+
"name": self.name,
65+
"action": self.action,
66+
}
67+
68+
69+
def print_bugs_yaml(name, bugs_dict, indent=2):
70+
def get_bug_line(indent_level: int, text: str, margin_mark: bool = False):
71+
if margin_mark:
72+
return "- ".rjust(indent_level * indent) + text
73+
return " " * indent * indent_level + text
74+
75+
print(f"{name}:")
76+
for bugs_file, bugs_pass_dict in sorted(iter(bugs_dict.items())):
77+
print(get_bug_line(1, f"{bugs_file}:"))
78+
for bugs_pass, bugs_list in sorted(iter(bugs_pass_dict.items())):
79+
print(get_bug_line(2, f"{bugs_pass}:"))
80+
for bug in bugs_list:
81+
bug_dict = bug.to_dict()
82+
first_line = True
83+
# First item needs a '-' in the margin.
84+
for key, val in sorted(iter(bug_dict.items())):
85+
if "\n" in val:
86+
# Output block text for any multiline string.
87+
print(get_bug_line(3, f"{key}: |", first_line))
88+
for line in val.splitlines():
89+
print(get_bug_line(4, line))
90+
else:
91+
print(get_bug_line(3, f"{key}: {val}", first_line))
92+
first_line = False
4593

4694
# Report the bugs in form of html.
4795
def generate_html_report(
@@ -430,9 +478,16 @@ def get_json_chunk(file, start, size):
430478
# Parse the program arguments.
431479
def parse_program_args(parser):
432480
parser.add_argument("file_name", type=str, help="json file to process")
433-
parser.add_argument("html_file", type=str, help="html file to output data")
434-
parser.add_argument(
435-
"-compress", action="store_true", help="create reduced html report"
481+
parser.add_argument("--reduce", action="store_true", help="create reduced report")
482+
483+
report_type_group = parser.add_mutually_exclusive_group(required=True)
484+
report_type_group.add_argument(
485+
"--report-html-file", type=str, help="output HTML file for the generated report"
486+
)
487+
report_type_group.add_argument(
488+
"--acceptance-test",
489+
action="store_true",
490+
help="if set, produce terminal-friendly output and return 0 iff the input file is empty or does not exist",
436491
)
437492

438493
return parser.parse_args()
@@ -442,10 +497,22 @@ def Main():
442497
parser = argparse.ArgumentParser()
443498
opts = parse_program_args(parser)
444499

445-
if not opts.html_file.endswith(".html"):
500+
if opts.report_html_file is not None and not opts.report_html_file.endswith(
501+
".html"
502+
):
446503
print("error: The output file must be '.html'.")
447504
sys.exit(1)
448505

506+
if opts.acceptance_test:
507+
if os.path.isdir(opts.file_name):
508+
print(f"error: Directory passed as input file: '{opts.file_name}'")
509+
sys.exit(1)
510+
if not os.path.exists(opts.file_name):
511+
# We treat an empty input file as a success, as debugify will generate an output file iff any errors are
512+
# found, meaning we expect 0 errors to mean that the expected file does not exist.
513+
print(f"No errors detected for: {opts.file_name}")
514+
sys.exit(0)
515+
449516
# Use the defaultdict in order to make multidim dicts.
450517
di_location_bugs = defaultdict(lambda: defaultdict(list))
451518
di_subprogram_bugs = defaultdict(lambda: defaultdict(list))
@@ -489,9 +556,9 @@ def Main():
489556
skipped_lines += 1
490557
continue
491558

492-
di_loc_bugs = di_location_bugs[bugs_file][bugs_pass]
493-
di_sp_bugs = di_subprogram_bugs[bugs_file][bugs_pass]
494-
di_var_bugs = di_variable_bugs[bugs_file][bugs_pass]
559+
di_loc_bugs = di_location_bugs.get("bugs_file", {}).get("bugs_pass", [])
560+
di_sp_bugs = di_subprogram_bugs.get("bugs_file", {}).get("bugs_pass", [])
561+
di_var_bugs = di_variable_bugs.get("bugs_file", {}).get("bugs_pass", [])
495562

496563
# Omit duplicated bugs.
497564
di_loc_set = set()
@@ -515,9 +582,9 @@ def Main():
515582
skipped_bugs += 1
516583
continue
517584
di_loc_bug = DILocBug(origin, action, bb_name, fn_name, instr)
518-
if not str(di_loc_bug) in di_loc_set:
519-
di_loc_set.add(str(di_loc_bug))
520-
if opts.compress:
585+
if not di_loc_bug.key() in di_loc_set:
586+
di_loc_set.add(di_loc_bug.key())
587+
if opts.reduce:
521588
pass_instr = bugs_pass + instr
522589
if not pass_instr in di_loc_pass_instr_set:
523590
di_loc_pass_instr_set.add(pass_instr)
@@ -538,9 +605,9 @@ def Main():
538605
skipped_bugs += 1
539606
continue
540607
di_sp_bug = DISPBug(action, name)
541-
if not str(di_sp_bug) in di_sp_set:
542-
di_sp_set.add(str(di_sp_bug))
543-
if opts.compress:
608+
if not di_sp_bug.key() in di_sp_set:
609+
di_sp_set.add(di_sp_bug.key())
610+
if opts.reduce:
544611
pass_fn = bugs_pass + name
545612
if not pass_fn in di_sp_pass_fn_set:
546613
di_sp_pass_fn_set.add(pass_fn)
@@ -562,9 +629,9 @@ def Main():
562629
skipped_bugs += 1
563630
continue
564631
di_var_bug = DIVarBug(action, name, fn_name)
565-
if not str(di_var_bug) in di_var_set:
566-
di_var_set.add(str(di_var_bug))
567-
if opts.compress:
632+
if not di_var_bug.key() in di_var_set:
633+
di_var_set.add(di_var_bug.key())
634+
if opts.reduce:
568635
pass_var = bugs_pass + name
569636
if not pass_var in di_var_pass_var_set:
570637
di_var_pass_var_set.add(pass_var)
@@ -582,19 +649,40 @@ def Main():
582649
skipped_bugs += 1
583650
continue
584651

585-
di_location_bugs[bugs_file][bugs_pass] = di_loc_bugs
586-
di_subprogram_bugs[bugs_file][bugs_pass] = di_sp_bugs
587-
di_variable_bugs[bugs_file][bugs_pass] = di_var_bugs
588-
589-
generate_html_report(
590-
di_location_bugs,
591-
di_subprogram_bugs,
592-
di_variable_bugs,
593-
di_location_bugs_summary,
594-
di_sp_bugs_summary,
595-
di_var_bugs_summary,
596-
opts.html_file,
597-
)
652+
if di_loc_bugs:
653+
di_location_bugs[bugs_file][bugs_pass] = di_loc_bugs
654+
if di_sp_bugs:
655+
di_subprogram_bugs[bugs_file][bugs_pass] = di_sp_bugs
656+
if di_var_bugs:
657+
di_variable_bugs[bugs_file][bugs_pass] = di_var_bugs
658+
659+
if opts.report_html_file is not None:
660+
generate_html_report(
661+
di_location_bugs,
662+
di_subprogram_bugs,
663+
di_variable_bugs,
664+
di_location_bugs_summary,
665+
di_sp_bugs_summary,
666+
di_var_bugs_summary,
667+
opts.report_html_file,
668+
)
669+
else:
670+
# Pretty(ish) print the detected bugs, but check if any exist first so that we don't print an empty dict.
671+
if di_location_bugs:
672+
print_bugs_yaml("DILocation Bugs", di_location_bugs)
673+
if di_subprogram_bugs:
674+
print_bugs_yaml("DISubprogram Bugs", di_subprogram_bugs)
675+
if di_variable_bugs:
676+
print_bugs_yaml("DIVariable Bugs", di_variable_bugs)
677+
678+
if opts.acceptance_test:
679+
if any((di_location_bugs, di_subprogram_bugs, di_variable_bugs)):
680+
# Add a newline gap after printing at least one error.
681+
print()
682+
print(f"Errors detected for: {opts.file_name}")
683+
sys.exit(1)
684+
else:
685+
print(f"No errors detected for: {opts.file_name}")
598686

599687
if skipped_lines > 0:
600688
print("Skipped lines: " + str(skipped_lines))

0 commit comments

Comments
 (0)