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
309 lines
10 KiB
Python
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()
|