Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

309 lines
10 KiB
Python

# Copyright (c) 2025-2026 Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""CLI entry point: ``python -m amdisa``."""
import argparse
from pathlib import Path
import sys
from tempfile import TemporaryDirectory
import xml.etree.ElementTree as elem_tree
from amdisa import (
Cdna1Profile,
Cdna2Profile,
CdnaProfile,
CodegenConfig,
CodeGenerator,
Gfx1250Profile,
Parser,
Rdna1Profile,
Rdna2Profile,
Rdna3Profile,
Rdna3_5Profile,
Rdna4Profile,
)
from amdisa import xml_schema as xs
from amdisa.cross_isa import CrossIsaAnalyzer
from amdisa.encoding_translator_codegen import (
generate_encoding_fields,
generate_encoding_translators,
)
from amdisa.legalization import LegalizationGenerator
from amdisa.legalization_codegen import emit_all as emit_legalization
from amdisa.isa_properties_codegen import emit_isa_properties
from amdisa.semantics import derive_all_semantics
_ENCODING_TRANSLATOR_PAIRS = [
('cdna4', 'cdna3'),
('cdna4', 'rdna4'),
('cdna4', 'rdna3'),
]
_PROFILES = {
'cdna': CdnaProfile,
'cdna1': Cdna1Profile,
'cdna2': Cdna2Profile,
'cdna3': CdnaProfile,
'cdna4': CdnaProfile,
'rdna1': Rdna1Profile,
'rdna2': Rdna2Profile,
'rdna3': Rdna3Profile,
'rdna3.5': Rdna3_5Profile,
'rdna4': Rdna4Profile,
'gfx1250': Gfx1250Profile,
}
def _collect_shared_execute_body_variants(specs, plan):
"""Collect candidate shared execute bodies from each ISA.
The cross-ISA analyzer proves structural compatibility, but the final
generated body can still depend on architecture-specific lowering choices.
This preflight keeps those generated bodies visible before the real output
pass decides which keys can remain shared.
"""
variants: dict[tuple[str, str], dict[str, tuple]] = {}
with TemporaryDirectory(prefix='amdisa-shared-preflight-') as out_dir:
config = CodegenConfig()
for name, spec, sem in specs:
code_gen = CodeGenerator(
spec, out_dir, sem, config=config, shared_plan=plan
)
code_gen.gen_all()
for key, data in code_gen._shared_execute_bodies.items():
variants.setdefault(key, {})[name] = data
return variants
def _unshared_execute_keys_from_variants(
variants: dict[tuple[str, str], dict[str, tuple]],
) -> frozenset[tuple[str, str]]:
"""Return keys whose generated shared bodies differ between ISAs."""
divergent = set()
for key, by_isa in variants.items():
bodies = {data[2] for data in by_isa.values()}
if len(bodies) > 1:
divergent.add(key)
return frozenset(divergent)
def _merge_shared_execute_body(
merged: dict[tuple[str, str], tuple],
key: tuple[str, str],
data: tuple,
isa_name: str,
) -> None:
"""Merge a final shared body and reject unexpected divergence."""
existing = merged.get(key)
if existing is None:
merged[key] = data
return
if existing[2] != data[2]:
raise AssertionError(
'shared execute body collision after divergence preflight: '
f'isa={isa_name!r} mnemonic={key[0]!r} enc={key[1]!r} produced '
'a different body for a key that was still marked shareable.'
f'\n--- first writer body ---\n{existing[2]}'
f'\n--- this writer body ---\n{data[2]}'
)
def _detect_profile(isa_xml: str) -> str:
"""Detect the ISA profile from the XML filename and architecture name.
Parses only the architecture name element to determine the profile
without loading the full spec.
"""
# TODO: Remove this filename override once the gfx1250 XML carries a
# finalized architecture name that can be detected through the normal path.
if 'gfx1250' in Path(isa_xml).stem:
return 'gfx1250'
root = elem_tree.parse(isa_xml).getroot()
isa_node = xs.get_node(root, xs.ISA)
arch_node = xs.get_node(isa_node, xs.ARCH)
arch_name_raw = xs.get_node_text(xs.get_node(arch_node, xs.ARCH_NAME))
parts = arch_name_raw.split()
family = parts[1].lower()
version = parts[2]
key = f'{family}{version}'
if key in _PROFILES:
return key
key_underscore = f'{family}{version.replace(".", "_")}'
if key_underscore in _PROFILES:
return key_underscore
if family == 'cdna':
return 'cdna'
if family == 'rdna':
major = int(version.split('.')[0])
if major >= 4:
return 'rdna4'
if major >= 3:
return 'rdna3'
return 'rdna1'
return 'cdna'
def _run_multi(args) -> None:
"""Multi-ISA mode: parse all XMLs, run CrossIsaAnalyzer, generate shared + per-ISA."""
specs = []
for entry in args.multi:
if ':' not in entry:
print(
f'error: --multi entry must be name:xml_path, got: {entry}',
file=sys.stderr,
)
sys.exit(1)
name, xml_path = entry.split(':', 1)
profile_key = name.replace('.', '_')
if profile_key not in _PROFILES:
profile_key = _detect_profile(xml_path)
profile = _PROFILES[profile_key]()
spec = Parser(xml_path, profile).parse()
sem = derive_all_semantics(spec)
specs.append((name, spec, sem))
analyzer = CrossIsaAnalyzer()
plan = analyzer.analyze(specs)
print(
f'Cross-ISA analysis: {plan.total_universal} universal, '
f'{plan.total_family_shared} family-shared, '
f'{plan.total_exclusive} exclusive',
file=sys.stderr,
)
# Generate per-ISA files, accumulating shared execute bodies.
if args.gen_isas:
emit_isa_properties(args.isa_output, specs)
body_variants = _collect_shared_execute_body_variants(specs, plan)
unshared_keys = _unshared_execute_keys_from_variants(body_variants)
config = CodegenConfig(unshared_execute_keys=unshared_keys)
if unshared_keys:
print(
f'Keeping {len(unshared_keys)} arch-dependent shared execute '
f'keys ISA-local after body preflight',
file=sys.stderr,
)
all_shared_bodies: dict[tuple[str, str], tuple] = {}
for name, spec, sem in specs:
code_gen = CodeGenerator(
spec, args.isa_output, sem, config=config, shared_plan=plan
)
code_gen.gen_all()
for key, data in code_gen._shared_execute_bodies.items():
_merge_shared_execute_body(all_shared_bodies, key, data, name)
if all_shared_bodies:
first_spec = specs[0][1]
first_sem = specs[0][2]
writer = CodeGenerator(
first_spec, args.isa_output, first_sem, config=config, shared_plan=plan
)
writer._shared_execute_bodies = all_shared_bodies
writer._write_shared_execute_templates()
# DBT legalization tables and encoding translators.
if args.gen_dbt:
dbt_output = args.dbt_output
if not dbt_output:
print(
'error: --dbt-output is required when generating DBT tables with --multi',
file=sys.stderr,
)
sys.exit(1)
leg_gen = LegalizationGenerator(specs)
results = leg_gen.generate_all()
generated = emit_legalization(dbt_output, results)
for src, dst, entries in results:
counts = leg_gen.summary(entries)
identity = counts['identity']
substitute = counts['substitute']
lower = counts['lower']
expand = counts['expand']
illegal = counts['illegal']
print(
f' {src} -> {dst}: {len(entries)} entries '
f'({identity} identity, {substitute} substitute, '
f'{lower} lower, {expand} expand, '
f'{illegal} illegal)',
file=sys.stderr,
)
print(f'Generated {len(generated)} files in {dbt_output}', file=sys.stderr)
generate_encoding_fields(specs, dbt_output)
spec_map = {name: (spec, sem) for name, spec, sem in specs}
for src_n, dst_n in _ENCODING_TRANSLATOR_PAIRS:
if src_n in spec_map and dst_n in spec_map:
src_spec, _ = spec_map[src_n]
dst_spec, _ = spec_map[dst_n]
generate_encoding_translators(
src_spec, dst_spec, src_n, dst_n, dbt_output
)
def main() -> None:
"""Parse an AMD GPU ISA XML spec and generate C++ sources."""
arg_parser = argparse.ArgumentParser(
description='Parse a machine-readable AMD GPU ISA specification and generate C++ sources'
)
arg_parser.add_argument(
'isafile',
nargs='?',
default=None,
help='XML file with machine-readable AMD GPU ISA specification',
)
arg_parser.add_argument(
'--multi',
nargs='+',
metavar='NAME:XML',
help='Multi-ISA mode: parse all XMLs and generate shared execute() templates. '
'Each argument is name:xml_path (e.g., cdna1:/path/to/cdna1.xml).',
)
arg_parser.add_argument(
'--gen-isas',
action='store_true',
default=True,
help='Generate ISA C++ files (decoders, encodings, execute bodies). Default.',
)
arg_parser.add_argument(
'--gen-dbt',
action='store_true',
default=True,
help='Generate DBT legalization tables and encoding translators. Default.',
)
arg_parser.add_argument(
'--isa-output', help='Output path for generated ISA C++ files'
)
arg_parser.add_argument(
'--dbt-output',
metavar='DIR',
help='Output directory for DBT tables (defaults to --isa-output).',
)
args = arg_parser.parse_args()
# Multi-ISA mode.
if args.multi:
_run_multi(args)
return
if not args.isafile:
print('error: isafile required in single-ISA mode', file=sys.stderr)
sys.exit(1)
profile_key = _detect_profile(args.isafile)
profile = _PROFILES[profile_key]()
isa = Parser(args.isafile, profile).parse()
semantics = derive_all_semantics(isa)
config = CodegenConfig()
if args.gen_isas:
code_gen = CodeGenerator(isa, args.isa_output, semantics, config=config)
code_gen.gen_all()
if __name__ == '__main__':
main()