#!/usr/bin/env python3 # Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved. # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. from dataclasses import dataclass from typing import Dict, List, Optional, Any, Callable, TypeVar, Generic, Union import re import warnings """ A configuration parser for rocPRIM config header files that extracts and manages algorithm-specific configurations across different architectures and data types. This module provides functionality to parse C++ template specializations from header files containing default configurations for various AMD GPU architectures. It supports both simple parameter extraction using predefined patterns and custom extraction logic through user-provided functions. """ T = TypeVar("T") # Type variable for config parameters @dataclass class ConfigInfo(Generic[T]): """Generic configuration info class.""" arch: str key_type: str value_type: str params: T ParamType = Union[Dict[str, List[int]], Any] ExtractorType = Callable[[str, str, str, str], Optional[ParamType]] class ConfigParser(Generic[T]): """Parser for device configuration header files.""" def __init__( self, algo_name: str, param_names: Optional[List[str]] = None, extract_params: Optional[ExtractorType] = None, ): self.algo_name = algo_name self.param_names = param_names self.custom_extractor = extract_params self.configs: List[ConfigInfo[T]] = [] # Initialize regex patterns self.arch_pattern = r"target_arch::(\w+)" self.key_type_pattern = r"key_type\s*=\s*([^\s,]+)" self.value_type_pattern = r"value_type\s*=\s*([^\s,]+)" # Build default config pattern if using simple parameter extraction if param_names and not extract_params: config_params_pattern = r"\s*,\s*".join([r"(\d+)"] * len(param_names)) self.config_pattern = rf"{algo_name}_config\s*<{config_params_pattern}>" # Base pattern for matching configurations self.base_pattern = ( r"//\s*Based on key_type\s*=\s*[^\s,]+,\s*value_type\s*=\s*[^\n]+\n" r"\s*template\s*<[^>]+>\s*" r"struct\s+default_{algo_name}_config<[^\{{]+>>[^\{{]*" r":[^\{{]+\n*\{{[^\}}]*\}};" ) self.specialization_pattern = self.base_pattern.format(algo_name=algo_name) def _extract_simple_params( self, config_text: str, arch: str, key_type: str, value_type: str ) -> Optional[Dict[str, List[int]]]: """Default parameter extraction for simple configurations.""" if not self.param_names: return None config_match = re.search(self.config_pattern, config_text) if not config_match: return None params = {} try: for i, param_name in enumerate(self.param_names, 1): params[param_name] = [int(config_match.group(i))] return params except (IndexError, ValueError) as e: warnings.warn( f"Failed to parse parameters for {arch}/{key_type}/{value_type}: {e}" ) return None def parse_header(self, header_content: str) -> List[ConfigInfo[T]]: """ Parse the header content and extract configurations. Args: header_content: Content of the header file. Returns: List of ConfigInfo objects containing the parsed configurations. """ self.configs = [] specializations = re.finditer( self.specialization_pattern, header_content, re.DOTALL ) for spec in specializations: self._process_specialization(spec.group(0)) if not self.configs: warnings.warn( f"No configuration patterns found for algorithm '{self.algo_name}'" ) return self.configs def _process_specialization(self, spec_text: str) -> None: """ Process a single specialization and extract its configuration. Args: spec_text: Text of the specialization to process. """ arch_match = re.search(self.arch_pattern, spec_text) key_match = re.search(self.key_type_pattern, spec_text) value_match = re.search(self.value_type_pattern, spec_text) if not all((arch_match, key_match, value_match)): warnings.warn("Failed to extract basic fields from specialization.") return arch = arch_match.group(1) key_type = key_match.group(1).strip() value_type = value_match.group(1).strip() # Use custom extractor if provided, otherwise use simple extraction if self.custom_extractor: params = self.custom_extractor(spec_text, arch, key_type, value_type) else: params = self._extract_simple_params(spec_text, arch, key_type, value_type) if params is not None: self.configs.append( ConfigInfo( arch=arch, key_type=key_type, value_type=value_type, params=params ) ) def get_default_config( self, arch: str, key_type: str, value_type: str = "" ) -> Optional[T]: """ Get the default configuration for a specific architecture and types. Args: arch: Target architecture (e.g., 'gfx1030'). key_type: Type of keys (e.g., 'int', 'float'). value_type: Type of values (e.g., 'int', 'empty_type'). Returns: Configuration parameters, or None if not found. """ value_type = "empty_type" if not value_type else value_type for config in self.configs: if ( config.arch == arch and config.key_type == key_type and config.value_type == value_type ): return config.params for config in self.configs: if ( config.arch == "unknown" and config.key_type == key_type and config.value_type == value_type ): return config.params warnings.warn( f"No configuration found for:\n" f" Architecture: {arch}\n" f" Key type: {key_type}\n" f" Value type: {value_type}\n" f"Available architectures: {sorted(set(c.arch for c in self.configs))}" ) return None