aboutsummaryrefslogtreecommitdiff
path: root/gcc/config/riscv/pipeline-checker
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/config/riscv/pipeline-checker')
-rwxr-xr-xgcc/config/riscv/pipeline-checker191
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()