diff options
Diffstat (limited to 'gcc/config/riscv/pipeline-checker')
-rwxr-xr-x | gcc/config/riscv/pipeline-checker | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/gcc/config/riscv/pipeline-checker b/gcc/config/riscv/pipeline-checker new file mode 100755 index 0000000..815698b --- /dev/null +++ b/gcc/config/riscv/pipeline-checker @@ -0,0 +1,191 @@ +#!/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() |