| #!/usr/bin/env python |
| |
| # Copyright 2018 The Clspv Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import argparse |
| import collections |
| import os |
| import re |
| import subprocess |
| import tempfile |
| |
| DESCRIPTION = """This script can be used to help with writing test cases for clspv. |
| |
| When passed an OpenCL C source file, it will: |
| |
| 1. Build a SPIR-V module using clspv |
| 2. Disassemble the module |
| 3. Post-process the disassembly to introduce FileCheck variables and CHECK directives |
| 4. Prepend appropriate run commands, append the source and print the final test case |
| |
| When passed a SPIR-V module, only the post-processed disassembly is printed. |
| """ |
| |
| # When one of these regular expressions matches on a disassembly line |
| # CHECK-DAG will be used instead of CHECK |
| CHECK_DAG_INSTRUCTION_REGEXES = ( |
| r'OpType[a-zA-Z]+', |
| r'OpConstant[a-zA-Z]*', |
| r'OpSpecConstant[a-zA-Z]*', |
| r'OpCapability', |
| r'OpUndef', |
| r'OpVariable', |
| ) |
| |
| CHECK_DAG = '// CHECK-DAG:' |
| CHECK = '// CHECK:' |
| |
| DROP_REGEXES = { |
| 'boilerplate': ( |
| r'^;', |
| r'OpCapability', |
| r'OpExtension', |
| r'OpMemoryModel', |
| r'OpEntryPoint', |
| r'OpExecutionMode', |
| r'OpSource', |
| r'OpDecorate', |
| r'OpMemberDecorate', |
| ), |
| 'functions': ( |
| r'OpTypeFunction', |
| r'OpFunction', |
| r'OpLabel', |
| r'OpReturn', |
| r'OpFunctionEnd', |
| ), |
| 'memory': ( |
| r'OpTypeRuntimeArray', |
| r'OpTypePointer', |
| r'OpVariable', |
| r'OpAccessChain', |
| r'OpLoad', |
| r'OpStore', |
| ), |
| } |
| |
| # Keep track of the FileCheck variables we're defining |
| variables = set() |
| |
| def substitute_backreference(regex, value): |
| return regex.replace(r'\1', value) |
| |
| def replace_ids_with_filecheck_variables(line): |
| # regex to match IDs, (variable name pattern, variable definition pattern) |
| regexp_repl = collections.OrderedDict(( |
| (r'%([0-9]+)', (r'__original_id_\1', r'%[[\1:[0-9]+]]')), |
| (r'%([0-9a-zA-Z_]+)', (r'\1', r'%[[\1:[0-9a-zA-Z_]+]]')), |
| )) |
| def repl(m): |
| namerex, defrex = regexp_repl[m.re.pattern] |
| var_match = m.group(1) |
| |
| # Get the final FileCheck variable name |
| var = substitute_backreference(namerex, var_match) |
| |
| # Do we know that variable? |
| if var in variables: |
| # Just use it |
| return '%[[{}]]'.format(var) |
| else: |
| # Add it to the list of known variables and define it in the output |
| variables.add(var) |
| return substitute_backreference(defrex, var) |
| |
| for rr in regexp_repl.items(): |
| line = re.sub(rr[0], repl, line) |
| |
| return line |
| |
| def process_disasm_line(args, line): |
| |
| def format_line(check, line): |
| return '{:{}} {}'.format(check, len(CHECK_DAG), line.strip()) |
| |
| # Drop the line? |
| for drop_group in args.drop: |
| for rex in DROP_REGEXES[drop_group]: |
| if re.search(rex, line): |
| return '' |
| |
| # First deal with IDs |
| line = replace_ids_with_filecheck_variables(line) |
| |
| # Can we use CHECK-DAG? |
| for rex in CHECK_DAG_INSTRUCTION_REGEXES: |
| if re.search(rex, line): |
| return format_line(CHECK_DAG, line) |
| |
| # Nope, use CHECK |
| return format_line(CHECK, line) |
| |
| def generate_run_section(args): |
| |
| clspv_options = ' '.join(args.clspv_options) |
| |
| runsec = '' |
| runsec += '// RUN: clspv {} %s -o %t.spv\n'.format(clspv_options) |
| runsec += '// RUN: spirv-dis -o %t2.spvasm %t.spv\n' |
| runsec += '// RUN: FileCheck %s < %t2.spvasm\n' |
| runsec += '// RUN: spirv-val --target-env vulkan1.0 %t.spv\n' |
| runsec += '\n' |
| |
| return runsec |
| |
| def disassemble_and_post_process(args, spirv_module): |
| |
| ret = '' |
| |
| # Get SPIR-V disassembly for the module |
| cmd = [ |
| 'spirv-dis', |
| spirv_module |
| ] |
| |
| disasm = subprocess.check_output(cmd).decode('utf-8') |
| |
| # Process the disassembly |
| for line in disasm.split('\n'): |
| if line.strip() == '': |
| continue |
| processed_line = process_disasm_line(args, line) |
| if processed_line: |
| ret += '{}\n'.format(processed_line) |
| |
| return ret |
| |
| def generate_test_case_from_source(args): |
| |
| tc = '' |
| |
| # Start with the RUN directives |
| tc += generate_run_section(args) |
| |
| # Then compile the source |
| fd, spirvfile = tempfile.mkstemp() |
| os.close(fd) |
| |
| cmd = [ |
| args.clspv, |
| ] + args.clspv_options + [ |
| '-o', spirvfile, |
| args.spirv_module_or_cl_source |
| ] |
| |
| subprocess.check_call(cmd) |
| |
| # Append the processed disassembly |
| tc += disassemble_and_post_process(args, spirvfile) |
| |
| # Append the original source |
| tc += '\n' |
| with open(args.spirv_module_or_cl_source) as f: |
| tc += f.read() |
| |
| # Finally, delete the temporary SPIR-V module |
| os.remove(spirvfile) |
| |
| return tc |
| |
| def generate_test_case(args): |
| |
| # Determine whether we got source or SPIR-V |
| _, inputext = os.path.splitext(args.spirv_module_or_cl_source) |
| |
| # Now choose the behaviour |
| if inputext == '.cl': |
| tc = generate_test_case_from_source(args) |
| else: |
| tc = disassemble_and_post_process(args.spirv_module_or_cl_source) |
| |
| return tc |
| |
| if __name__ == '__main__': |
| |
| # Parse command line arguments |
| class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): |
| pass |
| |
| parser = argparse.ArgumentParser( |
| description=DESCRIPTION, |
| formatter_class=HelpFormatter |
| ) |
| |
| parser.add_argument( |
| '--clspv', default='clspv', |
| help='clspv binary to use' |
| ) |
| |
| parser.add_argument( |
| '--clspv-options', action='append', default=[], |
| help='Pass an option to clspv when building (can be used multiple times)' |
| ) |
| |
| parser.add_argument( |
| '--drop', action='append', default=[], choices=DROP_REGEXES.keys(), |
| help='Remove specific groups of instructions from generated SPIR-V disassembly' |
| ) |
| |
| parser.add_argument( |
| 'spirv_module_or_cl_source', |
| help='SPIR-V module or OpenCL C source file' |
| ) |
| |
| args = parser.parse_args() |
| |
| # Generate test case and print to stdout |
| print(generate_test_case(args)) |
| |