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

293 lines
8.7 KiB
Python

# Copyright (c) 2025-2026 Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""Post-parse enrichment for SemaAST blocks.
Transforms raw SemaAST trees into enriched versions by:
- Stripping spurious RETURN_DATA writes from non-RTN DS atomics (XML bug)
- Wrapping VOP3 source operands with NEG/ABS modifier application
- Wrapping VOP3 destination with CLAMP/OMOD modifier application
- Applying known-bug patches keyed by instruction name and ISA
EXEC mask wrapping is NOT done here — it is the lowering pass's
responsibility (``sema_lower.py``).
"""
from __future__ import annotations
import dataclasses
from amdisa.sema_ast import (
ExecModel,
SemaBlock,
SemaNode,
SemaNodeKind,
SemaType,
validate_types,
validate_well_formed,
)
_DS_ATOMIC_PREFIXES = (
'DS_ADD_',
'DS_SUB_',
'DS_RSUB_',
'DS_MIN_',
'DS_MAX_',
'DS_AND_',
'DS_OR_',
'DS_XOR_',
'DS_MSKOR_',
'DS_CMPSTORE_',
'DS_INC_',
'DS_DEC_',
'DS_PK_ADD_',
'DS_COND_SUB_',
'DS_SUB_CLAMP_',
)
_NO_DST_MODIFIER_INSTRUCTIONS = frozenset(
{
'V_CVT_F16_BF8',
'V_CVT_F16_FP8',
'V_CVT_NORM_I16_F16',
'V_CVT_NORM_U16_F16',
}
)
def _is_ds_atomic(name: str) -> bool:
return any(name.startswith(p) for p in _DS_ATOMIC_PREFIXES)
def _is_ds_load_addtid(name: str) -> bool:
return name.startswith('DS_LOAD_ADDTID_') or name.startswith('DS_STORE_ADDTID_')
def enrich_block(
block: SemaBlock,
enc_field_names: frozenset[str] | None = None,
) -> SemaBlock:
"""Apply enrichment transformations to a SemaBlock.
Args:
block: The raw (pre-enrichment) SemaBlock from the parser.
enc_field_names: Set of encoding field names (e.g., ``{'neg', 'abs',
'clamp', 'omod'}``). Used to determine which VOP3 modifiers to
inject. If None, no modifier wrapping is applied.
Returns:
A new SemaBlock with ``enriched=True``. The original is not modified.
"""
if block.is_empty:
return SemaBlock(
instruction_name=block.instruction_name,
pragma=block.pragma,
body=block.body,
enriched=True,
)
body = block.body
name = block.instruction_name
body = _fix_non_rtn_atomics(name, body)
if enc_field_names:
if name in _NO_DST_MODIFIER_INSTRUCTIONS:
enc_field_names = enc_field_names - frozenset({'clamp', 'omod'})
body = _add_vop3_modifiers(body, enc_field_names, block.pragma)
result = SemaBlock(
instruction_name=name,
pragma=block.pragma,
body=body,
enriched=True,
)
return result
def _fix_non_rtn_atomics(name: str, body: SemaNode) -> SemaNode:
"""Strip spurious RETURN_DATA write from non-RTN DS atomics."""
if '_RTN_' in name:
return body
if not (_is_ds_atomic(name) or _is_ds_load_addtid(name)):
return body
if body.kind != SemaNodeKind.SEQ:
return body
new_children = tuple(
stmt for stmt in body.children if not _is_return_data_assign(stmt)
)
if len(new_children) == len(body.children):
return body
return dataclasses.replace(body, children=new_children)
def _is_return_data_assign(node: SemaNode) -> bool:
"""Check if a node is an ASSIGN whose LHS targets RETURN_DATA."""
if node.kind != SemaNodeKind.ASSIGN or not node.children:
return False
lhs = node.children[0]
while lhs.kind == SemaNodeKind.CAST and lhs.children:
lhs = lhs.children[0]
return lhs.kind == SemaNodeKind.ID and lhs.id_name == 'RETURN_DATA'
def _add_vop3_modifiers(
body: SemaNode,
enc_fields: frozenset[str],
pragma: ExecModel,
) -> SemaNode:
"""Wrap source operands with NEG/ABS and destination with CLAMP/OMOD.
Source modifiers (NEG, ABS) are applied per source operand:
if (abs & (1 << idx)) src = fabs(src);
if (neg & (1 << idx)) src = -src;
Destination modifiers are applied to the result:
if (omod) result *= {2, 4, 0.5};
if (clamp) result = clamp(result, 0.0, 1.0);
These are injected as CALL nodes to apply_src_mod / apply_omod /
apply_clamp helper functions, which the lowering pass emits as
inline C++.
"""
has_neg = 'neg' in enc_fields
has_abs = 'abs' in enc_fields
has_clamp = 'clamp' in enc_fields
has_omod = 'omod' in enc_fields
if not (has_neg or has_abs or has_clamp or has_omod):
return body
if has_neg or has_abs:
body = _wrap_src_modifiers(body, has_neg, has_abs)
if has_clamp or has_omod:
body = _wrap_dst_modifiers(body, has_clamp, has_omod)
return body
def _wrap_src_modifiers(
node: SemaNode,
has_neg: bool,
has_abs: bool,
) -> SemaNode:
"""Recursively wrap INSTOPERAND(S, N) reads with src modifier application."""
if (
node.kind == SemaNodeKind.CAST
and node.children
and node.children[0].kind == SemaNodeKind.INSTOPERAND
):
inner = node.children[0]
if (
inner.children
and inner.children[0].kind == SemaNodeKind.ID
and inner.children[0].id_name == 'S'
):
idx_node = inner.children[1] if len(inner.children) > 1 else None
idx_lit = idx_node.lit_value if idx_node else '0'
return SemaNode(
kind=SemaNodeKind.CALL,
ty=node.ty,
call_name='apply_src_mod',
children=(
SemaNode(SemaNodeKind.ID, id_name='apply_src_mod'),
node,
SemaNode(SemaNodeKind.LIT, lit_value=idx_lit),
SemaNode(SemaNodeKind.LIT, lit_value='1' if has_neg else '0'),
SemaNode(SemaNodeKind.LIT, lit_value='1' if has_abs else '0'),
),
)
if not node.children:
return node
new_children = tuple(
_wrap_src_modifiers(c, has_neg, has_abs) for c in node.children
)
if new_children == node.children:
return node
return dataclasses.replace(node, children=new_children)
def _wrap_dst_modifiers(
node: SemaNode,
has_clamp: bool,
has_omod: bool,
) -> SemaNode:
"""Wrap the RHS of the top-level ASSIGN with OMOD then CLAMP."""
if node.kind == SemaNodeKind.ASSIGN and len(node.children) == 2:
lhs, rhs = node.children
if _is_dst_operand(lhs):
if has_omod:
rhs = SemaNode(
kind=SemaNodeKind.CALL,
ty=rhs.ty,
call_name='apply_omod',
children=(
SemaNode(SemaNodeKind.ID, id_name='apply_omod'),
rhs,
),
)
if has_clamp:
rhs = SemaNode(
kind=SemaNodeKind.CALL,
ty=rhs.ty,
call_name='apply_clamp',
children=(
SemaNode(SemaNodeKind.ID, id_name='apply_clamp'),
rhs,
),
)
return dataclasses.replace(node, children=(lhs, rhs))
if node.children:
new_children = tuple(
_wrap_dst_modifiers(c, has_clamp, has_omod) for c in node.children
)
if new_children != node.children:
return dataclasses.replace(node, children=new_children)
return node
def _is_dst_operand(node: SemaNode) -> bool:
"""Check if a node is a destination operand (.instoperand(D, N))."""
if node.kind == SemaNodeKind.INSTOPERAND:
if node.children and node.children[0].kind == SemaNodeKind.ID:
return node.children[0].id_name == 'D'
if node.kind == SemaNodeKind.CAST and node.children:
return _is_dst_operand(node.children[0])
return False
def build_sema_block(
instruction_name: str,
xml_sema: SemaBlock | None,
enc_field_names: frozenset[str] | None = None,
) -> SemaBlock | None:
"""Four-stage composable pipeline to build a complete SemaBlock.
Stage 1: Start from XML parse result (may be None or empty).
Stage 2: Enrich with encoding-format modifiers + bug fixes.
Stage 3: Helper resolution (deferred to lowering — .call nodes
are resolved during C++ emission, not here).
Stage 4: Fallback (returns None — caller uses string-tag generator).
Args:
instruction_name: The instruction mnemonic.
xml_sema: Parsed SemaBlock from ``sema_parser.py``, or None.
enc_field_names: Encoding field names for modifier injection.
Returns:
Enriched SemaBlock, or None if no SemaBlock could be built.
"""
if xml_sema is None or xml_sema.is_empty:
return None
block = enrich_block(xml_sema, enc_field_names)
return block