220 lines
5.9 KiB
Python
Executable File
220 lines
5.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
"""A test case generator for register stackification.
|
|
|
|
This script exhaustively generates small linear SSA programs, then filters them
|
|
based on heuristics designed to keep interesting multivalue test cases and
|
|
prints them as LLVM IR functions in a FileCheck test file.
|
|
|
|
The output of this script is meant to be used in conjunction with
|
|
update_llc_test_checks.py.
|
|
|
|
```
|
|
./multivalue-stackify.py > multivalue-stackify.ll
|
|
../../../utils/update_llc_test_checks.py multivalue-stackify.ll
|
|
```
|
|
|
|
Programs are represented internally as lists of operations, where each operation
|
|
is a pair of tuples, the first of which specifies the operation's uses and the
|
|
second of which specifies its defs.
|
|
|
|
TODO: Before embarking on a rewrite of the register stackifier, an abstract
|
|
interpreter should be written to automatically check that the test assertions
|
|
generated by update_llc_test_checks.py have the same semantics as the functions
|
|
generated by this script. Once that is done, exhaustive testing can be done by
|
|
making `is_interesting` return True.
|
|
"""
|
|
|
|
|
|
from itertools import product
|
|
from collections import deque
|
|
|
|
|
|
MAX_PROGRAM_OPS = 4
|
|
MAX_PROGRAM_DEFS = 3
|
|
MAX_OP_USES = 2
|
|
|
|
|
|
def get_num_defs(program):
|
|
num_defs = 0
|
|
for _, defs in program:
|
|
num_defs += len(defs)
|
|
return num_defs
|
|
|
|
|
|
def possible_ops(program):
|
|
program_defs = get_num_defs(program)
|
|
for num_defs in range(MAX_PROGRAM_DEFS - program_defs + 1):
|
|
for num_uses in range(MAX_OP_USES + 1):
|
|
if num_defs == 0 and num_uses == 0:
|
|
continue
|
|
for uses in product(range(program_defs), repeat=num_uses):
|
|
yield uses, tuple(program_defs + i for i in range(num_defs))
|
|
|
|
|
|
def generate_programs():
|
|
queue = deque()
|
|
queue.append([])
|
|
program_id = 0
|
|
while True:
|
|
program = queue.popleft()
|
|
if len(program) == MAX_PROGRAM_OPS:
|
|
break
|
|
for op in possible_ops(program):
|
|
program_id += 1
|
|
new_program = program + [op]
|
|
queue.append(new_program)
|
|
yield program_id, new_program
|
|
|
|
|
|
def get_num_terminal_ops(program):
|
|
num_terminal_ops = 0
|
|
for _, defs in program:
|
|
if len(defs) == 0:
|
|
num_terminal_ops += 1
|
|
return num_terminal_ops
|
|
|
|
|
|
def get_max_uses(program):
|
|
num_uses = [0] * MAX_PROGRAM_DEFS
|
|
for uses, _ in program:
|
|
for u in uses:
|
|
num_uses[u] += 1
|
|
return max(num_uses)
|
|
|
|
|
|
def has_unused_op(program):
|
|
used = [False] * MAX_PROGRAM_DEFS
|
|
for uses, defs in program[::-1]:
|
|
if defs and all(not used[d] for d in defs):
|
|
return True
|
|
for u in uses:
|
|
used[u] = True
|
|
return False
|
|
|
|
|
|
def has_multivalue_use(program):
|
|
is_multi = [False] * MAX_PROGRAM_DEFS
|
|
for uses, defs in program:
|
|
if any(is_multi[u] for u in uses):
|
|
return True
|
|
if len(defs) >= 2:
|
|
for d in defs:
|
|
is_multi[d] = True
|
|
return False
|
|
|
|
|
|
def has_mvp_use(program):
|
|
is_mvp = [False] * MAX_PROGRAM_DEFS
|
|
for uses, defs in program:
|
|
if uses and all(is_mvp[u] for u in uses):
|
|
return True
|
|
if len(defs) <= 1:
|
|
if any(is_mvp[u] for u in uses):
|
|
return True
|
|
for d in defs:
|
|
is_mvp[d] = True
|
|
return False
|
|
|
|
|
|
def is_interesting(program):
|
|
# Allow only multivalue single-op programs
|
|
if len(program) == 1:
|
|
return len(program[0][1]) > 1
|
|
|
|
# Reject programs where the last two instructions are identical
|
|
if len(program) >= 2 and program[-1][0] == program[-2][0]:
|
|
return False
|
|
|
|
# Reject programs with too many ops that don't produce values
|
|
if get_num_terminal_ops(program) > 2:
|
|
return False
|
|
|
|
# The third use of a value is no more interesting than the second
|
|
if get_max_uses(program) >= 3:
|
|
return False
|
|
|
|
# Reject nontrivial programs that have unused instructions
|
|
if has_unused_op(program):
|
|
return False
|
|
|
|
# Reject programs that have boring MVP uses of MVP defs
|
|
if has_mvp_use(program):
|
|
return False
|
|
|
|
# Otherwise if it has multivalue usage it is interesting
|
|
return has_multivalue_use(program)
|
|
|
|
|
|
def make_llvm_type(num_defs):
|
|
if num_defs == 0:
|
|
return 'void'
|
|
else:
|
|
return '{' + ', '.join(['i32'] * num_defs) + '}'
|
|
|
|
|
|
def make_llvm_op_name(num_uses, num_defs):
|
|
return f'op_{num_uses}_to_{num_defs}'
|
|
|
|
|
|
def make_llvm_args(first_use, num_uses):
|
|
return ', '.join([f'i32 %t{first_use + i}' for i in range(num_uses)])
|
|
|
|
|
|
def print_llvm_program(program, name):
|
|
tmp = 0
|
|
def_data = []
|
|
print(f'define void @{name}() {{')
|
|
for uses, defs in program:
|
|
first_arg = tmp
|
|
# Extract operands
|
|
for use in uses:
|
|
ret_type, var, idx = def_data[use]
|
|
print(f' %t{tmp} = extractvalue {ret_type} %t{var}, {idx}')
|
|
tmp += 1
|
|
# Print instruction
|
|
assignment = ''
|
|
if len(defs) > 0:
|
|
assignment = f'%t{tmp} = '
|
|
result_var = tmp
|
|
tmp += 1
|
|
ret_type = make_llvm_type(len(defs))
|
|
op_name = make_llvm_op_name(len(uses), len(defs))
|
|
args = make_llvm_args(first_arg, len(uses))
|
|
print(f' {assignment}call {ret_type} @{op_name}({args})')
|
|
# Update def_data
|
|
for i in range(len(defs)):
|
|
def_data.append((ret_type, result_var, i))
|
|
print(' ret void')
|
|
print('}')
|
|
|
|
|
|
def print_header():
|
|
print('; NOTE: Test functions have been generated by multivalue-stackify.py.')
|
|
print()
|
|
print('; RUN: llc < %s -verify-machineinstrs -mattr=+multivalue',
|
|
'| FileCheck %s')
|
|
print()
|
|
print('; Test that the multivalue stackification works')
|
|
print()
|
|
print('target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"')
|
|
print('target triple = "wasm32-unknown-unknown"')
|
|
print()
|
|
for num_uses in range(MAX_OP_USES + 1):
|
|
for num_defs in range(MAX_PROGRAM_DEFS + 1):
|
|
if num_uses == 0 and num_defs == 0:
|
|
continue
|
|
ret_type = make_llvm_type(num_defs)
|
|
op_name = make_llvm_op_name(num_uses, num_defs)
|
|
args = make_llvm_args(0, num_uses)
|
|
print(f'declare {ret_type} @{op_name}({args})')
|
|
print()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print_header()
|
|
for i, program in generate_programs():
|
|
if is_interesting(program):
|
|
print_llvm_program(program, 'f' + str(i))
|
|
print()
|