[MLIR][SPIRV] Extend automation script to generate coverage report.

This patch adds a new cli argument to the automation script to generate
a report of the current SPIRV spec instruction coverage. It dumps to the
standard output a YAML string with the coverage information.

Differential Revision: https://reviews.llvm.org/D82006
This commit is contained in:
ergawy 2020-06-23 11:38:30 -04:00 committed by Lei Zhang
parent 79ceda2e39
commit 4837562de4
2 changed files with 98 additions and 5 deletions

View File

@ -20,6 +20,7 @@ import itertools
import re
import requests
import textwrap
import yaml
SPIRV_HTML_SPEC_URL = 'https://www.khronos.org/registry/spir-v/specs/unified1/SPIRV.html'
SPIRV_JSON_SPEC_URL = 'https://raw.githubusercontent.com/KhronosGroup/SPIRV-Headers/master/include/spirv/unified1/spirv.core.grammar.json'
@ -72,7 +73,7 @@ def get_spirv_grammar_from_json_spec():
return spirv['operand_kinds'], spirv['instructions']
def split_list_into_sublists(items, offset):
def split_list_into_sublists(items):
"""Split the list of items into multiple sublists.
This is to make sure the string composed from each sublist won't exceed
@ -80,7 +81,6 @@ def split_list_into_sublists(items, offset):
Arguments:
- items: a list of strings
- offset: the offset in calculating each sublist's length
"""
chuncks = []
chunk = []
@ -391,7 +391,7 @@ def gen_operand_kind_enum_attr(operand_kind, capability_mapping):
for case in kind_cases]
# Split them into sublists and concatenate into multiple lines
case_names = split_list_into_sublists(case_names, 6)
case_names = split_list_into_sublists(case_names)
case_names = ['{:6}'.format('') + ', '.join(sublist)
for sublist in case_names]
case_names = ',\n'.join(case_names)
@ -428,7 +428,7 @@ def gen_opcode(instructions):
opcode_list = [
decl_fmt_str.format(name=inst['opname']) for inst in instructions
]
opcode_list = split_list_into_sublists(opcode_list, 6)
opcode_list = split_list_into_sublists(opcode_list)
opcode_list = [
'{:6}'.format('') + ', '.join(sublist) for sublist in opcode_list
]
@ -439,6 +439,75 @@ def gen_opcode(instructions):
' ]>;'.format(name='Opcode', lst=opcode_list)
return opcode_str + '\n\n' + enum_attr
def map_cap_to_opnames(instructions):
"""Maps capabilities to instructions enabled by those capabilities
Arguments:
- instructions: a list containing a subset of SPIR-V instructions' grammar
Returns:
- A map with keys representing capabilities and values of lists of
instructions enabled by the corresponding key
"""
cap_to_inst = {}
for inst in instructions:
caps = inst['capabilities'] if 'capabilities' in inst else ['0_core_0']
for cap in caps:
if cap not in cap_to_inst:
cap_to_inst[cap] = []
cap_to_inst[cap].append(inst['opname'])
return cap_to_inst
def gen_instr_coverage_report(path, instructions):
"""Dumps to standard output a YAML report of current instruction coverage
Arguments:
- path: the path to SPIRBase.td
- instructions: a list containing all SPIR-V instructions' grammar
"""
with open(path, 'r') as f:
content = f.read()
content = content.split(AUTOGEN_OPCODE_SECTION_MARKER)
existing_opcodes = [k[11:] for k in re.findall('def SPV_OC_\w+', content[1])]
existing_instructions = list(
filter(lambda inst: (inst['opname'] in existing_opcodes),
instructions))
instructions_opnames = [inst['opname'] for inst in instructions]
remaining_opcodes = list(set(instructions_opnames) - set(existing_opcodes))
remaining_instructions = list(
filter(lambda inst: (inst['opname'] in remaining_opcodes),
instructions))
rem_cap_to_instr = map_cap_to_opnames(remaining_instructions)
ex_cap_to_instr = map_cap_to_opnames(existing_instructions)
rem_cap_to_cov = {}
# Calculate coverage for each capability
for cap in rem_cap_to_instr:
if cap not in ex_cap_to_instr:
rem_cap_to_cov[cap] = 0.0
else:
rem_cap_to_cov[cap] = \
(len(ex_cap_to_instr[cap]) / (len(ex_cap_to_instr[cap]) \
+ len(rem_cap_to_instr[cap])))
report = {}
# Merge the 3 maps into one report
for cap in rem_cap_to_instr:
report[cap] = {}
report[cap]['Supported Instructions'] = \
ex_cap_to_instr[cap] if cap in ex_cap_to_instr else []
report[cap]['Unsupported Instructions'] = rem_cap_to_instr[cap]
report[cap]['Coverage'] = '{}%'.format(int(rem_cap_to_cov[cap] * 100))
print(yaml.dump(report))
def update_td_opcodes(path, instructions, filter_list):
"""Updates SPIRBase.td with new generated opcode cases.
@ -771,7 +840,6 @@ def extract_td_op_info(op_def):
"""Extracts potentially manually specified sections in op's definition.
Arguments: - A string containing the op's TableGen definition
- doc: the instruction's SPIR-V HTML doc
Returns:
- A dict containing potential manually specified sections
@ -930,6 +998,8 @@ if __name__ == '__main__':
default='Op',
help='SPIR-V instruction category used for choosing '\
'the TableGen base class to define this op')
cli_parser.add_argument('--gen-inst-coverage', dest='gen_inst_coverage', action='store_true')
cli_parser.set_defaults(gen_inst_coverage=False)
args = cli_parser.parse_args()
@ -956,3 +1026,6 @@ if __name__ == '__main__':
print('Done. Note that this script just generates a template; ', end='')
print('please read the spec and update traits, arguments, and ', end='')
print('results accordingly.')
if args.gen_inst_coverage:
gen_instr_coverage_report(args.base_td_path, instructions)

View File

@ -0,0 +1,20 @@
#!/bin/bash
# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
# Script for reporting current level of SPIR-V spec instruction coverage in spv
# Dialect. It dumps to standard output a YAML string of current coverage.
#
# Run as:
# ./report_coverage.sh
set -e
current_file="$(readlink -f "$0")"
current_dir="$(dirname "$current_file")"
python3 ${current_dir}/gen_spirv_dialect.py \
--base-td-path ${current_dir}/../../include/mlir/Dialect/SPIRV/SPIRVBase.td \
--gen-inst-coverage