blob: 9253cf58cdfa1ad276fc7e2a5bb6570b2346115b [file]
#!/usr/bin/env python3
# Script to autogenerate the parsing and serialization routines for the
# aarch64 JSON tuning parameters.
#
# Copyright The GNU Toolchain Authors.
#
# 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/>.
DESCRIPTION = """
Maintenance script to regenerate aarch64-json-tunings-*-generated.inc files
from the JSON schema in aarch64-json-schema.h.
This script is run automatically whenever aarch64-json-schema.h is modified.
Usage:
python3 <path-to-script>/aarch64-generate-json-tuning-routines.py [options]
Options:
--generate-only <parser|printer> Generate only parser or printer file.
If not specified, generates both.
Note that the script can be called from any directory.
Generates (in gcc/config/aarch64/):
aarch64-json-tunings-parser-generated.inc
aarch64-json-tunings-printer-generated.inc
"""
import json
import re
import os
import argparse
from typing import Dict, Any, List, Tuple
def extract_schema_from_header(file_path: str) -> str:
with open(file_path, "r") as f:
content = f.read()
# Find the schema_json variable content between R"json( and )json"
pattern = r'static const char \*schema_json = R"json\((.*?)\)json";'
match = re.search(pattern, content, re.DOTALL)
if not match:
raise ValueError("Could not find schema_json in header file")
return match.group(1).strip()
def get_macro(operation: str, field_type: str) -> str:
type_map = {
"int": "INTEGER",
"uint": "UNSIGNED_INTEGER",
"boolean": "BOOLEAN",
"string": "STRING",
"enum": "ENUM",
}
if field_type not in type_map:
raise ValueError(f"Unknown field type: {field_type}")
return f"{operation}_{type_map[field_type]}_FIELD"
def generate_field_code(
operation: str,
key: str,
value: Any,
struct_name: str,
current_path: List[str],
function_map: Dict[str, str],
obj_name: str = "jo",
indent: str = " ",
) -> List[str]:
lines = []
if operation == 'parse':
ctxt = 'ctxt, '
else:
ctxt = ''
if isinstance(value, str):
macro = get_macro(operation.upper(), value)
if value == "enum":
enum_mapping = f"{key}_mappings"
lines.append(
f'{indent}{macro} ({ctxt}{obj_name}, "{key}", {struct_name}.{key}, {enum_mapping});'
)
else:
lines.append(f'{indent}{macro} ({ctxt}{obj_name}, "{key}", {struct_name}.{key});')
elif isinstance(value, dict):
# Nested object - find function name based on current context + key
child_path = current_path + [key]
child_path_key = "_".join(child_path)
func_name = function_map.get(child_path_key, f"{operation.lower()}_{key}")
macro_name = f"{operation.upper()}_OBJECT"
lines.append(
f'{indent}{macro_name} ({ctxt}{obj_name}, "{key}", {struct_name}.{key}, {func_name});'
)
elif isinstance(value, list) and len(value) > 0:
if isinstance(value[0], dict):
element_key = f"{key}_element"
element_path = current_path + [element_key]
element_path_key = "_".join(element_path)
func_name = function_map.get(
element_path_key, f"{operation.lower()}_{element_key}"
)
macro_name = f"{operation.upper()}_ARRAY_FIELD"
if operation.lower() == "serialize":
lines.append(
f'{indent}{macro_name} ({ctxt}{obj_name}, "{key}", {struct_name}.{key}, ARRAY_SIZE ({struct_name}.{key}), {func_name});'
)
else:
lines.append(
f'{indent}{macro_name} ({ctxt}{obj_name}, "{key}", {struct_name}.{key}, {func_name});'
)
else:
raise ValueError(f"Arrays of non-object types are not yet supported: {key}")
else:
raise ValueError(f"Unhandled field type for key '{key}': {type(value)}")
return lines
def generate_field_parsing(
key: str,
value: Any,
struct_name: str,
current_path: List[str],
function_map: Dict[str, str],
indent: str = " ",
) -> List[str]:
return generate_field_code(
"parse", key, value, struct_name, current_path, function_map, "jo", indent
)
def generate_field_serialization(
key: str,
value: Any,
struct_name: str,
obj_name: str,
current_path: List[str],
function_map: Dict[str, str],
indent: str = " ",
) -> List[str]:
return generate_field_code(
"serialize",
key,
value,
struct_name,
current_path,
function_map,
obj_name,
indent,
)
def generate_function(
operation: str,
full_name: str,
local_name: str,
schema: Dict[str, Any],
current_path: List[str],
function_map: Dict[str, str],
) -> List[str]:
lines = []
lines.append("template <typename T>")
if operation.lower() == "parse":
lines.append("static void")
lines.append(f"parse_{full_name} (gcc_json_context &ctxt, const json::object &jo, T &{local_name})")
lines.append("{")
for key, value in schema.items():
field_lines = generate_field_parsing(
key, value, local_name, current_path, function_map
)
lines.extend(field_lines)
elif operation.lower() == "serialize":
lines.append("static std::unique_ptr<json::object>")
lines.append(f"serialize_{full_name} (const T &{local_name})")
lines.append("{")
lines.append(f" auto {local_name}_obj = std::make_unique<json::object> ();")
lines.append("")
for key, value in schema.items():
field_lines = generate_field_serialization(
key, value, local_name, f"{local_name}_obj", current_path, function_map
)
lines.extend(field_lines)
lines.append("")
lines.append(f" return {local_name}_obj;")
lines.append("}")
return lines
"""Collect all object schemas with their full paths. This is necessary for
generating names for the routines with the correct hierarchal path to ensure
that identical keys in different structures are not given the same name.
For example:
vec_costs.issue_info.sve maps to <parse/serialize>_vec_costs_issue_info_sve
vec_costs.sve maps to <parse/serialize>_vec_costs_sve.
"""
def collect_all_objects_with_paths(
schema: Dict[str, Any], path: List[str] = []
) -> Dict[str, Tuple[List[str], Dict[str, Any]]]:
objects = {}
for key, value in schema.items():
current_path = path + [key]
if isinstance(value, dict):
path_key = "_".join(current_path)
objects[path_key] = (current_path, value)
nested = collect_all_objects_with_paths(value, current_path)
objects.update(nested)
elif isinstance(value, list) and len(value) > 0 and isinstance(value[0], dict):
element_key = key.rstrip("s") if key.endswith("s") else f"{key}_element"
element_path = current_path[:-1] + [element_key]
element_path_key = "_".join(element_path)
objects[element_path_key] = (element_path, value[0])
nested = collect_all_objects_with_paths(value[0], element_path)
objects.update(nested)
return objects
"""Calculate dependency depth of an object schema. 0 indicates no
dependencies, ie. the object has only primitive types."""
def get_dependency_depth(obj_schema: Dict[str, Any]) -> int:
max_depth = 0
for value in obj_schema.values():
if isinstance(value, dict):
max_depth = max(max_depth, 1 + get_dependency_depth(value))
elif isinstance(value, list) and len(value) > 0 and isinstance(value[0], dict):
max_depth = max(max_depth, 1 + get_dependency_depth(value[0]))
return max_depth
def generate_enum_mappings(operation: str) -> str:
mappings = f"""
static const enum_mapping<tune_params::aarch64_autoprefetch_model>
autoprefetcher_model_mappings[] = {{
#define AARCH64_AUTOPREFETCH_MODE(NAME, ENUM_VALUE) {{NAME, tune_params::ENUM_VALUE}},
#include "aarch64-tuning-enums.def"
}};
static const enum_mapping<aarch64_ldp_stp_policy> ldp_policy_model_mappings[] = {{
#define AARCH64_LDP_STP_POLICY(NAME, ENUM_VALUE) {{NAME, ENUM_VALUE}},
#include "aarch64-tuning-enums.def"
}};
static const enum_mapping<aarch64_ldp_stp_policy> stp_policy_model_mappings[] = {{
#define AARCH64_LDP_STP_POLICY(NAME, ENUM_VALUE) {{NAME, ENUM_VALUE}},
#include "aarch64-tuning-enums.def"
}};
"""
return mappings
def generate_all_functions(schema_file: str, operation: str) -> str:
schema_str = extract_schema_from_header(schema_file)
schema = json.loads(schema_str)
tune_params_schema = schema.get("tune_params", {})
all_objects_with_paths = collect_all_objects_with_paths(tune_params_schema)
function_map = {}
for path_key, (path, obj_schema) in all_objects_with_paths.items():
if path:
full_name = "_".join(path)
function_map[path_key] = f"{operation}_{full_name}"
else:
function_map[path_key] = f"{operation}_{path_key}"
""" Structures can have nested structures that may not have been defined yet.
Therefore, we need to sort the objects by dependency depth and define
functions for the inner structures first."""
sorted_objects = sorted(
all_objects_with_paths.items(), key=lambda x: get_dependency_depth(x[1][1])
)
generated_functions = []
generated_functions.append(generate_enum_mappings(operation))
for path_key, (path, obj_schema) in sorted_objects:
# Use the full path for function generation
if path:
full_name = "_".join(path)
local_name = path[-1]
else:
full_name = path_key
local_name = path_key
function_str = generate_function(
operation, full_name, local_name, obj_schema, path, function_map
)
generated_functions.append("\n".join(function_str))
main_function = generate_function(
operation, "tunings", "tunings", tune_params_schema, [], function_map
)
generated_functions.append("\n".join(main_function))
return "\n\n".join(generated_functions)
def write_generated_include_file(
output_file_path: str, generated_code: str, operation: str
) -> None:
header_comment = f"""/* This file is auto-generated by aarch64-generate-json-tuning-routines.py. */
/* Copyright The GNU Toolchain Authors.
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/>. */
/* This file contains the auto-generated {operation} functions for JSON tuning parameters. */
"""
try:
with open(output_file_path, "w") as f:
f.write(header_comment)
f.write(generated_code)
print(f"Successfully generated {output_file_path}")
except Exception as e:
print(f"Error writing to {output_file_path}: {e}")
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--generate-only',
choices=['parser', 'printer'],
help='Generate only parser or printer file. If not specified, generates both.')
args = parser.parse_args()
try:
script_dir = os.path.dirname(os.path.abspath(__file__))
schema_file = os.path.join(script_dir, "aarch64-json-schema.h")
parser_inc_file = os.path.join(
script_dir, "aarch64-json-tunings-parser-generated.inc"
)
printer_inc_file = os.path.join(
script_dir, "aarch64-json-tunings-printer-generated.inc"
)
if args.generate_only is None or args.generate_only == 'parser':
parser_generated_code = generate_all_functions(schema_file, "parse")
write_generated_include_file(parser_inc_file, parser_generated_code, "parser")
if args.generate_only is None or args.generate_only == 'printer':
serializer_generated_code = generate_all_functions(schema_file, "serialize")
write_generated_include_file(
printer_inc_file, serializer_generated_code, "serializer"
)
print(f"Generated files in: {script_dir}")
except Exception as e:
print(f"Error: {e}")
return 1
return 0
if __name__ == "__main__":
exit(main())