Skip to content

Commit c782ed3

Browse files
authored
[utils][UpdateLLCTestChecks] Add MIR support to update_llc_test_checks.py. (#164965)
This change enables update_llc_test_checks.py to automatically generate MIR checks for RUN lines that use `-stop-before` or `-stop-after` flags allowing tests to verify intermediate compilation stages (e.g., after instruction selection but before peephole optimizations) alongside the final assembly output. If `-debug-only` flag is present in the run line it's considered as the main point of interest for testing and stop flags above are ignored (that is no MIR checks are generated). This resulted from the scenario, when I needed to test two instruction matching patterns where the later pattern in the peepholer reverts the earlier pattern in the instruction selector and distinguish it from the case when the earlier pattern didn't worked at all. Initially created by Claude Sonnet 4.5 it was improved later to handle conflicts in MIR <-> ASM prefixes and formatting.
1 parent 6c640b8 commit c782ed3

File tree

9 files changed

+194
-19
lines changed

9 files changed

+194
-19
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
; RUN: llc -mtriple=x86_64 < %s | FileCheck %s --check-prefix=ASM
2+
; RUN: llc -mtriple=x86_64 -stop-after=finalize-isel < %s | FileCheck %s --check-prefix=MIR
3+
4+
define i64 @test1(i64 %i) nounwind readnone {
5+
%loc = alloca i64
6+
%j = load i64, ptr %loc
7+
%r = add i64 %i, %j
8+
ret i64 %r
9+
}
10+
11+
define i64 @test2(i32 %i) nounwind readnone {
12+
%loc = alloca i32
13+
%j = load i32, ptr %loc
14+
%r = add i32 %i, %j
15+
%ext = zext i32 %r to i64
16+
ret i64 %ext
17+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
2+
; RUN: llc -mtriple=x86_64 < %s | FileCheck %s --check-prefix=ASM
3+
; RUN: llc -mtriple=x86_64 -stop-after=finalize-isel < %s | FileCheck %s --check-prefix=MIR
4+
5+
define i64 @test1(i64 %i) nounwind readnone {
6+
; ASM-LABEL: test1:
7+
; ASM: # %bb.0:
8+
; ASM-NEXT: movq %rdi, %rax
9+
; ASM-NEXT: addq -{{[0-9]+}}(%rsp), %rax
10+
; ASM-NEXT: retq
11+
; MIR-LABEL: name: test1
12+
; MIR: bb.0 (%ir-block.0):
13+
; MIR-NEXT: liveins: $rdi
14+
; MIR-NEXT: {{ $}}
15+
; MIR-NEXT: [[COPY:%[0-9]+]]:gr64 = COPY $rdi
16+
; MIR-NEXT: [[ADD64rm:%[0-9]+]]:gr64 = ADD64rm [[COPY]], %stack.0.loc, 1, $noreg, 0, $noreg, implicit-def dead $eflags :: (dereferenceable load (s64) from %ir.loc)
17+
; MIR-NEXT: $rax = COPY [[ADD64rm]]
18+
; MIR-NEXT: RET 0, $rax
19+
%loc = alloca i64
20+
%j = load i64, ptr %loc
21+
%r = add i64 %i, %j
22+
ret i64 %r
23+
}
24+
25+
define i64 @test2(i32 %i) nounwind readnone {
26+
; ASM-LABEL: test2:
27+
; ASM: # %bb.0:
28+
; ASM-NEXT: movl %edi, %eax
29+
; ASM-NEXT: addl -{{[0-9]+}}(%rsp), %eax
30+
; ASM-NEXT: retq
31+
; MIR-LABEL: name: test2
32+
; MIR: bb.0 (%ir-block.0):
33+
; MIR-NEXT: liveins: $edi
34+
; MIR-NEXT: {{ $}}
35+
; MIR-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY $edi
36+
; MIR-NEXT: [[ADD32rm:%[0-9]+]]:gr32 = ADD32rm [[COPY]], %stack.0.loc, 1, $noreg, 0, $noreg, implicit-def dead $eflags :: (dereferenceable load (s32) from %ir.loc)
37+
; MIR-NEXT: [[SUBREG_TO_REG:%[0-9]+]]:gr64 = SUBREG_TO_REG 0, killed [[ADD32rm]], %subreg.sub_32bit
38+
; MIR-NEXT: $rax = COPY [[SUBREG_TO_REG]]
39+
; MIR-NEXT: RET 0, $rax
40+
%loc = alloca i32
41+
%j = load i32, ptr %loc
42+
%r = add i32 %i, %j
43+
%ext = zext i32 %r to i64
44+
ret i64 %ext
45+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
; RUN: llc -mtriple=x86_64 < %s | FileCheck %s --check-prefix=CHECK
2+
; RUN: llc -mtriple=x86_64 -stop-after=finalize-isel < %s | FileCheck %s --check-prefix=CHECK
3+
4+
define i32 @add(i32 %a, i32 %b) {
5+
%sum = add i32 %a, %b
6+
ret i32 %sum
7+
}
8+
9+
define i32 @sub(i32 %a, i32 %b) {
10+
%diff = sub i32 %a, %b
11+
ret i32 %diff
12+
}
13+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
2+
; RUN: llc -mtriple=x86_64 < %s | FileCheck %s --check-prefix=CHECK
3+
; RUN: llc -mtriple=x86_64 -stop-after=finalize-isel < %s | FileCheck %s --check-prefix=CHECK
4+
5+
define i32 @add(i32 %a, i32 %b) {
6+
%sum = add i32 %a, %b
7+
ret i32 %sum
8+
}
9+
10+
define i32 @sub(i32 %a, i32 %b) {
11+
%diff = sub i32 %a, %b
12+
ret i32 %diff
13+
}
14+
15+
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
16+
; CHECK: {{.*}}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# REQUIRES: x86-registered-target
2+
## Test checking that update_llc_test_checks.py can generate both ASM and MIR checks in the same file
3+
4+
# RUN: cp -f %S/Inputs/x86_asm_mir_mixed.ll %t.ll && %update_llc_test_checks %t.ll
5+
# RUN: diff -u %S/Inputs/x86_asm_mir_mixed.ll.expected %t.ll
6+
7+
## Verify that running the script again on an already updated file doesn't add duplicate checks
8+
# RUN: %update_llc_test_checks %t.ll
9+
# RUN: diff -u %S/Inputs/x86_asm_mir_mixed.ll.expected %t.ll
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
## Test that using the same prefix for both ASM and MIR outputs generates a warning
2+
## and doesn't produce any checks.
3+
4+
# RUN: cp -f %S/Inputs/x86_asm_mir_same_prefix.ll %t.ll && %update_llc_test_checks %t.ll 2>&1 | FileCheck %s --check-prefix=WARNING
5+
# RUN: diff -u %S/Inputs/x86_asm_mir_same_prefix.ll.expected %t.ll
6+
7+
# WARNING: WARNING: The following prefixes are used for both ASM and MIR output, which will cause FileCheck failures: CHECK

llvm/utils/UpdateTestChecks/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,7 @@ def invoke_tool(exe, cmd_args, ir, preprocess_cmd=None, verbose=False):
605605
TRIPLE_ARG_RE = re.compile(r"-m?triple[= ]([^ ]+)")
606606
MARCH_ARG_RE = re.compile(r"-march[= ]([^ ]+)")
607607
DEBUG_ONLY_ARG_RE = re.compile(r"-debug-only[= ]([^ ]+)")
608+
STOP_PASS_RE = re.compile(r"-stop-(before|after)=(\w+)")
608609

609610
IS_DEBUG_RECORD_RE = re.compile(r"^(\s+)#dbg_")
610611
IS_SWITCH_CASE_RE = re.compile(r"^\s+i\d+ \d+, label %\S+")

llvm/utils/UpdateTestChecks/mir.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,15 @@ def add_mir_checks_for_function(
163163
print_fixed_stack,
164164
first_check_is_next,
165165
at_the_function_name,
166+
check_indent=None,
166167
):
167168
printed_prefixes = set()
168169
for run in run_list:
169170
for prefix in run[0]:
170171
if prefix in printed_prefixes:
171172
break
172-
if not func_dict[prefix][func_name]:
173+
# func_info can be empty if there was a prefix conflict.
174+
if not func_dict[prefix].get(func_name):
173175
continue
174176
if printed_prefixes:
175177
# Add some space between different check prefixes.
@@ -185,6 +187,7 @@ def add_mir_checks_for_function(
185187
func_dict[prefix][func_name],
186188
print_fixed_stack,
187189
first_check_is_next,
190+
check_indent,
188191
)
189192
break
190193
else:
@@ -204,6 +207,7 @@ def add_mir_check_lines(
204207
func_info,
205208
print_fixed_stack,
206209
first_check_is_next,
210+
check_indent=None,
207211
):
208212
func_body = str(func_info).splitlines()
209213
if single_bb:
@@ -220,7 +224,10 @@ def add_mir_check_lines(
220224
first_line = func_body[0]
221225
indent = len(first_line) - len(first_line.lstrip(" "))
222226
# A check comment, indented the appropriate amount
223-
check = "{:>{}}; {}".format("", indent, prefix)
227+
if check_indent is not None:
228+
check = "{}; {}".format(check_indent, prefix)
229+
else:
230+
check = "{:>{}}; {}".format("", indent, prefix)
224231

225232
output_lines.append("{}-LABEL: name: {}".format(check, func_name))
226233

llvm/utils/update_llc_test_checks.py

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import os # Used to advertise this file's name ("autogenerated_note").
1616
import sys
1717

18-
from UpdateTestChecks import common
18+
from UpdateTestChecks import common, mir
1919

2020
# llc is the only llc-like in the LLVM tree but downstream forks can add
2121
# additional ones here if they have them.
@@ -33,6 +33,7 @@ def update_test(ti: common.TestInfo):
3333
break
3434

3535
run_list = []
36+
mir_run_list = []
3637
for l in ti.run_lines:
3738
if "|" not in l:
3839
common.warn("Skipping unparsable RUN line: " + l)
@@ -57,9 +58,14 @@ def update_test(ti: common.TestInfo):
5758
if m:
5859
march_in_cmd = m.groups()[0]
5960

61+
target_list = run_list
6062
m = common.DEBUG_ONLY_ARG_RE.search(llc_cmd)
6163
if m and m.groups()[0] == "isel":
6264
from UpdateTestChecks import isel as output_type
65+
elif not m and common.STOP_PASS_RE.search(llc_cmd):
66+
# MIR output mode. If -debug-only is present assume
67+
# the debug output is the main point of interest.
68+
target_list = mir_run_list
6369
else:
6470
from UpdateTestChecks import asm as output_type
6571

@@ -84,7 +90,7 @@ def update_test(ti: common.TestInfo):
8490

8591
# FIXME: We should use multiple check prefixes to common check lines. For
8692
# now, we just ignore all but the last.
87-
run_list.append(
93+
target_list.append(
8894
(
8995
check_prefixes,
9096
llc_tool,
@@ -119,14 +125,20 @@ def update_test(ti: common.TestInfo):
119125
ginfo=ginfo,
120126
)
121127

122-
for (
123-
prefixes,
124-
llc_tool,
125-
llc_args,
126-
preprocess_cmd,
127-
triple_in_cmd,
128-
march_in_cmd,
129-
) in run_list:
128+
# Dictionary to store MIR function bodies separately
129+
mir_func_dict = {}
130+
for run_tuple, is_mir in [(run, False) for run in run_list] + [
131+
(run, True) for run in mir_run_list
132+
]:
133+
(
134+
prefixes,
135+
llc_tool,
136+
llc_args,
137+
preprocess_cmd,
138+
triple_in_cmd,
139+
march_in_cmd,
140+
) = run_tuple
141+
130142
common.debug("Extracted LLC cmd:", llc_tool, llc_args)
131143
common.debug("Extracted FileCheck prefixes:", str(prefixes))
132144

@@ -141,22 +153,54 @@ def update_test(ti: common.TestInfo):
141153
if not triple:
142154
triple = common.get_triple_from_march(march_in_cmd)
143155

144-
scrubber, function_re = output_type.get_run_handler(triple)
145-
if 0 == builder.process_run_line(
146-
function_re, scrubber, raw_tool_output, prefixes
147-
):
148-
common.warn(
149-
"Couldn't match any function. Possibly the wrong target triple has been provided"
156+
if is_mir:
157+
# MIR output mode
158+
common.debug("Detected MIR output mode for prefixes:", str(prefixes))
159+
for prefix in prefixes:
160+
if prefix not in mir_func_dict:
161+
mir_func_dict[prefix] = {}
162+
163+
mir.build_function_info_dictionary(
164+
ti.path,
165+
raw_tool_output,
166+
triple,
167+
prefixes,
168+
mir_func_dict,
169+
ti.args.verbose,
150170
)
151-
builder.processed_prefixes(prefixes)
171+
else:
172+
# ASM output mode
173+
scrubber, function_re = output_type.get_run_handler(triple)
174+
if 0 == builder.process_run_line(
175+
function_re, scrubber, raw_tool_output, prefixes
176+
):
177+
common.warn(
178+
"Couldn't match any function. Possibly the wrong target triple has been provided"
179+
)
180+
builder.processed_prefixes(prefixes)
152181

153182
func_dict = builder.finish_and_get_func_dict()
183+
184+
# Check for conflicts: same prefix used for both ASM and MIR
185+
conflicting_prefixes = set(func_dict.keys()) & set(mir_func_dict.keys())
186+
if conflicting_prefixes:
187+
common.warn(
188+
"The following prefixes are used for both ASM and MIR output, which will cause FileCheck failures: {}".format(
189+
", ".join(sorted(conflicting_prefixes))
190+
),
191+
test_file=ti.path,
192+
)
193+
for prefix in conflicting_prefixes:
194+
mir_func_dict[prefix] = {}
195+
func_dict[prefix] = {}
196+
154197
global_vars_seen_dict = {}
155198

156199
is_in_function = False
157200
is_in_function_start = False
158201
func_name = None
159202
prefix_set = set([prefix for p in run_list for prefix in p[0]])
203+
prefix_set.update([prefix for p in mir_run_list for prefix in p[0]])
160204
common.debug("Rewriting FileCheck prefixes:", str(prefix_set))
161205
output_lines = []
162206

@@ -221,6 +265,22 @@ def update_test(ti: common.TestInfo):
221265
is_filtered=builder.is_filtered(),
222266
)
223267
)
268+
269+
# Also add MIR checks if we have them for this function
270+
if mir_run_list and func_name:
271+
mir.add_mir_checks_for_function(
272+
ti.path,
273+
output_lines,
274+
mir_run_list,
275+
mir_func_dict,
276+
func_name,
277+
single_bb=False, # Don't skip basic block labels.
278+
print_fixed_stack=False, # Don't print fixed stack (ASM tests don't need it).
279+
first_check_is_next=False, # First check is LABEL, not NEXT.
280+
at_the_function_name=False, # Use "name:" not "@name".
281+
check_indent="", # No indentation for IR files (not MIR files).
282+
)
283+
224284
is_in_function_start = False
225285

226286
if is_in_function:

0 commit comments

Comments
 (0)