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()
 |