Reapply "[utils][UpdateLLCTestChecks] Add MIR support to update_llc_test_checks.py." (#164965) (#166575)
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.
This commit is contained in:
parent
3ea1ffde02
commit
59f6f33bc3
@ -0,0 +1,17 @@
|
||||
; RUN: llc -mtriple=x86_64 < %s | FileCheck %s --check-prefix=ASM
|
||||
; RUN: llc -mtriple=x86_64 -stop-after=finalize-isel < %s | FileCheck %s --check-prefix=MIR
|
||||
|
||||
define i64 @test1(i64 %i) nounwind readnone {
|
||||
%loc = alloca i64
|
||||
%j = load i64, ptr %loc
|
||||
%r = add i64 %i, %j
|
||||
ret i64 %r
|
||||
}
|
||||
|
||||
define i64 @test2(i32 %i) nounwind readnone {
|
||||
%loc = alloca i32
|
||||
%j = load i32, ptr %loc
|
||||
%r = add i32 %i, %j
|
||||
%ext = zext i32 %r to i64
|
||||
ret i64 %ext
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
|
||||
; RUN: llc -mtriple=x86_64 < %s | FileCheck %s --check-prefix=ASM
|
||||
; RUN: llc -mtriple=x86_64 -stop-after=finalize-isel < %s | FileCheck %s --check-prefix=MIR
|
||||
|
||||
define i64 @test1(i64 %i) nounwind readnone {
|
||||
; ASM-LABEL: test1:
|
||||
; ASM: # %bb.0:
|
||||
; ASM-NEXT: movq %rdi, %rax
|
||||
; ASM-NEXT: addq -{{[0-9]+}}(%rsp), %rax
|
||||
; ASM-NEXT: retq
|
||||
; MIR-LABEL: name: test1
|
||||
; MIR: bb.0 (%ir-block.0):
|
||||
; MIR-NEXT: liveins: $rdi
|
||||
; MIR-NEXT: {{ $}}
|
||||
; MIR-NEXT: [[COPY:%[0-9]+]]:gr64 = COPY $rdi
|
||||
; 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)
|
||||
; MIR-NEXT: $rax = COPY [[ADD64rm]]
|
||||
; MIR-NEXT: RET 0, $rax
|
||||
%loc = alloca i64
|
||||
%j = load i64, ptr %loc
|
||||
%r = add i64 %i, %j
|
||||
ret i64 %r
|
||||
}
|
||||
|
||||
define i64 @test2(i32 %i) nounwind readnone {
|
||||
; ASM-LABEL: test2:
|
||||
; ASM: # %bb.0:
|
||||
; ASM-NEXT: movl %edi, %eax
|
||||
; ASM-NEXT: addl -{{[0-9]+}}(%rsp), %eax
|
||||
; ASM-NEXT: retq
|
||||
; MIR-LABEL: name: test2
|
||||
; MIR: bb.0 (%ir-block.0):
|
||||
; MIR-NEXT: liveins: $edi
|
||||
; MIR-NEXT: {{ $}}
|
||||
; MIR-NEXT: [[COPY:%[0-9]+]]:gr32 = COPY $edi
|
||||
; 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)
|
||||
; MIR-NEXT: [[SUBREG_TO_REG:%[0-9]+]]:gr64 = SUBREG_TO_REG 0, killed [[ADD32rm]], %subreg.sub_32bit
|
||||
; MIR-NEXT: $rax = COPY [[SUBREG_TO_REG]]
|
||||
; MIR-NEXT: RET 0, $rax
|
||||
%loc = alloca i32
|
||||
%j = load i32, ptr %loc
|
||||
%r = add i32 %i, %j
|
||||
%ext = zext i32 %r to i64
|
||||
ret i64 %ext
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
; RUN: llc -mtriple=x86_64 < %s | FileCheck %s --check-prefix=CHECK
|
||||
; RUN: llc -mtriple=x86_64 -stop-after=finalize-isel < %s | FileCheck %s --check-prefix=CHECK
|
||||
|
||||
define i32 @add(i32 %a, i32 %b) {
|
||||
%sum = add i32 %a, %b
|
||||
ret i32 %sum
|
||||
}
|
||||
|
||||
define i32 @sub(i32 %a, i32 %b) {
|
||||
%diff = sub i32 %a, %b
|
||||
ret i32 %diff
|
||||
}
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
|
||||
; RUN: llc -mtriple=x86_64 < %s | FileCheck %s --check-prefix=CHECK
|
||||
; RUN: llc -mtriple=x86_64 -stop-after=finalize-isel < %s | FileCheck %s --check-prefix=CHECK
|
||||
|
||||
define i32 @add(i32 %a, i32 %b) {
|
||||
%sum = add i32 %a, %b
|
||||
ret i32 %sum
|
||||
}
|
||||
|
||||
define i32 @sub(i32 %a, i32 %b) {
|
||||
%diff = sub i32 %a, %b
|
||||
ret i32 %diff
|
||||
}
|
||||
|
||||
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
|
||||
; CHECK: {{.*}}
|
||||
@ -0,0 +1,9 @@
|
||||
# REQUIRES: x86-registered-target
|
||||
## Test checking that update_llc_test_checks.py can generate both ASM and MIR checks in the same file
|
||||
|
||||
# RUN: cp -f %S/Inputs/x86_asm_mir_mixed.ll %t.ll && %update_llc_test_checks %t.ll
|
||||
# RUN: diff -u %S/Inputs/x86_asm_mir_mixed.ll.expected %t.ll
|
||||
|
||||
## Verify that running the script again on an already updated file doesn't add duplicate checks
|
||||
# RUN: %update_llc_test_checks %t.ll
|
||||
# RUN: diff -u %S/Inputs/x86_asm_mir_mixed.ll.expected %t.ll
|
||||
@ -0,0 +1,8 @@
|
||||
# REQUIRES: x86-registered-target
|
||||
## Test that using the same prefix for both ASM and MIR outputs generates a warning
|
||||
## and doesn't produce any checks.
|
||||
|
||||
# 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
|
||||
# RUN: diff -u %S/Inputs/x86_asm_mir_same_prefix.ll.expected %t.ll
|
||||
|
||||
# WARNING: WARNING: The following prefixes are used for both ASM and MIR output, which will cause FileCheck failures: CHECK
|
||||
@ -605,6 +605,7 @@ TRIPLE_IR_RE = re.compile(r'^\s*target\s+triple\s*=\s*"([^"]+)"$')
|
||||
TRIPLE_ARG_RE = re.compile(r"-m?triple[= ]([^ ]+)")
|
||||
MARCH_ARG_RE = re.compile(r"-march[= ]([^ ]+)")
|
||||
DEBUG_ONLY_ARG_RE = re.compile(r"-debug-only[= ]([^ ]+)")
|
||||
STOP_PASS_RE = re.compile(r"-stop-(before|after)=(\w+)")
|
||||
|
||||
IS_DEBUG_RECORD_RE = re.compile(r"^(\s+)#dbg_")
|
||||
IS_SWITCH_CASE_RE = re.compile(r"^\s+i\d+ \d+, label %\S+")
|
||||
|
||||
@ -163,13 +163,15 @@ def add_mir_checks_for_function(
|
||||
print_fixed_stack,
|
||||
first_check_is_next,
|
||||
at_the_function_name,
|
||||
check_indent=None,
|
||||
):
|
||||
printed_prefixes = set()
|
||||
for run in run_list:
|
||||
for prefix in run[0]:
|
||||
if prefix in printed_prefixes:
|
||||
break
|
||||
if not func_dict[prefix][func_name]:
|
||||
# func_info can be empty if there was a prefix conflict.
|
||||
if not func_dict[prefix].get(func_name):
|
||||
continue
|
||||
if printed_prefixes:
|
||||
# Add some space between different check prefixes.
|
||||
@ -185,6 +187,7 @@ def add_mir_checks_for_function(
|
||||
func_dict[prefix][func_name],
|
||||
print_fixed_stack,
|
||||
first_check_is_next,
|
||||
check_indent,
|
||||
)
|
||||
break
|
||||
else:
|
||||
@ -204,6 +207,7 @@ def add_mir_check_lines(
|
||||
func_info,
|
||||
print_fixed_stack,
|
||||
first_check_is_next,
|
||||
check_indent=None,
|
||||
):
|
||||
func_body = str(func_info).splitlines()
|
||||
if single_bb:
|
||||
@ -220,7 +224,10 @@ def add_mir_check_lines(
|
||||
first_line = func_body[0]
|
||||
indent = len(first_line) - len(first_line.lstrip(" "))
|
||||
# A check comment, indented the appropriate amount
|
||||
check = "{:>{}}; {}".format("", indent, prefix)
|
||||
if check_indent is not None:
|
||||
check = "{}; {}".format(check_indent, prefix)
|
||||
else:
|
||||
check = "{:>{}}; {}".format("", indent, prefix)
|
||||
|
||||
output_lines.append("{}-LABEL: name: {}".format(check, func_name))
|
||||
|
||||
|
||||
@ -15,7 +15,7 @@ import argparse
|
||||
import os # Used to advertise this file's name ("autogenerated_note").
|
||||
import sys
|
||||
|
||||
from UpdateTestChecks import common
|
||||
from UpdateTestChecks import common, mir
|
||||
|
||||
# llc is the only llc-like in the LLVM tree but downstream forks can add
|
||||
# additional ones here if they have them.
|
||||
@ -33,6 +33,7 @@ def update_test(ti: common.TestInfo):
|
||||
break
|
||||
|
||||
run_list = []
|
||||
mir_run_list = []
|
||||
for l in ti.run_lines:
|
||||
if "|" not in l:
|
||||
common.warn("Skipping unparsable RUN line: " + l)
|
||||
@ -57,9 +58,14 @@ def update_test(ti: common.TestInfo):
|
||||
if m:
|
||||
march_in_cmd = m.groups()[0]
|
||||
|
||||
target_list = run_list
|
||||
m = common.DEBUG_ONLY_ARG_RE.search(llc_cmd)
|
||||
if m and m.groups()[0] == "isel":
|
||||
from UpdateTestChecks import isel as output_type
|
||||
elif not m and common.STOP_PASS_RE.search(llc_cmd):
|
||||
# MIR output mode. If -debug-only is present assume
|
||||
# the debug output is the main point of interest.
|
||||
target_list = mir_run_list
|
||||
else:
|
||||
from UpdateTestChecks import asm as output_type
|
||||
|
||||
@ -84,7 +90,7 @@ def update_test(ti: common.TestInfo):
|
||||
|
||||
# FIXME: We should use multiple check prefixes to common check lines. For
|
||||
# now, we just ignore all but the last.
|
||||
run_list.append(
|
||||
target_list.append(
|
||||
(
|
||||
check_prefixes,
|
||||
llc_tool,
|
||||
@ -119,14 +125,20 @@ def update_test(ti: common.TestInfo):
|
||||
ginfo=ginfo,
|
||||
)
|
||||
|
||||
for (
|
||||
prefixes,
|
||||
llc_tool,
|
||||
llc_args,
|
||||
preprocess_cmd,
|
||||
triple_in_cmd,
|
||||
march_in_cmd,
|
||||
) in run_list:
|
||||
# Dictionary to store MIR function bodies separately
|
||||
mir_func_dict = {}
|
||||
for run_tuple, is_mir in [(run, False) for run in run_list] + [
|
||||
(run, True) for run in mir_run_list
|
||||
]:
|
||||
(
|
||||
prefixes,
|
||||
llc_tool,
|
||||
llc_args,
|
||||
preprocess_cmd,
|
||||
triple_in_cmd,
|
||||
march_in_cmd,
|
||||
) = run_tuple
|
||||
|
||||
common.debug("Extracted LLC cmd:", llc_tool, llc_args)
|
||||
common.debug("Extracted FileCheck prefixes:", str(prefixes))
|
||||
|
||||
@ -141,22 +153,54 @@ def update_test(ti: common.TestInfo):
|
||||
if not triple:
|
||||
triple = common.get_triple_from_march(march_in_cmd)
|
||||
|
||||
scrubber, function_re = output_type.get_run_handler(triple)
|
||||
if 0 == builder.process_run_line(
|
||||
function_re, scrubber, raw_tool_output, prefixes
|
||||
):
|
||||
common.warn(
|
||||
"Couldn't match any function. Possibly the wrong target triple has been provided"
|
||||
if is_mir:
|
||||
# MIR output mode
|
||||
common.debug("Detected MIR output mode for prefixes:", str(prefixes))
|
||||
for prefix in prefixes:
|
||||
if prefix not in mir_func_dict:
|
||||
mir_func_dict[prefix] = {}
|
||||
|
||||
mir.build_function_info_dictionary(
|
||||
ti.path,
|
||||
raw_tool_output,
|
||||
triple,
|
||||
prefixes,
|
||||
mir_func_dict,
|
||||
ti.args.verbose,
|
||||
)
|
||||
builder.processed_prefixes(prefixes)
|
||||
else:
|
||||
# ASM output mode
|
||||
scrubber, function_re = output_type.get_run_handler(triple)
|
||||
if 0 == builder.process_run_line(
|
||||
function_re, scrubber, raw_tool_output, prefixes
|
||||
):
|
||||
common.warn(
|
||||
"Couldn't match any function. Possibly the wrong target triple has been provided"
|
||||
)
|
||||
builder.processed_prefixes(prefixes)
|
||||
|
||||
func_dict = builder.finish_and_get_func_dict()
|
||||
|
||||
# Check for conflicts: same prefix used for both ASM and MIR
|
||||
conflicting_prefixes = set(func_dict.keys()) & set(mir_func_dict.keys())
|
||||
if conflicting_prefixes:
|
||||
common.warn(
|
||||
"The following prefixes are used for both ASM and MIR output, which will cause FileCheck failures: {}".format(
|
||||
", ".join(sorted(conflicting_prefixes))
|
||||
),
|
||||
test_file=ti.path,
|
||||
)
|
||||
for prefix in conflicting_prefixes:
|
||||
mir_func_dict[prefix] = {}
|
||||
func_dict[prefix] = {}
|
||||
|
||||
global_vars_seen_dict = {}
|
||||
|
||||
is_in_function = False
|
||||
is_in_function_start = False
|
||||
func_name = None
|
||||
prefix_set = set([prefix for p in run_list for prefix in p[0]])
|
||||
prefix_set.update([prefix for p in mir_run_list for prefix in p[0]])
|
||||
common.debug("Rewriting FileCheck prefixes:", str(prefix_set))
|
||||
output_lines = []
|
||||
|
||||
@ -221,6 +265,22 @@ def update_test(ti: common.TestInfo):
|
||||
is_filtered=builder.is_filtered(),
|
||||
)
|
||||
)
|
||||
|
||||
# Also add MIR checks if we have them for this function
|
||||
if mir_run_list and func_name:
|
||||
mir.add_mir_checks_for_function(
|
||||
ti.path,
|
||||
output_lines,
|
||||
mir_run_list,
|
||||
mir_func_dict,
|
||||
func_name,
|
||||
single_bb=False, # Don't skip basic block labels.
|
||||
print_fixed_stack=False, # Don't print fixed stack (ASM tests don't need it).
|
||||
first_check_is_next=False, # First check is LABEL, not NEXT.
|
||||
at_the_function_name=False, # Use "name:" not "@name".
|
||||
check_indent="", # No indentation for IR files (not MIR files).
|
||||
)
|
||||
|
||||
is_in_function_start = False
|
||||
|
||||
if is_in_function:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user