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
2597 lines
92 KiB
Python
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)
|