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

2597 lines
92 KiB
Python

# Copyright (c) 2026 Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""Derive instruction execution semantics from mnemonic and encoding format.
Instead of maintaining a separate XML file, semantic metadata (class,
operation, data type, SCC behavior, branch condition) is derived directly
from each instruction's mnemonic name and its encoding format name.
"""
from __future__ import annotations
from dataclasses import dataclass, replace
from typing import TYPE_CHECKING
import re
if TYPE_CHECKING:
from amdisa.gpuisa import IsaSpec
@dataclass
class InstructionSemantics:
"""Semantic metadata for a single instruction.
Attributes:
name: Instruction mnemonic (e.g. ``S_ADD_U32``).
semantic_class: High-level category that selects the code-generation
template in :class:`~amdisa.codegen.CodeGenerator` (e.g.
``scalar_binop``, ``vector_cmp``, ``smem_load``).
operation: Sub-operation within the class (e.g. ``add``, ``eq``).
data_type: Canonical data-type string (e.g. ``f32``, ``i64``).
sets_scc: How the instruction updates SCC. ``None`` means no update;
other values are ``nonzero``, ``carry``, ``borrow``, ``overflow``,
``compare``, and ``none`` (explicitly does not set SCC).
branch_condition: For conditional branches, the predicate to test
(e.g. ``scc0``, ``vccnz``, ``execz``).
elem_size: Element size in bytes for memory instructions.
num_elems: Number of elements for memory instructions.
transpose_kind: Cross-lane transpose kind for transpose-load instructions.
sign_extend: True if the load result should be sign-extended.
"""
name: str
semantic_class: str
operation: str | None = None
data_type: str | None = None
sets_scc: str | None = None
branch_condition: str | None = None
elem_size: int | None = None
num_elems: int | None = None
transpose_kind: int = 0
sign_extend: bool = False
d16_hi: bool = False
d16_lo: bool = False
accvgpr_srcs: bool = False
class SemanticsSpec:
"""Collection of instruction semantics keyed by instruction name.
Wraps a ``dict[str, InstructionSemantics]`` with container protocol
methods so callers can use ``name in spec`` and ``spec[name]`` directly.
"""
def __init__(self, instructions: dict[str, InstructionSemantics]) -> None:
self._instructions = instructions
def __getitem__(self, name: str) -> InstructionSemantics:
return self._instructions[name]
def __contains__(self, name: object) -> bool:
return name in self._instructions
def __len__(self) -> int:
return len(self._instructions)
def __iter__(self):
return iter(self._instructions)
@property
def instructions(self) -> dict[str, InstructionSemantics]:
"""Read-only view of the underlying instruction map."""
return self._instructions
# Standard data-type suffixes found at end of AMD ISA mnemonics.
_DTYPE_SUFFIXES = [
# Compound conversion types (must come before simple ones)
'F32_UBYTE0',
'F32_UBYTE1',
'F32_UBYTE2',
'F32_UBYTE3',
'FLR_I32_F32',
'RPI_I32_F32',
'NEAREST_I32_F32',
'FLOOR_I32_F32',
'F64_I32',
'I32_F64',
'F64_U32',
'U32_F64',
'F64_F32',
'F32_F64',
'F16_F32',
'F32_F16',
'F16_U16',
'F16_I16',
'U16_F16',
'I16_F16',
'F32_I32',
'F32_U32',
'I32_F32',
'U32_F32',
'I32_I24',
'U32_U24',
'I32_I16',
'I32_I8',
'U32_U16',
'U32_U8',
'I32_B32',
'I32_B64',
'I32_I64',
'B64_B32',
# Simple types
'F64',
'F32',
'F16',
'I64',
'U64',
'I32',
'U32',
'I24',
'U24',
'I16',
'U16',
'B64',
'B32',
'B16',
]
# Map suffix text to our canonical dtype string.
_DTYPE_MAP = {
'F64': 'f64',
'F32': 'f32',
'F16': 'f16',
'I64': 'i64',
'U64': 'u64',
'I32': 'i32',
'U32': 'u32',
'I24': 'i24',
'U24': 'u24',
'I16': 'i16',
'U16': 'u16',
'B64': 'b64',
'B32': 'b32',
'B16': 'u16',
'F32_I32': 'f32_i32',
'F32_U32': 'f32_u32',
'I32_F32': 'i32_f32',
'U32_F32': 'u32_f32',
'F64_I32': 'f64_i32',
'I32_F64': 'i32_f64',
'F64_U32': 'f64_u32',
'U32_F64': 'u32_f64',
'F64_F32': 'f64_f32',
'F32_F64': 'f32_f64',
'F16_F32': 'f16_f32',
'F32_F16': 'f32_f16',
'F16_U16': 'f16_u16',
'F16_I16': 'f16_i16',
'U16_F16': 'u16_f16',
'I16_F16': 'i16_f16',
'FLR_I32_F32': 'flr_i32_f32',
'RPI_I32_F32': 'rpi_i32_f32',
'NEAREST_I32_F32': 'rpi_i32_f32',
'FLOOR_I32_F32': 'flr_i32_f32',
'F32_UBYTE0': 'f32_ubyte0',
'F32_UBYTE1': 'f32_ubyte1',
'F32_UBYTE2': 'f32_ubyte2',
'F32_UBYTE3': 'f32_ubyte3',
'I32_I24': 'i24',
'U32_U24': 'u24',
'I32_I16': 'i32_i16',
'I32_I8': 'i32_i8',
'U32_U16': 'u32_u16',
'U32_U8': 'u32_u8',
'I32_B32': 'b32',
'I32_B64': 'b64',
'I32_I64': 'i64',
'B64_B32': 'b32',
}
def _split_dtype(name: str):
"""Split instruction name into (stem, dtype_str) or (name, None)."""
for suffix in _DTYPE_SUFFIXES:
tag = '_' + suffix
if name.endswith(tag):
return name[: -len(tag)], _DTYPE_MAP[suffix]
return name, None
# Comparison operation mapping (LG -> ne, negated FP comparisons, etc.)
_CMP_OP_MAP = {
'EQ': 'eq',
'NE': 'ne',
'LG': 'lg',
'LT': 'lt',
'LE': 'le',
'GT': 'gt',
'GE': 'ge',
'F': 'f',
'T': 't',
'TRU': 't',
# Unordered FP comparisons (true when NaN):
'NEQ': 'neq',
'NGE': 'nge',
'NGT': 'ngt',
'NLE': 'nle',
'NLG': 'nlg',
'NLT': 'nlt',
# Ordered / Unordered predicates:
'O': 'o',
'U': 'u',
}
# Operations that produce no SCC update.
_SCC_NONE_OPS = frozenset(
{
'mul',
'mulhi',
'bfm',
'pack_ll',
'pack_lh',
'pack_hh',
}
)
# Operations where SCC = (result == src0) i.e. "compare" semantics (min/max).
_SCC_COMPARE_OPS = frozenset({'min', 'max'})
_SOP1_SCC_NONE_OPS = frozenset(
{
'bitset0',
'bitset1',
# These SOP1 operations have no implicit SCC output in the MR ISA.
# Other bit/count unary operations, such as S_NOT and S_BCNT*, do.
'brev',
'ceil',
'clz',
'clz64',
'ctz',
'cvt_f16_f32',
'cvt_f32_f16',
'cvt_f32_i32',
'cvt_f32_u32',
'cvt_hi_f32_f16',
'cvt_i32_f32',
'cvt_u32_f32',
'ff0',
'ff1',
'flbit',
'flbit_i32',
'flbit_i32_i64',
'floor',
'rndne',
'trunc',
}
)
def _scalar_binop_scc(op: str, dtype: str) -> str:
"""Determine SCC semantics for a scalar binary operation."""
if op in _SCC_NONE_OPS:
return 'none'
if dtype in ('f16', 'f32', 'f64', 'bf16'):
return 'none'
if op in _SCC_COMPARE_OPS:
return 'compare'
# add/addc/lshl*_add on unsigned → carry
if dtype in ('u32', 'u64') and op in (
'add',
'addc',
'lshl1_add',
'lshl2_add',
'lshl3_add',
'lshl4_add',
):
return 'carry'
# sub/subb on unsigned → borrow
if dtype in ('u32', 'u64') and op in ('sub', 'subb'):
return 'borrow'
# add/sub on signed → overflow
if dtype in ('i32', 'i64') and op in ('add', 'sub', 'addc', 'subb'):
return 'overflow'
# Everything else (bitwise, shifts, bfe, absdiff, …) → nonzero
return 'nonzero'
# Branch condition mnemonics.
_CBRANCH_COND = {
'S_CBRANCH_SCC0': 'scc0',
'S_CBRANCH_SCC1': 'scc1',
'S_CBRANCH_VCCZ': 'vccz',
'S_CBRANCH_VCCNZ': 'vccnz',
'S_CBRANCH_EXECZ': 'execz',
'S_CBRANCH_EXECNZ': 'execnz',
}
_NAMED_WAITCNT_COUNTERS = {
'S_WAITCNT_VSCNT': 'waitcnt_vscnt',
'S_WAITCNT_VMCNT': 'waitcnt_vmcnt',
'S_WAITCNT_LGKMCNT': 'waitcnt_lgkmcnt',
'S_WAITCNT_EXPCNT': 'waitcnt_expcnt',
}
def _derive_sopp(name: str) -> InstructionSemantics | None:
"""Derive semantics for an SOPP (Scalar One-operand Program) instruction."""
if name in _CBRANCH_COND:
return InstructionSemantics(
name, 'cbranch', branch_condition=_CBRANCH_COND[name]
)
if name == 'S_BRANCH':
return InstructionSemantics(name, 'branch', branch_condition='always')
if name.startswith('S_ENDPGM'):
return InstructionSemantics(name, 'endpgm')
if name == 'S_TRAP':
# S_TRAP enters the trap handler instead of continuing with the next
# instruction. The simulator currently treats the instruction body as a
# stub, but DBT/CFG construction still needs the semantic class so the
# generated instruction metadata can mark it as a program terminator.
return InstructionSemantics(name, 'trap')
if name == 'S_WAITCNT':
return InstructionSemantics(name, 'waitcnt')
if name == 'S_SET_VGPR_MSB':
return InstructionSemantics(name, 'set_vgpr_msb')
# RDNA4 split-wait instructions (GFX12 — no S_WAITCNT; each waits on a
# single counter whose threshold is the immediate operand directly).
_SPLIT_WAIT = {
'S_WAIT_LOADCNT',
'S_WAIT_STORECNT',
'S_WAIT_KMCNT',
'S_WAIT_DSCNT',
'S_WAIT_EXPCNT',
'S_WAIT_SAMPLECNT',
'S_WAIT_BVHCNT',
'S_WAIT_TENSORCNT',
'S_WAIT_ASYNCCNT',
'S_WAIT_LOADCNT_DSCNT',
'S_WAIT_STORECNT_DSCNT',
}
if name in _SPLIT_WAIT:
return InstructionSemantics(name, 'wait_counter', operation=name[2:].lower())
# S_BARRIER_WAIT uses the existing whole-workgroup barrier model for the
# common split form where S_BARRIER_SIGNAL is immediately followed by
# S_BARRIER_WAIT. Arrival accounting and named-barrier ids are not modeled
# yet: signal stays a no-op, wait parks the wavefront, and CU release logic
# keys only on all non-halted sibling waves in the same workgroup.
if name in ('S_BARRIER', 'S_BARRIER_WAIT'):
return InstructionSemantics(name, 'barrier')
if name == 'S_SET_GPR_IDX_OFF':
return InstructionSemantics(name, 'gpr_idx', operation='off')
if name == 'S_SET_GPR_IDX_MODE':
return InstructionSemantics(name, 'gpr_idx', operation='mode')
# S_NOP, S_SLEEP, S_SETHALT, S_SETPRIO, S_SENDMSG, S_ICACHE_INV,
# S_INCPERFLEVEL, S_DECPERFLEVEL — all are either no-ops or system/debug
# instructions that don't affect compute simulation correctness.
return InstructionSemantics(name, 'true_nop')
# Mnemonic stems (after stripping dtype) → (semantic_class, operation).
# Operations that need special handling are listed separately.
_SOP1_SPECIAL = {
'S_MOV': ('scalar_mov', None),
'S_CMOV': ('scalar_cmov', None),
'S_NOT': ('scalar_unary', 'not'),
'S_WQM': ('scalar_unary', 'wqm'),
'S_BREV': ('scalar_unary', 'brev'),
'S_BCNT0_I32': ('scalar_unary', 'bcnt0'),
'S_BCNT1_I32': ('scalar_unary', 'bcnt1'),
'S_FF0_I32': ('scalar_unary', 'ff0'),
'S_FF1_I32': ('scalar_unary', 'ff1'),
'S_FLBIT_I32': ('scalar_unary', 'flbit'),
'S_FLBIT_I32_I64': None, # handled specially below
'S_BITSET0': ('scalar_unary', 'bitset0'),
'S_BITSET1': ('scalar_unary', 'bitset1'),
'S_GETPC': ('scalar_getpc', None),
'S_GET_PC': ('scalar_getpc', None),
'S_SETPC': ('scalar_setpc', None),
'S_SET_PC': ('scalar_setpc', None),
'S_SWAPPC': ('scalar_swappc', None),
'S_SWAP_PC': ('scalar_swappc', None),
'S_ADD_PC': ('scalar_addpc', None),
'S_GET_SHADER_CYCLES': ('scalar_shader_cycles', None),
'S_RFE': ('true_nop', None),
'S_RFE_RESTORE': ('true_nop', None),
'S_MOVRELD': ('scalar_movrel', 'dst'),
'S_MOVRELS': ('scalar_movrel', 'src'),
'S_MOVRELSD_2': ('scalar_movrel', 'srcdst2'),
'S_ABS': ('scalar_unary', 'abs'),
'S_SEXT_I32_I8': None, # handled specially below
'S_SEXT_I32_I16': None,
'S_QUADMASK': ('scalar_unary', 'quadmask'),
'S_SET_GPR_IDX_ON': ('gpr_idx', 'on'),
'S_SET_GPR_IDX_IDX': ('gpr_idx', 'idx'),
'S_BITREPLICATE': ('scalar_bitreplicate', None),
'S_CBRANCH_JOIN': ('true_nop', None),
'S_BITREPL_B64_B32': ('scalar_bitreplicate', None),
# Newer SOP1 spellings shared by RDNA3+ and gfx1250 profiles:
'S_CTZ_I32': ('scalar_unary', 'ctz'),
'S_CLZ_I32_U32': ('scalar_unary', 'clz'),
'S_CLZ_I32_U64': ('scalar_unary', 'clz64', 'u64'),
'S_CLS_I32': None, # handled specially below
'S_CLS_I32_I64': None,
'S_MOVRELSD2': ('scalar_movrel', 'srcdst2'),
'S_MOVRELSD2_B32': ('scalar_movrel', 'srcdst2'),
'S_MOVRELSD_2': ('scalar_movrel', 'srcdst2'),
'S_MOVRELSD_2_B32': ('scalar_movrel', 'srcdst2'),
'S_SENDMSG_RTN': ('scalar_sendmsg_rtn', None),
'S_BARRIER_SIGNAL': ('true_nop', None),
'S_BARRIER_SIGNAL_ISFIRST': ('true_nop', None),
'S_GET_BARRIER_STATE': ('scalar_barrier_state', None, 'b32'),
'S_BARRIER_INIT': ('true_nop', None),
'S_BARRIER_JOIN': ('true_nop', None),
'S_WAKEUP_BARRIER': ('true_nop', None),
'S_ALLOC_VGPR': ('true_nop', None),
'S_SLEEP_VAR': ('true_nop', None),
'S_CEIL': ('scalar_unary', 'ceil'),
'S_FLOOR': ('scalar_unary', 'floor'),
'S_TRUNC': ('scalar_unary', 'trunc'),
'S_RNDNE': ('scalar_unary', 'rndne'),
'S_CVT_F32_I32': ('scalar_unary', 'cvt_f32_i32'),
'S_CVT_F32_U32': ('scalar_unary', 'cvt_f32_u32'),
'S_CVT_I32_F32': ('scalar_unary', 'cvt_i32_f32'),
'S_CVT_U32_F32': ('scalar_unary', 'cvt_u32_f32'),
'S_CVT_F16_F32': ('scalar_unary', 'cvt_f16_f32'),
'S_CVT_F32_F16': ('scalar_unary', 'cvt_f32_f16'),
'S_CVT_HI_F32_F16': ('scalar_unary', 'cvt_hi_f32_f16'),
}
def _derive_sop1(name: str) -> InstructionSemantics | None:
"""Derive semantics for an SOP1 (Scalar ALU One-operand) instruction."""
# SAVEEXEC / WREXEC patterns (B64 on CDNA/Wave64, B32 on RDNA/Wave32)
m = re.fullmatch(r'S_(\w+)_SAVEEXEC_(B32|B64)', name)
if m:
op = m.group(1).lower()
return InstructionSemantics(
name,
'scalar_saveexec',
operation=op,
data_type=m.group(2).lower(),
sets_scc='nonzero',
)
m = re.fullmatch(r'S_(\w+)_WREXEC_(B32|B64)', name)
if m:
op = m.group(1).lower()
return InstructionSemantics(
name,
'scalar_wrexec',
operation=op,
data_type=m.group(2).lower(),
sets_scc='nonzero',
)
# S_CLS is the newer spelling of the signed S_FLBIT operation.
if name in ('S_FLBIT_I32', 'S_CLS_I32'):
return InstructionSemantics(
name,
'scalar_unary',
operation='flbit_i32',
data_type='i32',
sets_scc='none',
)
# Sign-extend instructions
if name == 'S_SEXT_I32_I8':
return InstructionSemantics(
name, 'scalar_unary', operation='sext8', data_type='i32', sets_scc='none'
)
if name == 'S_SEXT_I32_I16':
return InstructionSemantics(
name, 'scalar_unary', operation='sext16', data_type='i32', sets_scc='none'
)
# The I64 forms read a full SGPR pair and return a 32-bit count.
if name in ('S_FLBIT_I32_I64', 'S_CLS_I32_I64'):
return InstructionSemantics(
name,
'scalar_unary',
operation='flbit_i32_i64',
data_type='i64',
sets_scc='none',
)
# Try matching each known stem against the name. We prefer a
# stem-first lookup over _split_dtype because compound names like
# S_BCNT0_I32_B32 must split as (S_BCNT0_I32, B32) not (S_BCNT0, I32_B32).
stem, dtype = None, None
for key in _SOP1_SPECIAL:
if name == key:
stem, dtype = key, None
break
if name.startswith(key + '_'):
rest = name[len(key) + 1 :]
stem = key
dtype = _DTYPE_MAP.get(rest)
break
if stem is None:
# Fallback to generic dtype stripping
stem, dtype = _split_dtype(name)
entry = _SOP1_SPECIAL.get(stem)
if entry is not None:
cls, op = entry[0], entry[1]
entry_dtype = entry[2] if len(entry) > 2 else None
if entry_dtype:
dtype = entry_dtype
scc = None
if cls == 'scalar_unary':
if op in _SOP1_SCC_NONE_OPS:
scc = 'none'
else:
scc = 'nonzero'
return InstructionSemantics(
name, cls, operation=op, data_type=dtype, sets_scc=scc
)
return None
# Map mnemonic stem (after stripping dtype) to operation name.
_SOP2_OP_MAP = {
'S_ADD': 'add',
'S_ADD_CO': 'add',
'S_ADD_CO_CI': 'addc',
'S_ADD_NC': 'add',
'S_SUB': 'sub',
'S_SUB_CO': 'sub',
'S_SUB_CO_CI': 'subb',
'S_SUB_NC': 'sub',
'S_ADDC': 'addc',
'S_SUBB': 'subb',
'S_MIN': 'min',
'S_MAX': 'max',
'S_MUL': 'mul',
'S_MUL_HI': 'mulhi',
'S_ABSDIFF': 'absdiff',
'S_AND': 'and',
'S_OR': 'or',
'S_XOR': 'xor',
'S_NAND': 'nand',
'S_NOR': 'nor',
'S_XNOR': 'xnor',
'S_ANDN2': 'andn2',
'S_AND_NOT1': 'andn2',
'S_ORN2': 'orn2',
'S_OR_NOT1': 'orn2',
'S_LSHL': 'shl',
'S_LSHR': 'shr',
'S_ASHR': 'ashr',
'S_BFM': 'bfm',
'S_BFE': 'bfe',
'S_CSELECT': None, # special class
'S_LSHL1_ADD': 'lshl1_add',
'S_LSHL2_ADD': 'lshl2_add',
'S_LSHL3_ADD': 'lshl3_add',
'S_LSHL4_ADD': 'lshl4_add',
'S_FMAAK': 'fma',
'S_FMAMK': 'fma',
'S_FMAC': 'fmac',
'S_PACK_LL_B32_B16': None,
'S_PACK_LH_B32_B16': None,
'S_PACK_HH_B32_B16': None,
}
def _derive_sop2(name: str) -> InstructionSemantics | None:
"""Derive semantics for an SOP2 (Scalar ALU Two-operand) instruction."""
# CSELECT
if name.startswith('S_CSELECT_'):
_, dtype = _split_dtype(name)
return InstructionSemantics(name, 'scalar_cselect', data_type=dtype)
# PACK instructions (no dtype suffix to strip - full name is the key)
if name == 'S_PACK_LL_B32_B16':
return InstructionSemantics(
name, 'scalar_binop', operation='pack_ll', data_type='b32', sets_scc='none'
)
if name == 'S_PACK_LH_B32_B16':
return InstructionSemantics(
name, 'scalar_binop', operation='pack_lh', data_type='b32', sets_scc='none'
)
if name == 'S_PACK_HH_B32_B16':
return InstructionSemantics(
name, 'scalar_binop', operation='pack_hh', data_type='b32', sets_scc='none'
)
if name == 'S_PACK_HL_B32_B16':
return InstructionSemantics(
name, 'scalar_binop', operation='pack_hl', data_type='b32', sets_scc='none'
)
if name == 'S_CVT_PK_RTZ_F16_F32':
return InstructionSemantics(
name,
'scalar_cvt_pkrtz_f16_f32',
operation='cvt_pkrtz_f16_f32',
data_type='f32',
sets_scc='none',
)
stem, dtype = _split_dtype(name)
if stem in ('S_MIN_NUM', 'S_MAX_NUM', 'S_MINIMUM', 'S_MAXIMUM'):
op_map = {
'S_MIN_NUM': 'min_num',
'S_MAX_NUM': 'max_num',
'S_MINIMUM': 'minimum',
'S_MAXIMUM': 'maximum',
}
return InstructionSemantics(
name,
'scalar_binop',
operation=op_map[stem],
data_type=dtype,
sets_scc='none',
)
op = _SOP2_OP_MAP.get(stem)
if op is not None and dtype is not None:
if stem in ('S_ADD_CO', 'S_SUB_CO') and dtype == 'i32':
scc = 'overflow'
elif stem == 'S_ADD_CO':
scc = 'carry'
elif stem == 'S_SUB_CO':
scc = 'borrow'
elif stem in ('S_ADD_NC', 'S_SUB_NC'):
scc = 'none'
else:
scc = _scalar_binop_scc(op, dtype)
return InstructionSemantics(
name, 'scalar_binop', operation=op, data_type=dtype, sets_scc=scc
)
# Unrecognized SOP2 instructions (S_CBRANCH_G_FORK, S_RFE_RESTORE, …)
return InstructionSemantics(name, 'nop')
def _derive_sopc(name: str) -> InstructionSemantics | None:
"""Derive semantics for an SOPC (Scalar ALU Compare) instruction."""
# S_BITCMP0_B32, S_BITCMP1_B32, etc.
m = re.match(r'S_BITCMP([01])_(\w+)', name)
if m:
_, dtype = _split_dtype('X_' + m.group(2)) # reuse dtype parser
return InstructionSemantics(
name,
'scalar_bitcmp',
operation='bitcmp' + m.group(1),
data_type=dtype,
sets_scc='compare',
)
# S_CMP_<op>_<dtype>
m = re.match(r'S_CMP_(\w+?)_(I32|U32|I64|U64|F16|F32|F64)$', name)
if m:
cmp_name, dt_raw = m.group(1), m.group(2)
op = _CMP_OP_MAP.get(cmp_name)
if op is not None:
return InstructionSemantics(
name,
'scalar_cmp',
operation=op,
data_type=_DTYPE_MAP[dt_raw],
sets_scc='compare',
)
if name == 'S_SET_GPR_IDX_ON':
return InstructionSemantics(name, 'gpr_idx', operation='on')
# Unrecognized SOPC instructions (S_SETVSKIP, …) → nop
return InstructionSemantics(name, 'nop')
def _derive_sopk(name: str) -> InstructionSemantics | None:
"""Derive semantics for an SOPK (Scalar with 16-bit Immediate) instruction."""
if name in _NAMED_WAITCNT_COUNTERS:
return InstructionSemantics(
name, 'wait_counter', operation=_NAMED_WAITCNT_COUNTERS[name]
)
if name == 'S_VERSION':
return InstructionSemantics(name, 'true_nop')
if name == 'S_MOVK_I32':
return InstructionSemantics(name, 'scalar_movk')
if name == 'S_CMOVK_I32':
return InstructionSemantics(name, 'scalar_cmovk')
if name == 'S_ADDK_I32':
return InstructionSemantics(name, 'scalar_addk', sets_scc='overflow')
if name == 'S_ADDK_CO_I32':
return InstructionSemantics(name, 'scalar_addk', sets_scc='overflow')
if name == 'S_MULK_I32':
return InstructionSemantics(name, 'scalar_mulk')
if name in ('S_CALL_B64', 'S_CALL_I64'):
return InstructionSemantics(name, 'scalar_call')
# S_CMPK_<op>_<dtype>
m = re.match(r'S_CMPK_(\w+?)_(I32|U32)$', name)
if m:
cmp_name, dt_raw = m.group(1), m.group(2)
op = _CMP_OP_MAP.get(cmp_name)
if op is not None:
return InstructionSemantics(
name,
'scalar_cmpk',
operation=op,
data_type=_DTYPE_MAP[dt_raw],
sets_scc='compare',
)
if name == 'S_GETREG_B32':
return InstructionSemantics(name, 'scalar_getreg')
if name == 'S_SETREG_B32':
return InstructionSemantics(name, 'scalar_setreg')
if name == 'S_SETREG_IMM32_B32':
return InstructionSemantics(name, 'scalar_setreg_imm')
# Everything else (CBRANCH_I_FORK, …) → nop
return InstructionSemantics(name, 'nop')
# Map mnemonic stem → operation.
_VOP1_OP_MAP = {
'V_MOV': ('vector_mov', None),
'V_READFIRSTLANE': ('vector_readfirstlane', None),
'V_NOP': ('true_nop', None),
'V_CLREXCP': ('true_nop', None),
'V_SAT_PK_U8_I16': ('nop', None),
'V_SCREEN_PARTITION_4SE': ('nop', None),
'V_ACCVGPR_MOV': ('vector_mov', None),
'V_CVT_F32_FP8': ('vector_unary', 'cvt_f32_fp8'),
'V_CVT_F32_BF8': ('vector_unary', 'cvt_f32_bf8'),
'V_CVT_F32_BF16': ('vector_unary', 'cvt_f32_bf16'),
'V_CVT_PK_F32_FP8': ('cvt_fp8', 'pk_f32_fp8'),
'V_CVT_PK_F32_BF8': ('cvt_fp8', 'pk_f32_bf8'),
'V_CVT_OFF_F32_I4': ('nop', None),
'V_CVT_NORM_I16': ('vector_unary', 'cvt_norm_i16_f16'),
'V_CVT_NORM_U16': ('vector_unary', 'cvt_norm_u16_f16'),
'V_SWAP': ('vector_swap', None),
'V_NOT': ('vector_unary', 'not'),
'V_BFREV': ('vector_unary', 'bfrev'),
'V_BCNT_U32': ('vector_unary', 'bcnt'),
'V_FFBL': ('vector_unary', 'ffbl'),
'V_FFBH_U32': ('vector_unary', 'ffbh_u32'),
'V_FFBH_I32': ('vector_unary', 'ffbh_i32'),
'V_CEIL': ('vector_unary', 'ceil'),
'V_FLOOR': ('vector_unary', 'floor'),
'V_TRUNC': ('vector_unary', 'trunc'),
'V_FRACT': ('vector_unary', 'fract'),
'V_RNDNE': ('vector_unary', 'rndne'),
'V_RCP': ('vector_unary', 'rcp'),
'V_RCP_IFLAG': ('vector_unary', 'rcp_iflag'),
'V_RSQ': ('vector_unary', 'rsq'),
'V_SQRT': ('vector_unary', 'sqrt'),
'V_SIN': ('vector_unary', 'sin'),
'V_COS': ('vector_unary', 'cos'),
'V_TANH': ('vector_unary', 'tanh'),
'V_LOG': ('vector_unary', 'log2'),
'V_LOG_LEGACY': ('vector_unary', 'log2'),
'V_EXP': ('vector_unary', 'exp2'),
'V_EXP_LEGACY': ('vector_unary', 'exp2'),
'V_PRNG': ('true_nop', None), # PRNG not simulated
'V_PERMLANE16_SWAP': ('vector_permlane16_swap', None),
'V_PERMLANE32_SWAP': ('vector_permlane32_swap', None),
'V_SWAPREL': ('nop', None),
'V_S_EXP': ('vector_unary', 'exp2'),
'V_S_LOG': ('vector_unary', 'log2'),
'V_S_RCP': ('vector_unary', 'rcp'),
'V_S_RSQ': ('vector_unary', 'rsq'),
'V_S_SQRT': ('vector_unary', 'sqrt'),
'V_FREXP_EXP_I32': ('vector_unary', 'frexp_exp_f32'),
'V_FREXP_EXP_I16': ('vector_unary', 'frexp_exp_f16'),
'V_FREXP_MANT': ('vector_unary', 'frexp_mant_f32'),
'V_CVT': ('vector_unary', 'cvt'),
'V_CVT_FLR': ('vector_unary', 'cvt'),
'V_CVT_RPI': ('vector_unary', 'cvt'),
'V_CVT_F32_UBYTE0': ('vector_unary', 'cvt'),
'V_CVT_F32_UBYTE1': ('vector_unary', 'cvt'),
'V_CVT_F32_UBYTE2': ('vector_unary', 'cvt'),
'V_CVT_F32_UBYTE3': ('vector_unary', 'cvt'),
# RDNA4 renamed conversions (same semantics as FLR/RPI):
'V_CVT_NEAREST': ('vector_unary', 'cvt'),
'V_CVT_FLOOR': ('vector_unary', 'cvt'),
# RDNA4 renamed bit-counting ops:
'V_CLZ_I32_U32': ('vector_unary', 'ffbh_u32'),
'V_CTZ_I32_B32': ('vector_unary', 'ffbl'),
'V_CLS_I32': ('vector_unary', 'cls_i32'),
# Pipeline / system (nop in simulation):
'V_PIPEFLUSH': ('true_nop', None),
# Relative addressing through M0.
'V_MOVRELD': ('vector_movrel', 'dst'),
'V_MOVRELS': ('vector_movrel', 'src'),
'V_MOVRELSD': ('nop', None),
'V_MOVRELSD2': ('nop', None),
'V_MOVRELSD2_B32': ('nop', None),
'V_MOVRELSD_2': ('nop', None),
'V_MOVRELSD_2_B32': ('nop', None),
'V_SWAP_REL': ('nop', None),
'V_PERMLANE64': ('vector_permlane64', None),
}
def _derive_vop1(name: str) -> InstructionSemantics | None:
"""Derive semantics for a VOP1 (Vector ALU One-operand) instruction."""
bf16_transcendentals = {
'V_RCP_BF16': 'rcp',
'V_SQRT_BF16': 'sqrt',
'V_RSQ_BF16': 'rsq',
'V_LOG_BF16': 'log2',
'V_EXP_BF16': 'exp2',
'V_SIN_BF16': 'sin',
'V_COS_BF16': 'cos',
'V_TANH_BF16': 'tanh',
}
if name in bf16_transcendentals:
return InstructionSemantics(
name, 'vector_unary', operation=bf16_transcendentals[name], data_type='bf16'
)
packed_fp8_bf8_conversions = {
'V_CVT_PK_F32_FP8': 'f32_fp8',
'V_CVT_PK_F32_BF8': 'f32_bf8',
'V_CVT_PK_F16_FP8': 'f16_fp8',
'V_CVT_PK_F16_BF8': 'f16_bf8',
}
if name in packed_fp8_bf8_conversions:
return InstructionSemantics(
name, 'vector_cvt_pk', operation=packed_fp8_bf8_conversions[name]
)
if name == 'V_CVT_OFF_F32_I4':
return InstructionSemantics(
name, 'vector_unary', operation='cvt_off_f32_i4', data_type='f32'
)
if name == 'V_CVT_F16_FP8':
return InstructionSemantics(
name, 'vector_unary', operation='cvt_f16_fp8', data_type='f16'
)
if name == 'V_CVT_F16_BF8':
return InstructionSemantics(
name, 'vector_unary', operation='cvt_f16_bf8', data_type='f16'
)
# Stem-first: check each table key against the name to handle compound
# suffixes like V_FREXP_EXP_I32_F32 → (V_FREXP_EXP_I32, F32).
stem, dtype, entry = None, None, None
for key, val in _VOP1_OP_MAP.items():
if name == key:
stem, dtype, entry = key, None, val
break
if name.startswith(key + '_'):
rest = name[len(key) + 1 :]
dt = _DTYPE_MAP.get(rest)
if dt is not None:
stem, dtype, entry = key, dt, val
break
if entry is None:
# Fallback to generic dtype stripping
stem, dtype = _split_dtype(name)
entry = _VOP1_OP_MAP.get(stem)
if entry is not None:
cls, op = entry
return InstructionSemantics(name, cls, operation=op, data_type=dtype)
return None
_VOP2_OP_MAP = {
'V_ADD': 'add',
'V_SUB': 'sub',
'V_SUBREV': 'rsub',
'V_MUL': 'mul',
'V_MUL_LEGACY': 'mul_legacy',
'V_MUL_I32_I24': None,
'V_MUL_U32_U24': None,
'V_MUL_HI_I32_I24': None,
'V_MUL_HI_U32_U24': None,
'V_AND': 'and',
'V_OR': 'or',
'V_XOR': 'xor',
'V_XNOR': 'xnor',
'V_LSHLREV': 'shl',
'V_LSHRREV': 'shr',
'V_ASHRREV': 'ashr',
'V_MIN': 'min',
'V_MAX': 'max',
# RDNA4 renamed min/max with IEEE 754 NaN semantics:
'V_MIN_NUM': 'min',
'V_MAX_NUM': 'max',
# RDNA4 renamed no-carry add/sub:
'V_ADD_NC': 'add',
'V_SUB_NC': 'sub',
'V_SUBREV_NC': 'rsub',
'V_FMAC': 'fmac',
'V_LDEXP': 'ldexp',
'V_BFM': 'bfm',
'V_CNDMASK': None, # special class
# RDNA4 V_MUL_DX9_ZERO variant (same as mul_legacy for zero*anything=0):
'V_MUL_DX9_ZERO': 'mul_legacy',
'V_MAC_LEGACY': 'fmac',
'V_FMAC_LEGACY': 'fmac',
'V_FMAC_DX9_ZERO': 'fmac',
}
def _derive_vop2(name: str) -> InstructionSemantics | None:
"""Derive semantics for a VOP2 (Vector ALU Two-operand) instruction."""
if name.startswith('V_CNDMASK_'):
_, dtype = _split_dtype(name)
return InstructionSemantics(name, 'vector_cndmask', data_type=dtype)
# RDNA carry-in variants: V_ADD_CO_CI_U32 = V_ADDC_CO_U32 equivalent.
# Check _CO_CI_ BEFORE the generic _CO_ pattern to avoid greedy match.
m = re.match(r'V_(ADD|SUB|SUBREV)_CO_CI_(\w+)', name)
if m:
ci_map = {'ADD': 'addc', 'SUB': 'subbc', 'SUBREV': 'subbrevco'}
return InstructionSemantics(
name,
'vector_add_co',
operation=ci_map[m.group(1)],
data_type=_DTYPE_MAP.get(m.group(2)),
)
# ADD_CO / SUB_CO / ADDC_CO etc. → vector_add_co
m = re.match(r'V_(ADD|SUB|SUBREV|ADDC|SUBB|SUBBREV)_CO_(\w+)', name)
if m:
op_raw, dt_raw = m.group(1), m.group(2)
op_map = {
'ADD': 'add',
'SUB': 'sub',
'SUBREV': 'rsub',
'ADDC': 'addc',
'SUBB': 'subbc',
'SUBBREV': 'subbrevco',
}
return InstructionSemantics(
name,
'vector_add_co',
operation=op_map[op_raw],
data_type=_DTYPE_MAP.get(dt_raw),
)
# FMAMK / FMAAK / MADMK / MADAK - FMA/MAD with inline constant
if name == 'V_FMAMK_F32':
return InstructionSemantics(name, 'vector_fmamk', data_type='f32')
if name == 'V_FMAAK_F32':
return InstructionSemantics(name, 'vector_fmaak', data_type='f32')
if name in ('V_MADMK_F16', 'V_FMAMK_F16'):
return InstructionSemantics(name, 'vector_fmamk', data_type='f16')
if name in ('V_MADAK_F16', 'V_FMAAK_F16'):
return InstructionSemantics(name, 'vector_fmaak', data_type='f16')
if name == 'V_MAC_F32':
return InstructionSemantics(
name, 'vector_binop', operation='fmac', data_type='f32'
)
if name == 'V_MAC_F16':
return InstructionSemantics(
name, 'vector_binop', operation='fmac', data_type='f16'
)
if name == 'V_MADMK_F32':
return InstructionSemantics(name, 'vector_fmamk', data_type='f32')
if name == 'V_MADAK_F32':
return InstructionSemantics(name, 'vector_fmaak', data_type='f32')
if name == 'V_MUL_LO_U16':
return InstructionSemantics(
name, 'vector_binop', operation='mul', data_type='u16'
)
# Packed FP16 FMA VOP2 form (the VOP3P form is functional).
if name == 'V_PK_FMAC_F16':
return InstructionSemantics(name, 'nop')
# DOT product instructions
if name == 'V_DOT2ACC_F32_F16':
return InstructionSemantics(
name, 'vector_dot', operation='dot2c', data_type='f32'
)
if name == 'V_DOT2C_F32_F16':
return InstructionSemantics(
name, 'vector_dot', operation='dot2c', data_type='f32'
)
if name == 'V_DOT2C_I32_I16':
return InstructionSemantics(
name, 'vector_dot', operation='dot2c', data_type='i32'
)
if name == 'V_DOT4C_I32_I8':
return InstructionSemantics(
name, 'vector_dot', operation='dot4c', data_type='i32'
)
if name == 'V_DOT8C_I32_I4':
return InstructionSemantics(
name, 'vector_dot', operation='dot8c', data_type='i32'
)
# 24-bit multiply variants (full name is the key)
_24bit = {
'V_MUL_I32_I24': ('mul', 'i24'),
'V_MUL_U32_U24': ('mul', 'u24'),
'V_MUL_HI_I32_I24': ('mulhi', 'i24'),
'V_MUL_HI_U32_U24': ('mulhi', 'u24'),
}
if name in _24bit:
op, dt = _24bit[name]
return InstructionSemantics(name, 'vector_binop', operation=op, data_type=dt)
# V_MUL_LO_U32 → mul, u32
if name == 'V_MUL_LO_U32':
return InstructionSemantics(
name, 'vector_binop', operation='mul', data_type='u32'
)
# V_MUL_HI_I32 / V_MUL_HI_U32
if name == 'V_MUL_HI_I32':
return InstructionSemantics(
name, 'vector_binop', operation='mulhi', data_type='i32'
)
if name == 'V_MUL_HI_U32':
return InstructionSemantics(
name, 'vector_binop', operation='mulhi', data_type='u32'
)
stem, dtype = _split_dtype(name)
op = _VOP2_OP_MAP.get(stem)
if op is not None and dtype is not None:
return InstructionSemantics(name, 'vector_binop', operation=op, data_type=dtype)
return None
def _derive_vopc(name: str) -> InstructionSemantics | None:
"""Derive semantics for a VOPC (Vector ALU Compare) instruction."""
# V_CMP[X]_CLASS_<dtype>
m = re.match(r'V_(CMPX?)_CLASS_(F32|F64|F16)$', name)
if m:
prefix, dt_raw = m.group(1), m.group(2)
cls = 'vector_cmpx_class' if prefix == 'CMPX' else 'vector_cmp_class'
return InstructionSemantics(name, cls, data_type=_DTYPE_MAP[dt_raw])
# V_CMP[X]_<op>_<dtype>
m = re.match(r'V_(CMPX?)_(\w+?)_(F32|F64|F16|I32|U32|I64|U64|I16|U16)$', name)
if m:
prefix, cmp_name, dt_raw = m.group(1), m.group(2), m.group(3)
op = _CMP_OP_MAP.get(cmp_name)
if op is not None:
cls = 'vector_cmpx' if prefix == 'CMPX' else 'vector_cmp'
return InstructionSemantics(
name, cls, operation=op, data_type=_DTYPE_MAP[dt_raw]
)
return None
_VOP3_TERNARY_MAP = {
'V_MAD': 'mad',
'V_MAD_LEGACY': 'mad',
'V_FMA': 'fma',
'V_FMA_LEGACY': 'fma',
'V_MIN3': 'min3',
'V_MAX3': 'max3',
'V_MED3': 'med3',
'V_BFE': None,
'V_BFI': 'bfi',
'V_ALIGNBIT': 'alignbit',
'V_ALIGNBYTE': 'alignbyte',
'V_ADD3': 'add3',
'V_LSHL_OR': 'lshl_or',
'V_AND_OR': 'and_or',
'V_OR3': 'or3',
'V_LSHL_ADD': 'lshl_add',
'V_ADD_LSHL': 'add_lshl',
'V_XAD': 'xad',
'V_PERM': 'perm',
}
def _derive_vop3(name: str) -> InstructionSemantics | None:
"""Derive semantics for a VOP3 (Vector ALU Three-operand) instruction."""
if name == 'V_READLANE_B32':
return InstructionSemantics(name, 'vector_readlane')
if name == 'V_WRITELANE_B32':
return InstructionSemantics(name, 'vector_writelane')
# BFE: signed vs unsigned
if name.startswith('V_BFE_'):
_, dtype = _split_dtype(name)
op = 'bfe_i' if dtype and dtype.startswith('i') else 'bfe_u'
return InstructionSemantics(
name, 'vector_ternary', operation=op, data_type=dtype
)
# Cube map instructions (VOP3-only)
_CUBE_MAP = {
'V_CUBEID_F32': 'cubeid',
'V_CUBESC_F32': 'cubesc',
'V_CUBETC_F32': 'cubetc',
'V_CUBEMA_F32': 'cubema',
}
if name in _CUBE_MAP:
return InstructionSemantics(
name, 'vector_ternary', operation=_CUBE_MAP[name], data_type='f32'
)
# Division helper instructions (VOP3-only)
for prefix in ('V_DIV_FIXUP_', 'V_DIV_FIXUP_LEGACY_'):
if name.startswith(prefix):
_, dtype = _split_dtype(name)
return InstructionSemantics(name, 'vector_div_fixup', data_type=dtype)
if name.startswith('V_DIV_SCALE_'):
_, dtype = _split_dtype(name)
return InstructionSemantics(name, 'vector_div_scale', data_type=dtype)
if name.startswith('V_DIV_FMAS_'):
_, dtype = _split_dtype(name)
return InstructionSemantics(name, 'vector_div_fmas', data_type=dtype)
# LERP
if name == 'V_LERP_U8':
return InstructionSemantics(
name, 'vector_ternary', operation='lerp_u8', data_type='u32'
)
# SAD (sum of absolute differences) - ternary: D = |S0 - S1| + S2
_SAD_MAP = {
'V_SAD_U8': 'sad_u8',
'V_SAD_HI_U8': 'sad_hi_u8',
'V_SAD_U16': 'sad_u16',
'V_SAD_U32': 'sad_u32',
'V_MSAD_U8': 'msad_u8',
}
if name in _SAD_MAP:
return InstructionSemantics(
name, 'vector_ternary', operation=_SAD_MAP[name], data_type='u32'
)
# MQSAD (complex, rarely used) → nop
if name in ('V_MQSAD_PK_U16_U8', 'V_MQSAD_U32_U8', 'V_QSAD_PK_U16_U8'):
return InstructionSemantics(name, 'nop')
# Masked bit count
if name == 'V_MBCNT_LO_U32_B32':
return InstructionSemantics(
name, 'vector_mbcnt', operation='lo', data_type='u32'
)
if name == 'V_MBCNT_HI_U32_B32':
return InstructionSemantics(
name, 'vector_mbcnt', operation='hi', data_type='u32'
)
# Mixed-precision multiply-add: D.u32 = S0.u16 * S1.u16 + S2.u32
if name == 'V_MAD_U32_U16':
return InstructionSemantics(name, 'vector_mad_32_16', data_type='u32')
if name == 'V_MAD_I32_I16':
return InstructionSemantics(name, 'vector_mad_32_16', data_type='i32')
# Widening multiply-add: D.u64 = S0.u32 * S1.u32 + S2.u64
if name in ('V_MAD_U64_U32', 'V_MAD_NC_U64_U32'):
return InstructionSemantics(name, 'vector_mad_64_32', data_type='u64')
if name in ('V_MAD_I64_I32', 'V_MAD_NC_I64_I32'):
return InstructionSemantics(name, 'vector_mad_64_32', data_type='i64')
# Pack/convert instructions
if name == 'V_CVT_PK_U8_F32':
return InstructionSemantics(name, 'vector_cvt_pk_u8_f32')
if name == 'V_CVT_PKACCUM_U8_F32':
return InstructionSemantics(name, 'vector_cvt_pk_u8_f32')
if name == 'V_CVT_PKNORM_I16_F32':
return InstructionSemantics(
name, 'vector_cvt_pknorm', operation='i16', data_type='f32'
)
if name == 'V_CVT_PKNORM_U16_F32':
return InstructionSemantics(
name, 'vector_cvt_pknorm', operation='u16', data_type='f32'
)
if name == 'V_CVT_PKRTZ_F16_F32':
return InstructionSemantics(name, 'vector_cvt_pkrtz_f16_f32')
if name == 'V_CVT_PK_U16_U32':
return InstructionSemantics(name, 'vector_cvt_pk', operation='u16_u32')
if name == 'V_CVT_PK_I16_I32':
return InstructionSemantics(name, 'vector_cvt_pk', operation='i16_i32')
# Trig preop → nop (internal microcode helper)
if name == 'V_TRIG_PREOP_F64':
return InstructionSemantics(name, 'nop')
# ── CDNA4 / RDNA4 new conversions ─────────────────────────────────
if name == 'V_CVT_F32_BF16':
return InstructionSemantics(
name, 'vector_unary', operation='cvt_f32_bf16', data_type='f32'
)
if name == 'V_CVT_PK_F16_F32':
return InstructionSemantics(name, 'vector_cvt_pk_f16_f32')
if name == 'V_CVT_PK_BF16_F32':
return InstructionSemantics(name, 'vector_cvt_pk_bf16_f32')
if name == 'V_CVT_SR_F16_F32':
return InstructionSemantics(name, 'vector_cvt_sr_f16_f32')
if name == 'V_CVT_SR_BF16_F32':
return InstructionSemantics(name, 'vector_cvt_sr_bf16_f32')
if name == 'V_CVT_SR_PK_F16_F32':
return InstructionSemantics(name, 'vector_cvt_sr_pk_f16_f32')
if name == 'V_CVT_SR_PK_BF16_F32':
return InstructionSemantics(name, 'vector_cvt_sr_pk_bf16_f32')
if name == 'V_DOT2C_F32_BF16':
return InstructionSemantics(name, 'vector_dot2c_bf16')
if name == 'V_MINIMUM3_F32':
return InstructionSemantics(
name, 'vector_ternary', operation='minimum3', data_type='f32'
)
if name == 'V_MAXIMUM3_F32':
return InstructionSemantics(
name, 'vector_ternary', operation='maximum3', data_type='f32'
)
if name in ('V_BITOP3_B32', 'V_BITOP3_B16'):
dt = 'b16' if name.endswith('B16') else 'b32'
return InstructionSemantics(name, 'vector_bitop3', data_type=dt)
if name == 'V_ASHR_PK_I8_I32':
return InstructionSemantics(
name, 'vector_ternary', operation='ashr_pk_i8_i32', data_type='b32'
)
if name == 'V_ASHR_PK_U8_I32':
return InstructionSemantics(
name, 'vector_ternary', operation='ashr_pk_u8_i32', data_type='b32'
)
# V_PK_MINIMUM3/MAXIMUM3 are VOP3P, handled in _derive_vop3p.
# ── Additional VOP3 ternary instructions ───────────────────────────
if name == 'V_XOR3_B32':
return InstructionSemantics(
name, 'vector_ternary', operation='xor3', data_type='b32'
)
if name == 'V_FMA_DX9_ZERO_F32':
return InstructionSemantics(
name, 'vector_ternary', operation='fma', data_type='f32'
)
if name == 'V_MULLIT_F32':
return InstructionSemantics(name, 'nop')
# MAXMIN/MINMAX/MINIMUMMAXIMUM/MAXIMUMMINIMUM variants (all types)
_CLAMP_TERNARY = {
'V_MAXMIN': 'maxmin',
'V_MINMAX': 'minmax',
'V_MINIMUMMAXIMUM': 'minimummaximum',
'V_MAXIMUMMINIMUM': 'maximumminimum',
'V_MAXMIN_NUM': 'maxmin_num',
'V_MINMAX_NUM': 'minmax_num',
}
for prefix, op in _CLAMP_TERNARY.items():
for suffix in ('_B32', '_U32', '_I32', '_F32', '_F16', '_F64'):
if name == f'{prefix}{suffix}':
_, dt = _split_dtype(name)
return InstructionSemantics(
name, 'vector_ternary', operation=op, data_type=dt
)
for prefix, op in (('V_ADD_MAX', 'add_max'), ('V_ADD_MIN', 'add_min')):
for suffix in ('_I32', '_U32'):
if name == f'{prefix}{suffix}':
_, dt = _split_dtype(name)
return InstructionSemantics(
name, 'vector_ternary', operation=op, data_type=dt
)
# MIN3/MAX3/MED3 F16 and NUM variants
if name == 'V_MINIMUM3_F16':
return InstructionSemantics(
name, 'vector_ternary', operation='minimum3', data_type='f16'
)
if name == 'V_MAXIMUM3_F16':
return InstructionSemantics(
name, 'vector_ternary', operation='maximum3', data_type='f16'
)
for op in ('MIN3', 'MAX3', 'MED3'):
for suffix in ('_F32', '_F16'):
if name == f'V_{op}_NUM{suffix}':
_, dt = _split_dtype(name)
return InstructionSemantics(
name, 'vector_ternary', operation=op.lower(), data_type=dt
)
# IEEE 754-2019 binary min/max (VOP3 forms)
if name.startswith('V_MINIMUM_') or name.startswith('V_MAXIMUM_'):
_, dt = _split_dtype(name)
op = 'minimum' if 'MINIMUM' in name else 'maximum'
return InstructionSemantics(name, 'vector_binop', operation=op, data_type=dt)
# Dot product with F16/BF16 output
if name == 'V_DOT2_F16_F16':
return InstructionSemantics(name, 'dot2_f16_f16')
if name == 'V_DOT2_BF16_BF16':
return InstructionSemantics(name, 'dot2_bf16_bf16')
# Pack/convert variants
if name == 'V_CVT_PK_I16_F32':
return InstructionSemantics(name, 'vector_cvt_pk', operation='i16_f32')
if name == 'V_CVT_PK_U16_F32':
return InstructionSemantics(name, 'vector_cvt_pk', operation='u16_f32')
if name == 'V_CVT_PK_RTZ_F16_F32':
return InstructionSemantics(name, 'vector_cvt_pkrtz_f16_f32')
for norm_suffix in ('_I16_F16', '_U16_F16', '_I16_F32', '_U16_F32'):
if name == f'V_CVT_PK_NORM{norm_suffix}':
_, out_ty, in_ty = norm_suffix.split('_')
return InstructionSemantics(
name,
'vector_cvt_pknorm',
operation=out_ty.lower(),
data_type=in_ty.lower(),
)
# 64-bit multiply-add with carry
if name == 'V_MAD_CO_U64_U32':
return InstructionSemantics(name, 'vector_mad_64_32', data_type='u64')
if name == 'V_MAD_CO_I64_I32':
return InstructionSemantics(name, 'vector_mad_64_32', data_type='i64')
if name == 'V_PERMLANE16_B32':
return InstructionSemantics(name, 'vector_permlane16', operation='imm')
if name == 'V_PERMLANEX16_B32':
return InstructionSemantics(name, 'vector_permlanex16', operation='imm')
if name == 'V_PERMLANE16_VAR_B32':
return InstructionSemantics(name, 'vector_permlane16', operation='var')
if name == 'V_PERMLANEX16_VAR_B32':
return InstructionSemantics(name, 'vector_permlanex16', operation='var')
if name == 'V_PERMLANE_BCAST_B32':
return InstructionSemantics(name, 'vector_permlane_family', operation='bcast')
if name == 'V_PERMLANE_DOWN_B32':
return InstructionSemantics(name, 'vector_permlane_family', operation='down')
if name == 'V_PERMLANE_UP_B32':
return InstructionSemantics(name, 'vector_permlane_family', operation='up')
if name == 'V_PERMLANE_XOR_B32':
return InstructionSemantics(name, 'vector_permlane_family', operation='xor')
if name == 'V_PERMLANE_IDX_GEN_B32':
return InstructionSemantics(name, 'vector_permlane_idx_gen')
if name == 'V_PACK_B32_F16':
sem = InstructionSemantics(name, 'vector_pack_b32_f16')
sem.semantic_class = 'vector_pack_b32_f16'
return sem
packed_fp8_bf8_outputs = {
'V_CVT_PK_FP8_F32': 'fp8_f32',
'V_CVT_PK_BF8_F32': 'bf8_f32',
'V_CVT_PK_FP8_F16': 'fp8_f16',
'V_CVT_PK_BF8_F16': 'bf8_f16',
}
if name in packed_fp8_bf8_outputs:
return InstructionSemantics(
name, 'vector_cvt_pk', operation=packed_fp8_bf8_outputs[name]
)
if name == 'V_CVT_SR_FP8_F16':
return InstructionSemantics(name, 'vector_cvt_sr_fp8_f16')
if name == 'V_CVT_SR_BF8_F16':
return InstructionSemantics(name, 'vector_cvt_sr_bf8_f16')
scaled_unpack_conversions = {
'V_CVT_SCALE_PK8_F16_FP4': 'unpack_pk8_f16_fp4',
'V_CVT_SCALE_PK8_BF16_FP4': 'unpack_pk8_bf16_fp4',
'V_CVT_SCALE_PK8_F32_FP4': 'unpack_pk8_f32_fp4',
'V_CVT_SCALE_PK8_F16_FP8': 'unpack_pk8_f16_fp8',
'V_CVT_SCALE_PK8_BF16_FP8': 'unpack_pk8_bf16_fp8',
'V_CVT_SCALE_PK8_F32_FP8': 'unpack_pk8_f32_fp8',
'V_CVT_SCALE_PK8_F16_BF8': 'unpack_pk8_f16_bf8',
'V_CVT_SCALE_PK8_BF16_BF8': 'unpack_pk8_bf16_bf8',
'V_CVT_SCALE_PK8_F32_BF8': 'unpack_pk8_f32_bf8',
'V_CVT_SCALE_PK16_F16_FP6': 'unpack_pk16_f16_fp6',
'V_CVT_SCALE_PK16_BF16_FP6': 'unpack_pk16_bf16_fp6',
'V_CVT_SCALE_PK16_F32_FP6': 'unpack_pk16_f32_fp6',
'V_CVT_SCALE_PK16_F16_BF6': 'unpack_pk16_f16_bf6',
'V_CVT_SCALE_PK16_BF16_BF6': 'unpack_pk16_bf16_bf6',
'V_CVT_SCALE_PK16_F32_BF6': 'unpack_pk16_f32_bf6',
}
if name in scaled_unpack_conversions:
return InstructionSemantics(
name, 'vector_cvt_scale', operation=scaled_unpack_conversions[name]
)
scaled_pack_conversions = {
'V_CVT_SCALEF32_PK8_FP4_F32': 'pack_pk8_fp4_f32',
'V_CVT_SCALEF32_PK8_FP4_F16': 'pack_pk8_fp4_f16',
'V_CVT_SCALEF32_PK8_FP4_BF16': 'pack_pk8_fp4_bf16',
'V_CVT_SCALEF32_PK8_FP8_F32': 'pack_pk8_fp8_f32',
'V_CVT_SCALEF32_PK8_FP8_F16': 'pack_pk8_fp8_f16',
'V_CVT_SCALEF32_PK8_FP8_BF16': 'pack_pk8_fp8_bf16',
'V_CVT_SCALEF32_PK8_BF8_F32': 'pack_pk8_bf8_f32',
'V_CVT_SCALEF32_PK8_BF8_F16': 'pack_pk8_bf8_f16',
'V_CVT_SCALEF32_PK8_BF8_BF16': 'pack_pk8_bf8_bf16',
'V_CVT_SCALEF32_PK16_FP6_F32': 'pack_pk16_fp6_f32',
'V_CVT_SCALEF32_PK16_FP6_F16': 'pack_pk16_fp6_f16',
'V_CVT_SCALEF32_PK16_FP6_BF16': 'pack_pk16_fp6_bf16',
'V_CVT_SCALEF32_PK16_BF6_F32': 'pack_pk16_bf6_f32',
'V_CVT_SCALEF32_PK16_BF6_F16': 'pack_pk16_bf6_f16',
'V_CVT_SCALEF32_PK16_BF6_BF16': 'pack_pk16_bf6_bf16',
}
if name in scaled_pack_conversions:
return InstructionSemantics(
name, 'vector_cvt_scale', operation=scaled_pack_conversions[name]
)
scaled_sr_pack_conversions = {
'V_CVT_SCALEF32_SR_PK8_FP4_F32': 'sr_pack_pk8_fp4_f32',
'V_CVT_SCALEF32_SR_PK8_FP4_F16': 'sr_pack_pk8_fp4_f16',
'V_CVT_SCALEF32_SR_PK8_FP4_BF16': 'sr_pack_pk8_fp4_bf16',
'V_CVT_SCALEF32_SR_PK8_FP8_F32': 'sr_pack_pk8_fp8_f32',
'V_CVT_SCALEF32_SR_PK8_FP8_F16': 'sr_pack_pk8_fp8_f16',
'V_CVT_SCALEF32_SR_PK8_FP8_BF16': 'sr_pack_pk8_fp8_bf16',
'V_CVT_SCALEF32_SR_PK8_BF8_F32': 'sr_pack_pk8_bf8_f32',
'V_CVT_SCALEF32_SR_PK8_BF8_F16': 'sr_pack_pk8_bf8_f16',
'V_CVT_SCALEF32_SR_PK8_BF8_BF16': 'sr_pack_pk8_bf8_bf16',
'V_CVT_SCALEF32_SR_PK16_FP6_F32': 'sr_pack_pk16_fp6_f32',
'V_CVT_SCALEF32_SR_PK16_FP6_F16': 'sr_pack_pk16_fp6_f16',
'V_CVT_SCALEF32_SR_PK16_FP6_BF16': 'sr_pack_pk16_fp6_bf16',
'V_CVT_SCALEF32_SR_PK16_BF6_F32': 'sr_pack_pk16_bf6_f32',
'V_CVT_SCALEF32_SR_PK16_BF6_F16': 'sr_pack_pk16_bf6_f16',
'V_CVT_SCALEF32_SR_PK16_BF6_BF16': 'sr_pack_pk16_bf6_bf16',
}
if name in scaled_sr_pack_conversions:
return InstructionSemantics(
name, 'vector_cvt_scale', operation=scaled_sr_pack_conversions[name]
)
# FP8/BF8 pack/convert (non-scaled, CDNA3/4/RDNA4)
_CVT_FP8_MAP = {
'V_CVT_SR_FP8_F32': 'sr_fp8_f32',
'V_CVT_SR_BF8_F32': 'sr_bf8_f32',
}
if name in _CVT_FP8_MAP:
return InstructionSemantics(name, 'cvt_fp8', operation=_CVT_FP8_MAP[name])
if name in ('V_CVT_PKNORM_I16_F16', 'V_CVT_PKNORM_U16_F16'):
return InstructionSemantics(name, 'nop')
# Scaled FP8/BF8/FP6/FP4 conversions not covered by the exact gfx1250
# mappings above still need per-encoding validation.
if 'CVT_SCALEF32' in name.upper():
suffix = name.upper()
prefix = 'V_CVT_SCALEF32_'
if suffix.startswith(prefix):
op = suffix[len(prefix) :].lower()
else:
op = suffix.split('CVT_SCALEF32_', 1)[1].lower()
return InstructionSemantics(name, 'cvt_scalef32', operation=op)
# V_PK_FMAC_F16 VOP2 form — the VOP3P form is handled via _VOP3P_PK16_MAP.
if name == 'V_PK_FMAC_F16':
return InstructionSemantics(name, 'nop')
# Legacy interpolation (CDNA1, RDNA1/2) — niche, not simulated
if name.startswith('V_INTERP_P1') or name.startswith('V_INTERP_P2'):
return InstructionSemantics(name, 'nop')
# V_DOT2ACC VOP2 form
if name == 'V_DOT2ACC_F32_F16':
return InstructionSemantics(name, 'nop')
# 64-bit shift/add (VOP3-only)
if name == 'V_LSHL_ADD_U64':
return InstructionSemantics(
name, 'vector_ternary', operation='lshl_add', data_type='u64'
)
if name == 'V_LSHLREV_B64':
return InstructionSemantics(
name, 'vector_binop', operation='shl', data_type='b64'
)
if name == 'V_LSHRREV_B64':
return InstructionSemantics(
name, 'vector_binop', operation='shr', data_type='b64'
)
if name == 'V_ASHRREV_I64':
return InstructionSemantics(
name, 'vector_binop', operation='ashr', data_type='i64'
)
stem, dtype = _split_dtype(name)
op = _VOP3_TERNARY_MAP.get(stem)
if op is not None and dtype is not None:
# Integer-logical ternary ops use u32 codegen path even when
# the mnemonic suffix is B32.
_TERNARY_U32_OPS = frozenset(
{
'add3',
'lshl_or',
'and_or',
'or3',
'lshl_add',
'add_lshl',
'xad',
'perm',
}
)
if dtype == 'b32' and op in _TERNARY_U32_OPS:
dtype = 'u32'
return InstructionSemantics(
name, 'vector_ternary', operation=op, data_type=dtype
)
# MAD with 24-bit types: V_MAD_I32_I24, V_MAD_U32_U24
if name == 'V_MAD_I32_I24':
return InstructionSemantics(
name, 'vector_ternary', operation='mad', data_type='i24'
)
if name == 'V_MAD_U32_U24':
return InstructionSemantics(
name, 'vector_ternary', operation='mad', data_type='u24'
)
return None
# Packed 16-bit operation map: stem → (semantic_class, operation, data_type)
_VOP3P_PK16_MAP = {
'V_PK_ADD_I16': ('pk_binop', 'add', 'i16'),
'V_PK_SUB_I16': ('pk_binop', 'sub', 'i16'),
'V_PK_MUL_LO_U16': ('pk_binop', 'mul', 'u16'),
'V_PK_ADD_U16': ('pk_binop', 'add', 'u16'),
'V_PK_SUB_U16': ('pk_binop', 'sub', 'u16'),
'V_PK_MAX_I16': ('pk_binop', 'max', 'i16'),
'V_PK_MIN_I16': ('pk_binop', 'min', 'i16'),
'V_PK_MAX_U16': ('pk_binop', 'max', 'u16'),
'V_PK_MIN_U16': ('pk_binop', 'min', 'u16'),
'V_PK_LSHLREV_B16': ('pk_binop', 'shl', 'u16'),
'V_PK_LSHRREV_B16': ('pk_binop', 'shr', 'u16'),
'V_PK_ASHRREV_I16': ('pk_binop', 'ashr', 'i16'),
'V_PK_MAD_I16': ('pk_ternary', 'mad', 'i16'),
'V_PK_MAD_U16': ('pk_ternary', 'mad', 'u16'),
'V_PK_FMA_F16': ('pk_ternary', 'fma', 'f16'),
'V_PK_ADD_F16': ('pk_binop', 'add', 'f16'),
'V_PK_MUL_F16': ('pk_binop', 'mul', 'f16'),
'V_PK_MIN_F16': ('pk_binop', 'min', 'f16'),
'V_PK_MAX_F16': ('pk_binop', 'max', 'f16'),
'V_PK_MIN_NUM_F16': ('pk_binop', 'min', 'f16'),
'V_PK_MAX_NUM_F16': ('pk_binop', 'max', 'f16'),
'V_PK_MINIMUM_F16': ('pk_binop', 'min', 'f16'),
'V_PK_MAXIMUM_F16': ('pk_binop', 'max', 'f16'),
'V_PK_FMAC_F16': ('pk_ternary', 'fmac', 'f16'),
'V_PK_FMA_BF16': ('pk_ternary', 'fma', 'bf16'),
'V_PK_ADD_BF16': ('pk_binop', 'add', 'bf16'),
'V_PK_MUL_BF16': ('pk_binop', 'mul', 'bf16'),
'V_PK_MIN_NUM_BF16': ('pk_binop', 'min', 'bf16'),
'V_PK_MAX_NUM_BF16': ('pk_binop', 'max', 'bf16'),
'V_PK_MAX3_I16': ('pk_ternary', 'max3', 'i16'),
'V_PK_MAX3_U16': ('pk_ternary', 'max3', 'u16'),
'V_PK_MIN3_I16': ('pk_ternary', 'min3', 'i16'),
'V_PK_MIN3_U16': ('pk_ternary', 'min3', 'u16'),
'V_PK_MIN3_NUM_F16': ('pk_ternary', 'min3', 'f16'),
'V_PK_MAX3_NUM_F16': ('pk_ternary', 'max3', 'f16'),
}
def _derive_vop3p(name: str) -> InstructionSemantics | None:
"""Derive semantics for a VOP3P (Vector Packed/Matrix) instruction."""
# Packed 16-bit ops
pk16 = _VOP3P_PK16_MAP.get(name)
if pk16 is not None:
cls, op, dt = pk16
return InstructionSemantics(name, cls, operation=op, data_type=dt)
# DOT2ACC with F32 output and F16 input
if name == 'V_DOT2ACC_F32_F16':
return InstructionSemantics(name, 'dot2_f32_f16')
# Packed F32 ops (VGPR pairs)
if name == 'V_PK_FMA_F32':
return InstructionSemantics(
name, 'pk_ternary_f32', operation='fma', data_type='f32'
)
if name == 'V_PK_MUL_F32':
return InstructionSemantics(
name, 'pk_binop_f32', operation='mul', data_type='f32'
)
if name == 'V_PK_ADD_F32':
return InstructionSemantics(
name, 'pk_binop_f32', operation='add', data_type='f32'
)
if name == 'V_PK_MOV_B32':
return InstructionSemantics(name, 'pk_mov_b32')
# Packed min3/max3 (CDNA4 / RDNA4)
if name in ('V_PK_MINIMUM3_F16', 'V_PK_MAXIMUM3_F16'):
op = 'minimum3' if 'MINIMUM' in name else 'maximum3'
return InstructionSemantics(name, 'pk_ternary', operation=op, data_type='f16')
# Mixed-precision MAD_MIX
if name == 'V_MAD_MIX_F32':
return InstructionSemantics(name, 'mad_mix_f32')
if name == 'V_MAD_MIXLO_F16':
return InstructionSemantics(name, 'mad_mixlo_f16')
if name == 'V_MAD_MIXHI_F16':
return InstructionSemantics(name, 'mad_mixhi_f16')
# Dot products
if name == 'V_DOT2_F32_F16':
return InstructionSemantics(name, 'dot2_f32_f16')
if name == 'V_DOT2_I32_I16':
return InstructionSemantics(name, 'dot2_i32_i16')
if name == 'V_DOT2_U32_U16':
return InstructionSemantics(name, 'dot2_u32_u16')
if name == 'V_DOT4_I32_I8':
return InstructionSemantics(name, 'dot4_i32_i8')
if name == 'V_DOT4_U32_U8':
return InstructionSemantics(name, 'dot4_u32_u8')
# FP8 dot products (RDNA4)
if name in (
'V_DOT4_F32_FP8_FP8',
'V_DOT4_F32_FP8_BF8',
'V_DOT4_F32_BF8_FP8',
'V_DOT4_F32_BF8_BF8',
):
return InstructionSemantics(name, 'dot4_f32_fp8')
if name == 'V_DOT8_I32_I4':
return InstructionSemantics(name, 'dot8_i32_i4')
if name == 'V_DOT8_U32_U4':
return InstructionSemantics(name, 'dot8_u32_u4')
# ACCVGPR read/write
if name == 'V_ACCVGPR_READ':
return InstructionSemantics(name, 'accvgpr_read')
if name == 'V_ACCVGPR_WRITE':
return InstructionSemantics(name, 'accvgpr_write')
# Additional dot product variants (RDNA3/4 naming). BF16 uses the same dot2
# structure as F16 but a different widening (8-bit exponent, no denormal
# renormalization), so it gets its own class rather than reusing f32_f16.
if name == 'V_DOT2_F32_BF16':
return InstructionSemantics(name, 'dot2_f32_bf16')
if name == 'V_DOT4_I32_IU8':
return InstructionSemantics(name, 'dot4_i32_iu8')
if name == 'V_DOT8_I32_IU4':
return InstructionSemantics(name, 'dot8_i32_iu4')
# FMA_MIX variants (RDNA3/4 renamed from MAD_MIX)
if name == 'V_FMA_MIX_F32':
return InstructionSemantics(name, 'mad_mix_f32')
if name == 'V_FMA_MIXLO_F16':
return InstructionSemantics(name, 'mad_mixlo_f16')
if name == 'V_FMA_MIXHI_F16':
return InstructionSemantics(name, 'mad_mixhi_f16')
if name == 'V_FMA_MIX_F32_BF16':
return InstructionSemantics(name, 'mad_mix_f32_bf16')
if name == 'V_FMA_MIXLO_BF16':
return InstructionSemantics(name, 'mad_mixlo_bf16')
if name == 'V_FMA_MIXHI_BF16':
return InstructionSemantics(name, 'mad_mixhi_bf16')
# MFMA / SMFMAC - all map to 'mfma' semantic class
if name.startswith('V_MFMA_') or name.startswith('V_SMFMAC_'):
return InstructionSemantics(name, 'mfma')
# WMMA (Wave Matrix Multiply-Accumulate) — RDNA3/3.5/4
import re
m = re.match(
r'V_(?:S?WMMA[C]?)_(F32|F16|BF16|BF16F32|I32|FP8|BF8)_'
r'(\d+)X(\d+)X(\d+)_?(F32|F16|BF16|IU8|IU4|FP8|BF8'
r'|FP8_FP8|FP8_BF8|BF8_FP8|BF8_BF8'
r'|F16_FP8|F16_BF8|BF16_FP8|BF16_BF8|F8F6F4|F4)?$',
name,
)
if m:
return InstructionSemantics(
name, 'mfma'
) # Reuse MFMA semantic class — same matrix pattern
# SWMMAC variants
if name.startswith('V_SWMMAC_'):
return InstructionSemantics(name, 'mfma')
return None
_SMEM_DWORD_MAP = {
'DWORD': 1,
'DWORDX2': 2,
'DWORDX4': 4,
'DWORDX8': 8,
'DWORDX16': 16,
# RDNA4 (GFX12) byte-width naming:
'B32': 1,
'B64': 2,
'B96': 3,
'B128': 4,
'B256': 8,
'B512': 16,
}
_SMEM_NARROW_LOAD_MAP = {
'I8': (1, True),
'U8': (1, False),
'I16': (2, True),
'U16': (2, False),
}
def _derive_smem(name: str) -> InstructionSemantics | None:
"""Derive semantics for an SMEM (Scalar Memory) instruction."""
upper = name.upper()
if upper in (
'S_PREFETCH_INST',
'S_PREFETCH_INST_PC_REL',
'S_PREFETCH_DATA',
'S_PREFETCH_DATA_PC_REL',
'S_BUFFER_PREFETCH_DATA',
'S_ATC_PROBE',
'S_ATC_PROBE_BUFFER',
):
return InstructionSemantics(name, 'true_nop')
# Cache invalidation / writeback instructions.
if upper in ('S_DCACHE_INV', 'S_DCACHE_INV_VOL'):
return InstructionSemantics(name, 'dcache_inv')
if upper in ('S_DCACHE_WB', 'S_DCACHE_WB_VOL'):
return InstructionSemantics(name, 'dcache_wb')
if upper == 'S_GL1_INV':
return InstructionSemantics(name, 'gl1_inv')
if upper in ('S_MEMTIME', 'S_MEMREALTIME'):
return InstructionSemantics(name, 'smem_time')
# Remaining scalar cache / special instructions.
if 'DCACHE' in upper or upper in ('S_ATC_PROBE', 'S_ATC_PROBE_BUFFER'):
return InstructionSemantics(name, 'nop')
# S_ATOMIC_* are scalar atomics — not currently simulated.
if '_ATOMIC_' in upper:
return InstructionSemantics(name, 'nop')
is_store = '_STORE_' in upper or '_SCRATCH_STORE_' in upper
if not is_store:
for suffix, (elem_size, sign_extend) in _SMEM_NARROW_LOAD_MAP.items():
if upper.endswith(f'_{suffix}'):
return InstructionSemantics(
name,
'smem_load',
elem_size=elem_size,
num_elems=1,
sign_extend=sign_extend,
)
for suffix, ndw in _SMEM_DWORD_MAP.items():
if upper.endswith(suffix):
cls = 'smem_store' if is_store else 'smem_load'
return InstructionSemantics(name, cls, num_elems=ndw)
if upper == 'BUFFER_WBL2':
return InstructionSemantics(name, 'gl2_wb')
if upper in (
'BUFFER_INV',
'BUFFER_GL0_INV',
'BUFFER_GL1_INV',
'S_BUFFER_GL0_INV',
'S_BUFFER_GL1_INV',
):
return InstructionSemantics(name, 'gl1_inv')
return InstructionSemantics(name, 'nop')
_FLAT_DATA_MAP: dict[str, tuple[int, int, bool]] = {
# CDNA / RDNA1-3 suffixes (DWORD-based naming).
'UBYTE': (1, 1, False),
'SBYTE': (1, 1, True),
'USHORT': (2, 1, False),
'SSHORT': (2, 1, True),
'SHORT': (2, 1, False),
'BYTE': (1, 1, False),
'BYTE_D16_HI': (1, 1, False),
'SHORT_D16_HI': (2, 1, False),
'DWORD': (4, 1, False),
'DWORDX2': (4, 2, False),
'DWORDX3': (4, 3, False),
'DWORDX4': (4, 4, False),
'UBYTE_D16': (1, 1, False),
'UBYTE_D16_HI': (1, 1, False),
'SBYTE_D16': (1, 1, True),
'SBYTE_D16_HI': (1, 1, True),
'SHORT_D16': (2, 1, False),
# RDNA4 suffixes (byte-width naming: B32/B64/B96/B128 instead of
# DWORD/DWORDX2/DWORDX3/DWORDX4, and U8/I8/U16/I16 instead of
# UBYTE/SBYTE/USHORT/SSHORT).
'B32': (4, 1, False),
'B64': (4, 2, False),
'B96': (4, 3, False),
'B128': (4, 4, False),
'U8': (1, 1, False),
'I8': (1, 1, True),
'U16': (2, 1, False),
'I16': (2, 1, True),
'B8': (1, 1, False),
'B16': (2, 1, False),
'D16_U8': (1, 1, False),
'D16_I8': (1, 1, True),
'D16_B16': (2, 1, False),
'D16_HI_U8': (1, 1, False),
'D16_HI_I8': (1, 1, True),
'D16_HI_B16': (2, 1, False),
'D16_HI_B8': (1, 1, False),
}
_TRANSPOSE_LOAD_MAP: dict[str, tuple[str, int, int, int]] = {
# suffix -> (semantic suffix, elem_size, num_elems, transpose kind)
# elem_size/num_elems describe the raw VGPR result size in dwords.
'B64_TR_B4': ('b4', 4, 2, 1),
'B96_TR_B6': ('b6', 4, 3, 2),
'B64_TR_B8': ('b8', 4, 2, 3),
'B64_TR_B16': ('b16', 4, 2, 4),
'TR4_B64': ('b4', 4, 2, 1),
'TR6_B96': ('b6', 4, 3, 2),
'TR8_B64': ('b8', 4, 2, 3),
'TR16_B128': ('b16', 4, 4, 4),
'TR_B64': ('b8', 4, 2, 3),
'TR_B128': ('b16', 4, 4, 4),
}
def _derive_transpose_load_info(upper: str) -> tuple[str, int, int, int] | None:
for suffix, info in _TRANSPOSE_LOAD_MAP.items():
if upper.endswith(suffix):
return info
return None
_FLAT_ATOMIC_OPS: dict[str, tuple[str, int]] = {
# op_suffix -> (operation, data_dwords for store_data)
# Integer atomics.
'SWAP': ('swap', 1),
'CMPSWAP': ('cmpswap', 2), # src + cmp
'FCMPSWAP': ('cmpswap', 2), # FP compare-and-swap
'ADD': ('add', 1),
'SUB': ('sub', 1),
'SMIN': ('smin', 1),
'UMIN': ('umin', 1),
'SMAX': ('smax', 1),
'UMAX': ('umax', 1),
'AND': ('and', 1),
'OR': ('or', 1),
'XOR': ('xor', 1),
'INC': ('inc', 1),
'DEC': ('dec', 1),
'CSUB': ('sub', 1),
'SUB_CLAMP': ('sub', 1),
# Floating-point atomics.
'ADD_F32': ('fadd', 1),
'ADD_F64': ('fadd', 2),
'MIN_F64': ('fmin', 2),
'MAX_F64': ('fmax', 2),
'FMIN': ('fmin', 1),
'FMAX': ('fmax', 1),
'MIN_F32': ('fmin', 1),
'MAX_F32': ('fmax', 1),
'MIN_F64': ('fmin', 2),
'MAX_F64': ('fmax', 2),
'MIN_NUM_F32': ('fmin', 1),
'MAX_NUM_F32': ('fmax', 1),
'MIN_NUM_F64': ('fmin', 2),
'MAX_NUM_F64': ('fmax', 2),
'COND_SUB': ('sub', 1),
'ORDERED_ADD': ('add', 2),
# RDNA3+ typed MIN/MAX (suffix stripped from the full instruction name).
'MIN_I32': ('smin', 1),
'MIN_U32': ('umin', 1),
'MAX_I32': ('smax', 1),
'MAX_U32': ('umax', 1),
'MIN_I64': ('smin', 2),
'MIN_U64': ('umin', 2),
'MAX_I64': ('smax', 2),
'MAX_U64': ('umax', 2),
# Packed FP atomics (treated as 32-bit fadd for now).
'PK_ADD_F16': ('fadd', 1),
'PK_ADD_BF16': ('fadd', 1),
}
_ATOMIC_TYPE_SUFFIXES = (
'_B32',
'_U32',
'_I32',
'_F32',
'_B64',
'_U64',
'_I64',
'_F64',
)
def _derive_flat_atomic_info(suffix: str, is_x2: bool) -> tuple[str, int, int] | None:
"""Return operation, memory element bytes, and source-data dwords.
Compare-swap carries two memory elements in its source operand payload.
Keep that payload width separate from the memory element width so B32
compare-swap remains a 4-byte memory operation, while B64 compare-swap
copies four source dwords.
"""
original_suffix = suffix
type_suffix = ''
info = _FLAT_ATOMIC_OPS.get(suffix)
if not info:
for tsuf in _ATOMIC_TYPE_SUFFIXES:
if suffix.endswith(tsuf):
info = _FLAT_ATOMIC_OPS.get(suffix[: len(suffix) - len(tsuf)])
type_suffix = tsuf
break
if not info:
return None
op, data_dw = info
is_64bit = (
is_x2
or '64' in type_suffix
or original_suffix.endswith(('_B64', '_U64', '_I64', '_F64'))
)
elem_size = 8 if is_64bit else 4
if op in ('cmpswap', 'mskor'):
data_dw_actual = 2 * (elem_size // 4)
elif data_dw == 1:
data_dw_actual = elem_size // 4
else:
data_dw_actual = data_dw
return op, elem_size, data_dw_actual
def _derive_flat(name: str) -> InstructionSemantics | None:
"""Derive semantics for a FLAT (Flat/Global/Scratch memory) instruction."""
upper = name.upper()
if upper == 'GLOBAL_INV':
return InstructionSemantics(name, 'gl1_inv')
if upper == 'GLOBAL_WB':
return InstructionSemantics(name, 'true_nop')
if upper == 'GLOBAL_WBINV':
return InstructionSemantics(name, 'gl1_wbinv')
if upper in ('FLAT_PREFETCH_B8', 'GLOBAL_PREFETCH_B8'):
return InstructionSemantics(name, 'true_nop')
if '_ATOMIC_' in upper:
# Extract the operation suffix after _ATOMIC_.
# Strip _X2 (64-bit variant) and type suffixes (_B32, _U32, _I32, etc.).
for prefix in ('FLAT_ATOMIC_', 'GLOBAL_ATOMIC_', 'SCRATCH_ATOMIC_'):
if upper.startswith(prefix):
suffix = upper[len(prefix) :]
is_x2 = suffix.endswith('_X2')
if is_x2:
suffix = suffix[:-3]
info = _derive_flat_atomic_info(suffix, is_x2)
if info:
op, elem_size, data_dw_actual = info
return InstructionSemantics(
name,
'flat_atomic',
operation=op,
elem_size=elem_size,
num_elems=data_dw_actual,
)
# FP atomics and other unrecognized patterns.
return InstructionSemantics(name, 'flat_atomic')
for prefix in ('GLOBAL_LOAD_ASYNC_TO_LDS_', 'CLUSTER_LOAD_ASYNC_TO_LDS_'):
if upper.startswith(prefix):
info = _FLAT_DATA_MAP.get(upper[len(prefix) :])
if info:
esz, ne, _ = info
return InstructionSemantics(
name, 'global_load_async_to_lds', elem_size=esz, num_elems=ne
)
if upper.startswith('GLOBAL_STORE_ASYNC_FROM_LDS_'):
info = _FLAT_DATA_MAP.get(upper[len('GLOBAL_STORE_ASYNC_FROM_LDS_') :])
if info:
esz, ne, _ = info
return InstructionSemantics(
name, 'global_store_async_from_lds', elem_size=esz, num_elems=ne
)
if upper.startswith('CLUSTER_LOAD_'):
info = _FLAT_DATA_MAP.get(upper[len('CLUSTER_LOAD_') :])
if info:
esz, ne, se = info
return InstructionSemantics(
name, 'flat_load', elem_size=esz, num_elems=ne, sign_extend=se
)
if upper == 'GLOBAL_LOAD_ADDTID_B32':
return InstructionSemantics(
name, 'global_load_addtid', elem_size=4, num_elems=1
)
if upper == 'GLOBAL_STORE_ADDTID_B32':
return InstructionSemantics(
name, 'global_store_addtid', elem_size=4, num_elems=1
)
if upper in ('GLOBAL_LOAD_BLOCK', 'SCRATCH_LOAD_BLOCK'):
return InstructionSemantics(name, 'flat_load', elem_size=4, num_elems=32)
if upper in ('GLOBAL_STORE_BLOCK', 'SCRATCH_STORE_BLOCK'):
return InstructionSemantics(name, 'flat_store', elem_size=4, num_elems=32)
for prefix in ('FLAT_LOAD_MONITOR_', 'GLOBAL_LOAD_MONITOR_'):
if upper.startswith(prefix):
info = _FLAT_DATA_MAP.get(upper[len(prefix) :])
if info:
esz, ne, se = info
return InstructionSemantics(
name, 'flat_load', elem_size=esz, num_elems=ne, sign_extend=se
)
is_store = '_STORE_' in upper
# Strip prefix: FLAT_LOAD_, FLAT_STORE_, GLOBAL_LOAD_, SCRATCH_LOAD_, etc.
for prefix in (
'FLAT_LOAD_',
'FLAT_STORE_',
'GLOBAL_LOAD_',
'GLOBAL_STORE_',
'SCRATCH_LOAD_',
'SCRATCH_STORE_',
):
if upper.startswith(prefix):
suffix = upper[len(prefix) :]
if prefix == 'GLOBAL_LOAD_':
transpose_info = _derive_transpose_load_info(upper)
if transpose_info:
_, esz, ne, transpose_kind = transpose_info
return InstructionSemantics(
name,
'flat_load',
elem_size=esz,
num_elems=ne,
transpose_kind=transpose_kind,
)
info = _FLAT_DATA_MAP.get(suffix)
if info:
esz, ne, se = info
cls = 'flat_store' if is_store else 'flat_load'
return InstructionSemantics(
name,
cls,
elem_size=esz,
num_elems=ne,
sign_extend=se,
d16_hi='D16_HI' in suffix,
d16_lo='D16' in suffix and 'D16_HI' not in suffix,
)
return InstructionSemantics(name, 'nop')
_BUFFER_FORMAT_MAP: dict[str, tuple[int, int]] = {
'FORMAT_X': (4, 1),
'FORMAT_XY': (4, 2),
'FORMAT_XYZ': (4, 3),
'FORMAT_XYZW': (4, 4),
'FORMAT_D16_X': (2, 1),
'FORMAT_D16_XY': (2, 2),
'FORMAT_D16_XYZ': (2, 3),
'FORMAT_D16_XYZW': (2, 4),
}
def _derive_mubuf(name: str) -> InstructionSemantics | None:
"""Derive semantics for a MUBUF (Untyped Buffer memory) instruction."""
upper = name.upper()
if upper == 'BUFFER_WBL2':
return InstructionSemantics(name, 'gl2_wb')
if upper in (
'BUFFER_WBINVL1',
'BUFFER_WBINVL1_SC',
'BUFFER_WBINVL1_VOL',
'BUFFER_GL0_INV',
'BUFFER_GL1_INV',
'BUFFER_INV',
):
return InstructionSemantics(name, 'gl1_inv')
if '_ATOMIC_' in upper:
for prefix in ('BUFFER_ATOMIC_',):
if upper.startswith(prefix):
suffix = upper[len(prefix) :]
is_x2 = suffix.endswith('_X2')
if is_x2:
suffix = suffix[:-3]
info = _derive_flat_atomic_info(suffix, is_x2)
if info:
op, elem_size, data_dw_actual = info
return InstructionSemantics(
name,
'buffer_atomic',
operation=op,
elem_size=elem_size,
num_elems=data_dw_actual,
)
return InstructionSemantics(name, 'buffer_atomic')
is_store = '_STORE_' in upper
for prefix in ('BUFFER_LOAD_', 'BUFFER_STORE_'):
if upper.startswith(prefix):
suffix = upper[len(prefix) :]
info = _FLAT_DATA_MAP.get(suffix)
if info:
esz, ne, se = info
cls = 'buffer_store' if is_store else 'buffer_load'
return InstructionSemantics(
name,
cls,
elem_size=esz,
num_elems=ne,
sign_extend=se,
d16_hi='D16_HI' in suffix,
d16_lo='D16' in suffix and 'D16_HI' not in suffix,
)
return InstructionSemantics(name, 'nop')
_MTBUF_FORMAT_MAP: dict[str, tuple[int, int, bool]] = {
'FORMAT_X': (4, 1, False),
'FORMAT_XY': (4, 2, False),
'FORMAT_XYZ': (4, 3, False),
'FORMAT_XYZW': (4, 4, False),
'FORMAT_D16_X': (2, 1, False),
'FORMAT_D16_XY': (2, 2, False),
'FORMAT_D16_XYZ': (2, 3, False),
'FORMAT_D16_XYZW': (2, 4, False),
}
def _derive_mtbuf(name: str) -> InstructionSemantics | None:
"""Derive semantics for an MTBUF (Typed Buffer memory) instruction."""
upper = name.upper()
is_store = '_STORE_' in upper
for prefix in ('TBUFFER_LOAD_', 'TBUFFER_STORE_'):
if upper.startswith(prefix):
suffix = upper[len(prefix) :]
info = _FLAT_DATA_MAP.get(suffix) or _MTBUF_FORMAT_MAP.get(suffix)
if info:
esz, ne, se = info
cls = 'tbuffer_store' if is_store else 'tbuffer_load'
return InstructionSemantics(
name,
cls,
elem_size=esz,
num_elems=ne,
sign_extend=se,
d16_hi='D16_HI' in suffix,
d16_lo='D16' in suffix and 'D16_HI' not in suffix,
)
return InstructionSemantics(name, 'nop')
_DS_DATA_MAP: dict[str, tuple[int, int]] = {
'B32': (4, 1),
'B64': (8, 1),
'B128': (4, 4),
'U8': (1, 1),
'I8': (1, 1),
'U16': (2, 1),
'I16': (2, 1),
'D16_HI_B32': (2, 1),
'B96': (4, 3),
'2ADDR_STRIDE64_B32': (4, 1),
'2ADDR_STRIDE64_B64': (8, 1),
# RDNA4 adds B8/B16 stores and D16 packing variants.
'B8': (1, 1),
'B16': (2, 1),
'U8_D16': (1, 1),
'U8_D16_HI': (1, 1),
'I8_D16': (1, 1),
'I8_D16_HI': (1, 1),
'U16_D16': (2, 1),
'U16_D16_HI': (2, 1),
'B8_D16_HI': (1, 1),
'B16_D16_HI': (2, 1),
}
def _derive_ds(name: str) -> InstructionSemantics | None:
"""Derive semantics for a DS (Data Share / LDS) instruction.
Handles both CDNA/RDNA1-3 naming (``DS_READ_*``/``DS_WRITE_*``) and
RDNA4 naming (``DS_LOAD_*``/``DS_STORE_*``). RDNA4 also renames
``DS_STOREXCHG`` (was ``DS_WRXCHG``) and ``DS_CMPSTORE``
(was ``DS_CMPST``), which are handled by the atomic fallthrough.
"""
upper = name.upper()
is_write2 = '_WRITE2' in upper or 'DS_STORE_2ADDR' in upper
is_read2 = '_READ2' in upper or 'DS_LOAD_2ADDR' in upper
is_write = ('_WRITE_' in upper or 'DS_STORE_' in upper) and not is_write2
is_read = ('_READ_' in upper or 'DS_LOAD_' in upper) and not is_read2
is_st64 = 'ST64' in upper or 'STRIDE64' in upper
if upper == 'DS_APPEND':
return InstructionSemantics(
name, 'ds_append_consume', operation='append', elem_size=4, num_elems=1
)
if upper == 'DS_CONSUME':
return InstructionSemantics(
name, 'ds_append_consume', operation='consume', elem_size=4, num_elems=1
)
if upper in ('DS_MSKOR_B32', 'DS_MSKOR_RTN_B32'):
return InstructionSemantics(
name, 'ds_mskor', operation='mskor', elem_size=4, num_elems=2
)
if upper in ('DS_MSKOR_B64', 'DS_MSKOR_RTN_B64'):
return InstructionSemantics(
name, 'ds_mskor', operation='mskor', elem_size=8, num_elems=4
)
if upper == 'DS_ATOMIC_ASYNC_BARRIER_ARRIVE_B64':
return InstructionSemantics(
name,
'ds_barrier_arrive',
operation='async_barrier_arrive',
elem_size=8,
num_elems=0,
)
if upper == 'DS_ATOMIC_BARRIER_ARRIVE_RTN_B64':
return InstructionSemantics(
name,
'ds_barrier_arrive',
operation='barrier_arrive',
elem_size=8,
num_elems=2,
)
if is_write2:
esz = 8 if 'B64' in upper else 4
return InstructionSemantics(
name,
'ds_write2',
elem_size=esz,
num_elems=1,
operation='st64' if is_st64 else None,
)
if is_read2:
esz = 8 if 'B64' in upper else 4
return InstructionSemantics(
name,
'ds_read2',
elem_size=esz,
num_elems=1,
operation='st64' if is_st64 else None,
)
if is_write:
for suffix, (esz, ne) in _DS_DATA_MAP.items():
if upper.endswith(suffix):
return InstructionSemantics(
name,
'ds_write',
elem_size=esz,
num_elems=ne,
d16_hi='D16_HI' in suffix,
)
return InstructionSemantics(name, 'ds_write', elem_size=4, num_elems=1)
if is_read:
transpose_info = _derive_transpose_load_info(upper)
if transpose_info:
kind, esz, ne, transpose_kind = transpose_info
return InstructionSemantics(
name,
f'ds_read_tr_{kind}',
elem_size=esz,
num_elems=ne,
transpose_kind=transpose_kind,
)
# ADDTID instructions: address = thread_id * M0_stride + offset.
if 'ADDTID' in upper:
if is_write:
return InstructionSemantics(
name, 'ds_write_addtid', elem_size=4, num_elems=1
)
if is_read:
return InstructionSemantics(
name, 'ds_read_addtid', elem_size=4, num_elems=1
)
return InstructionSemantics(name, 'nop')
if is_read:
for suffix, (esz, ne) in _DS_DATA_MAP.items():
if upper.endswith(suffix):
se = suffix.startswith('I')
return InstructionSemantics(
name,
'ds_read',
elem_size=esz,
num_elems=ne,
sign_extend=bool(se),
d16_hi='D16_HI' in suffix,
d16_lo='D16' in suffix and 'D16_HI' not in suffix,
)
return InstructionSemantics(name, 'ds_read', elem_size=4, num_elems=1)
# DS atomic operations — extract the specific op and data width.
# RTN variants use the same operation; the codegen sets is_load based
# on whether vdst is an explicit destination (num_dst_ > 0).
_DS_ATOMIC_MAP: dict[str, tuple[str, int, int]] = {
# keyword -> (operation, elem_size, data_dwords)
'_ADD_U32': ('add', 4, 1),
'_ADD_U64': ('add', 8, 2),
'_ADD_RTN_U32': ('add', 4, 1),
'_ADD_RTN_U64': ('add', 8, 2),
'_SUB_U32': ('sub', 4, 1),
'_SUB_U64': ('sub', 8, 2),
'_SUB_RTN_U32': ('sub', 4, 1),
'_SUB_RTN_U64': ('sub', 8, 2),
'_RSUB_U32': ('rsub', 4, 1),
'_RSUB_U64': ('rsub', 8, 2),
'_RSUB_RTN_U32': ('rsub', 4, 1),
'_RSUB_RTN_U64': ('rsub', 8, 2),
'_MIN_I32': ('smin', 4, 1),
'_MIN_I64': ('smin', 8, 2),
'_MIN_RTN_I32': ('smin', 4, 1),
'_MIN_RTN_I64': ('smin', 8, 2),
'_MIN_U32': ('umin', 4, 1),
'_MIN_U64': ('umin', 8, 2),
'_MIN_RTN_U32': ('umin', 4, 1),
'_MIN_RTN_U64': ('umin', 8, 2),
'_MAX_I32': ('smax', 4, 1),
'_MAX_I64': ('smax', 8, 2),
'_MAX_RTN_I32': ('smax', 4, 1),
'_MAX_RTN_I64': ('smax', 8, 2),
'_MAX_U32': ('umax', 4, 1),
'_MAX_U64': ('umax', 8, 2),
'_MAX_RTN_U32': ('umax', 4, 1),
'_MAX_RTN_U64': ('umax', 8, 2),
'_AND_B32': ('and', 4, 1),
'_AND_B64': ('and', 8, 2),
'_AND_RTN_B32': ('and', 4, 1),
'_AND_RTN_B64': ('and', 8, 2),
'_OR_B32': ('or', 4, 1),
'_OR_B64': ('or', 8, 2),
'_OR_RTN_B32': ('or', 4, 1),
'_OR_RTN_B64': ('or', 8, 2),
'_XOR_B32': ('xor', 4, 1),
'_XOR_B64': ('xor', 8, 2),
'_XOR_RTN_B32': ('xor', 4, 1),
'_XOR_RTN_B64': ('xor', 8, 2),
'_INC_U32': ('inc', 4, 1),
'_INC_U64': ('inc', 8, 2),
'_INC_RTN_U32': ('inc', 4, 1),
'_INC_RTN_U64': ('inc', 8, 2),
'_DEC_U32': ('dec', 4, 1),
'_DEC_U64': ('dec', 8, 2),
'_DEC_RTN_U32': ('dec', 4, 1),
'_DEC_RTN_U64': ('dec', 8, 2),
'_SWAP_B32': ('swap', 4, 1),
'_SWAP_B64': ('swap', 8, 2),
'_SWAP_RTN_B32': ('swap', 4, 1),
'_SWAP_RTN_B64': ('swap', 8, 2),
'_WRXCHG_RTN_B32': ('swap', 4, 1),
'_WRXCHG_RTN_B64': ('swap', 8, 2),
'_WRXCHG2_RTN_B32': ('swap', 4, 1),
'_WRXCHG2_RTN_B64': ('swap', 8, 2),
'_STOREXCHG_RTN_B32': ('swap', 4, 1),
'_STOREXCHG_RTN_B64': ('swap', 8, 2),
'_CMPST_B32': ('cmpswap', 4, 2),
'_CMPST_B64': ('cmpswap', 8, 4),
'_CMPST_RTN_B32': ('cmpswap', 4, 2),
'_CMPST_RTN_B64': ('cmpswap', 8, 4),
'_CMPST_F32': ('cmpswap', 4, 2),
'_CMPST_F64': ('cmpswap', 8, 4),
'_CMPST_RTN_F32': ('cmpswap', 4, 2),
'_CMPST_RTN_F64': ('cmpswap', 8, 4),
'_CMPSTORE_B32': ('cmpswap', 4, 2),
'_CMPSTORE_B64': ('cmpswap', 8, 4),
'_CMPSTORE_RTN_B32': ('cmpswap', 4, 2),
'_CMPSTORE_RTN_B64': ('cmpswap', 8, 4),
'_CONDXCHG32_RTN_B64': ('cmpswap', 8, 4),
'_ADD_F32': ('fadd', 4, 1),
'_ADD_RTN_F32': ('fadd', 4, 1),
'_ADD_F64': ('fadd', 8, 2),
'_ADD_RTN_F64': ('fadd', 8, 2),
'_MIN_F32': ('fmin', 4, 1),
'_MIN_F64': ('fmin', 8, 2),
'_MIN_RTN_F32': ('fmin', 4, 1),
'_MIN_RTN_F64': ('fmin', 8, 2),
'_MAX_F32': ('fmax', 4, 1),
'_MAX_F64': ('fmax', 8, 2),
'_MAX_RTN_F32': ('fmax', 4, 1),
'_MAX_RTN_F64': ('fmax', 8, 2),
'_MIN_NUM_F32': ('fmin', 4, 1),
'_MIN_NUM_RTN_F32': ('fmin', 4, 1),
'_MAX_NUM_F32': ('fmax', 4, 1),
'_MAX_NUM_RTN_F32': ('fmax', 4, 1),
'_MIN_NUM_F64': ('fmin', 8, 2),
'_MIN_NUM_RTN_F64': ('fmin', 8, 2),
'_MAX_NUM_F64': ('fmax', 8, 2),
'_MAX_NUM_RTN_F64': ('fmax', 8, 2),
'_SUB_CLAMP_U32': ('sub', 4, 1),
'_SUB_CLAMP_RTN_U32': ('sub', 4, 1),
'_CMPSTORE_F32': ('cmpswap', 4, 2),
'_CMPSTORE_RTN_F32': ('cmpswap', 4, 2),
'_CMPSTORE_F64': ('cmpswap', 8, 4),
'_CMPSTORE_RTN_F64': ('cmpswap', 8, 4),
'_WRXCHG2ST64_RTN_B32': ('swap', 4, 1),
'_WRXCHG2ST64_RTN_B64': ('swap', 8, 2),
'_STOREXCHG2ADDR_RTN_B32': ('swap', 4, 1),
'_STOREXCHG2ADDR_STRIDE64_RTN_B32': ('swap', 4, 1),
'_STOREXCHG2ADDR_RTN_B64': ('swap', 8, 2),
'_STOREXCHG2ADDR_STRIDE64_RTN_B64': ('swap', 8, 2),
'_STOREXCHG_2ADDR_RTN_B32': ('swap', 4, 1),
'_STOREXCHG_2ADDR_STRIDE64_RTN_B32': ('swap', 4, 1),
'_STOREXCHG_2ADDR_RTN_B64': ('swap', 8, 2),
'_STOREXCHG_2ADDR_STRIDE64_RTN_B64': ('swap', 8, 2),
'_PK_ADD_F16': ('fadd', 4, 1),
'_PK_ADD_RTN_F16': ('fadd', 4, 1),
'_PK_ADD_BF16': ('fadd', 4, 1),
'_PK_ADD_RTN_BF16': ('fadd', 4, 1),
}
for suffix, (op, esz, dw) in _DS_ATOMIC_MAP.items():
if suffix in upper:
return InstructionSemantics(
name, 'ds_atomic', operation=op, elem_size=esz, num_elems=dw
)
# ── Lane permutation / swizzle ──────────────────────────────────────
if upper in ('DS_PERMUTE_B32', 'DS_BPERMUTE_B32', 'DS_BPERMUTE_FI_B32'):
return InstructionSemantics(name, 'ds_permute')
if upper == 'DS_SWIZZLE_B32':
return InstructionSemantics(name, 'ds_swizzle')
# ── Explicitly classified as nop (not simulated) ────────────────────
# GS register operations — must precede the atomic fallback because
# DS_ADD_GS_REG_RTN / DS_SUB_GS_REG_RTN contain _ADD / _SUB.
if upper in ('DS_ADD_GS_REG_RTN', 'DS_SUB_GS_REG_RTN'):
return InstructionSemantics(name, 'nop')
# GWS (Global Wave Sync) — hardware scheduling primitive, not needed
# for compute simulation.
if upper.startswith('DS_GWS_'):
return InstructionSemantics(name, 'nop')
# GDS ordered count.
if upper == 'DS_ORDERED_COUNT':
return InstructionSemantics(name, 'nop')
# Explicit DS no-op.
if upper == 'DS_NOP':
return InstructionSemantics(name, 'true_nop')
# Fallback for unrecognized DS atomics.
if (
'_ADD' in upper
or '_SUB' in upper
or '_RSUB' in upper
or '_MIN' in upper
or '_MAX' in upper
or '_AND' in upper
or '_OR' in upper
or '_XOR' in upper
or '_INC' in upper
or '_DEC' in upper
or '_CMPST' in upper
or '_SWAP' in upper
or '_CONDXCHG' in upper
or '_WRXCHG' in upper
or '_STOREXCHG' in upper
or '_CMPSTORE' in upper
):
return InstructionSemantics(name, 'ds_atomic')
# Unrecognized DS instruction — fallthrough to nop.
return InstructionSemantics(name, 'nop')
def _derive_mimg(name: str) -> InstructionSemantics | None:
"""Derive semantics for an MIMG (Memory Image) instruction."""
upper = name.upper()
if upper.startswith('TENSOR_LOAD_TO_LDS'):
return InstructionSemantics(name, 'tensor_load_to_lds')
if upper.startswith('TENSOR_STORE_FROM_LDS'):
return InstructionSemantics(name, 'tensor_store_from_lds')
if upper.startswith('IMAGE_STORE'):
return InstructionSemantics(name, 'image_store')
if upper.startswith('IMAGE_LOAD'):
return InstructionSemantics(name, 'image_load')
if upper.startswith('IMAGE_ATOMIC'):
return InstructionSemantics(name, 'image_atomic')
if upper.startswith('IMAGE_SAMPLE') or upper.startswith('IMAGE_GATHER'):
return InstructionSemantics(name, 'image_sample')
if upper.startswith('IMAGE_GET_RESINFO') or upper.startswith('IMAGE_GET_LOD'):
return InstructionSemantics(name, 'image_query')
if upper.startswith('IMAGE_MSAA_LOAD'):
return InstructionSemantics(name, 'image_load')
if upper.startswith('IMAGE_BVH_INTERSECT'):
return InstructionSemantics(name, 'image_bvh')
return InstructionSemantics(name, 'nop')
def _derive_exp(name: str) -> InstructionSemantics | None:
"""Derive semantics for an EXP/VEXPORT (Export) instruction."""
return InstructionSemantics(name, 'export')
def _derive_vintrp(name: str) -> InstructionSemantics | None:
"""Derive semantics for a VINTRP (Vertex Interpolation) instruction."""
return InstructionSemantics(name, 'interp')
def _derive_ldsdir(name: str) -> InstructionSemantics | None:
"""Derive semantics for an LDSDIR/VDSDIR (LDS Direct) instruction."""
return InstructionSemantics(name, 'lds_direct')
# Map encoding format name (with ENC_ prefix stripped) to derivation function.
_ENC_DERIVE = {
'SOPP': _derive_sopp,
'SOP1': _derive_sop1,
'SOP2': _derive_sop2,
'SOPC': _derive_sopc,
'SOPK': _derive_sopk,
'VOP1': _derive_vop1,
'VOP2': _derive_vop2,
'VOPC': _derive_vopc,
'VOP3': _derive_vop3,
'VOP3_SDST': _derive_vop3,
'VOP3_SDST_ENC': _derive_vop3,
'VOP3P': _derive_vop3p,
'VOP3P_MFM': _derive_vop3p, # VOP3P_MFMA after trailing A strip
'SMEM': _derive_smem,
'FLAT': _derive_flat,
'MUBUF': _derive_mubuf,
'MTBUF': _derive_mtbuf,
'DS': _derive_ds,
# RDNA1-3 formats.
'MIMG': _derive_mimg,
'EXP': _derive_exp,
'VINTRP': _derive_vintrp,
'LDSDIR': _derive_ldsdir,
'VINTERP': _derive_vintrp,
# RDNA4 renamed formats (same instruction patterns, different encoding
# names): VFLAT/VGLOBAL/VSCRATCH reuse the FLAT derivation,
# VBUFFER reuses MUBUF, VDS reuses DS, etc.
'VFLAT': _derive_flat,
'VGLOBAL': _derive_flat,
'VSCRATCH': _derive_flat,
'VBUFFER': _derive_mubuf,
'VDS': _derive_ds,
'VIMAGE': _derive_mimg,
'VSAMPLE': _derive_mimg,
'VEXPORT': _derive_exp,
'VDSDIR': _derive_ldsdir,
}
def derive_semantics(name: str, enc_name: str) -> InstructionSemantics | None:
"""Derive execution semantics for an instruction from its name and encoding.
The encoding format name selects a format-specific derivation function
(e.g. ``_derive_sop2`` for SOP2). For VOP3, the VOP1/VOP2/VOPC
derivations are tried as fallbacks since VOP3 can re-encode those.
Args:
name: Instruction mnemonic (e.g. ``S_ADD_I32``).
enc_name: Encoding format name (e.g. ``ENC_SOP2``, ``VOP3A``).
Returns:
InstructionSemantics if the instruction could be classified, or
``None`` for unrecognized encodings.
"""
# Normalize encoding name: "ENC_SOP1"→"SOP1", "VOP3A"→"VOP3", etc.
base_enc = re.sub(r'^ENC_', '', enc_name)
# Strip trailing A/B only when the result maps to a known encoding
# (e.g. VOP3A→VOP3). A generic regex would mangle future encoding
# names that legitimately end in A or B.
if base_enc not in _ENC_DERIVE and base_enc[-1:] in ('A', 'B'):
stripped = base_enc[:-1]
if stripped in _ENC_DERIVE:
base_enc = stripped
derive_fn = _ENC_DERIVE.get(base_enc)
if derive_fn is None:
return None
sem = derive_fn(name)
# VOP3 (and VOP3_SDST_ENC) can re-encode VOP1/VOP2/VOPC instructions.
if sem is None and base_enc.startswith('VOP3'):
for fallback in (_derive_vop1, _derive_vop2, _derive_vopc):
sem = fallback(name)
if sem is not None:
break
return sem
def derive_all_semantics(isa_spec: IsaSpec) -> SemanticsSpec:
"""Derive semantics for every instruction in an ISA spec.
Iterates over all encodings and their instructions, calling
:func:`derive_semantics` for each. If the ISA profile provides
:attr:`~amdisa.isa_profile.IsaProfile.semantic_overrides`, those
take priority over the generic mnemonic-based derivation.
Duplicate instruction names (same mnemonic under multiple encodings)
are skipped.
Args:
isa_spec: Parsed ISA specification with populated encodings.
Returns:
SemanticsSpec containing all derivable instructions.
"""
overrides = isa_spec.profile.semantic_overrides if isa_spec.profile else {}
class_overrides = (
isa_spec.profile.semantic_class_overrides if isa_spec.profile else {}
)
instructions: dict[str, InstructionSemantics] = {}
for enc in isa_spec.inst_encodings:
for inst in enc.insts:
if inst.name in instructions:
continue
override = overrides.get(inst.name)
if override is not None:
sem_class, operation, data_type = override
instructions[inst.name] = InstructionSemantics(
inst.name,
sem_class,
operation=operation or None,
data_type=data_type or None,
)
continue
sem = derive_semantics(inst.name, enc.enc_name)
if sem is not None:
semantic_class = class_overrides.get(inst.name)
if semantic_class is not None:
sem = replace(sem, semantic_class=semantic_class)
instructions[inst.name] = sem
return SemanticsSpec(instructions)