blob: 815698b0e20ff2560e67d52285bef65f71f8d117 [file] [log] [blame]
#!/usr/bin/env python3
# RISC-V pipeline model checker.
# Copyright (C) 2025 Free Software Foundation, Inc.
#
# This file is part of GCC.
#
# GCC is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GCC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with GCC; see the file COPYING3. If not see
# <http://www.gnu.org/licenses/>.
import re
import sys
import argparse
from pathlib import Path
from typing import List
import pprint
def remove_line_comments(text: str) -> str:
# Remove ';;' and everything after it on each line
cleaned_lines = []
for line in text.splitlines():
comment_index = line.find(';;')
if comment_index != -1:
line = line[:comment_index]
cleaned_lines.append(line)
return '\n'.join(cleaned_lines)
def tokenize_sexpr(s: str) -> List[str]:
# Tokenize input string, including support for balanced {...} C blocks
tokens = []
i = 0
while i < len(s):
c = s[i]
if c.isspace():
i += 1
elif c == '(' or c == ')':
tokens.append(c)
i += 1
elif c == '"':
# Parse quoted string
j = i + 1
while j < len(s) and s[j] != '"':
if s[j] == '\\':
j += 1 # Skip escape
j += 1
tokens.append(s[i:j+1])
i = j + 1
elif c == '{':
# Parse balanced C block
depth = 1
j = i + 1
while j < len(s) and depth > 0:
if s[j] == '{':
depth += 1
elif s[j] == '}':
depth -= 1
j += 1
tokens.append(s[i:j]) # Include enclosing braces
i = j
else:
# Parse atom
j = i
while j < len(s) and not s[j].isspace() and s[j] not in '()"{}':
j += 1
tokens.append(s[i:j])
i = j
return tokens
def parse_sexpr(tokens: List[str]) -> any:
# Recursively parse tokenized S-expression
token = tokens.pop(0)
if token == '(':
lst = []
while tokens[0] != ')':
lst.append(parse_sexpr(tokens))
tokens.pop(0) # Discard closing parenthesis
return lst
elif token.startswith('"') and token.endswith('"'):
return token[1:-1] # Remove surrounding quotes
elif token.startswith('{') and token.endswith('}'):
return token # Keep C code block as-is
else:
return token
def find_define_attr_type(ast: any) -> List[List[str]]:
# Traverse AST to find all (define_attr "type" ...) entries
result = []
if isinstance(ast, list):
if ast and ast[0] == 'define_attr' and len(ast) >= 2 and ast[1] == 'type':
result.append(ast)
for elem in ast:
result.extend(find_define_attr_type(elem))
return result
def parse_md_file(path: Path):
# Read file, remove comments, and parse all top-level S-expressions
with open(path, encoding='utf-8') as f:
raw_content = f.read()
clean_content = remove_line_comments(raw_content)
tokens = tokenize_sexpr(clean_content)
items = []
while tokens:
items.append(parse_sexpr(tokens))
return items
def parsing_str_set(s: str) -> set:
s = s.replace('\\','').split(',')
s = set(map(lambda x: x.strip(), s))
return s
def get_avaliable_types(md_file_path: str):
# Main logic: parse input file and print define_attr "type" expressions
ast = parse_md_file(Path(md_file_path))
# Get all type from define_attr type
define_attr_types = find_define_attr_type(ast)
types = parsing_str_set (define_attr_types[0][2])
return types
def get_consumed_type(entry: List[str]) -> set:
# Extract the consumed type from a define_insn_reservation entry
current_type = entry[0]
if current_type in ['and', 'or']:
return get_consumed_type(entry[1]) | get_consumed_type(entry[2])
elif current_type == 'eq_attr' and entry[1] == 'type':
return parsing_str_set(entry[2])
return set()
def check_pipemodel(md_file_path: str):
# Load the RISCV MD file and check for pipemodel
ast = parse_md_file(Path(md_file_path))
consumed_type = set()
for entry in ast:
entry_type = entry[0]
if entry_type not in ["define_insn_reservation"]:
continue
consumed_type |= get_consumed_type(entry[3])
return consumed_type
def main():
parser = argparse.ArgumentParser(description='Check GCC pipeline model for instruction type coverage')
parser.add_argument('pipeline_model', help='Pipeline model file to check')
parser.add_argument('--base-md',
help='Base machine description file (default: riscv.md in script directory)',
default=None)
parser.add_argument('-v', '--verbose',
help='Show detailed type information',
action='store_true')
args = parser.parse_args()
# Set default base-md path if not provided
if args.base_md is None:
script_dir = Path(__file__).parent
base_md_path = script_dir / "riscv.md"
else:
base_md_path = Path(args.base_md)
avaliable_types = get_avaliable_types(str(base_md_path))
consumed_type = check_pipemodel(args.pipeline_model)
if args.verbose:
print("Available types:\n", avaliable_types)
print("Consumed types:\n", consumed_type)
if not avaliable_types.issubset(consumed_type):
print("Error: Some types are not consumed by the pipemodel")
print("Missing types:\n", avaliable_types - consumed_type)
sys.exit(1)
else:
print("All available types are consumed by the pipemodel.")
if __name__ == '__main__':
main()