#!/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 # . 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()