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
293 lines
8.7 KiB
Python
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
|