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

2863 lines
91 KiB
Python

# Copyright (c) 2025-2026 Advanced Micro Devices, Inc.
# SPDX-License-Identifier: MIT
"""Derive SemaAST from mnemonic-based InstructionSemantics.
For ISAs without expression-tree semantics, this module
converts :class:`~amdisa.semantics.InstructionSemantics` metadata into
:class:`~amdisa.sema_ast.SemaBlock` trees. These trees are structurally
equivalent to what the XML parser produces, enabling both paths
to converge on the same lowering backend.
Covers scalar semantic classes:
scalar_unary, scalar_binop, scalar_bfe, scalar_cmp, scalar_cmpk,
scalar_bitcmp, scalar_saveexec, scalar_mov, scalar_cmov
Covers vector ALU + cmp semantic classes:
vector_mov, vector_unary, vector_binop, vector_ternary,
vector_cmp, vector_cmpx, vector_cmp_class, vector_cmpx_class,
vector_add_co, vector_cndmask, vector_readfirstlane, vector_readlane,
vector_writelane, vector_swap, vector_fmaak, vector_fmamk
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from amdisa.sema_ast import (
ExecModel,
SemaBlock,
SemaNode,
SemaNodeKind,
SemaType,
)
from amdisa.sema_effects import inline_binary_op_effects
from amdisa.sema_properties import InstructionProperty, derive_properties
if TYPE_CHECKING:
from amdisa.semantics import InstructionSemantics
def _src(idx: int, ty: SemaType = SemaType.B32) -> SemaNode:
return SemaNode(
SemaNodeKind.INSTOPERAND,
ty=ty,
children=(
SemaNode(SemaNodeKind.ID, id_name='S'),
SemaNode(SemaNodeKind.LIT, lit_value=str(idx)),
),
)
def _dst(idx: int, ty: SemaType = SemaType.B32) -> SemaNode:
return SemaNode(
SemaNodeKind.INSTOPERAND,
ty=ty,
children=(
SemaNode(SemaNodeKind.ID, id_name='D'),
SemaNode(SemaNodeKind.LIT, lit_value=str(idx)),
),
)
def _cast(inner: SemaNode, target: SemaType) -> SemaNode:
return SemaNode(SemaNodeKind.CAST, ty=target, cast_target=target, children=(inner,))
def _lit(val: str, ty: SemaType = SemaType.U32) -> SemaNode:
return SemaNode(SemaNodeKind.LIT, lit_value=val, ty=ty)
def _id(name: str, ty: SemaType | None = None) -> SemaNode:
return SemaNode(SemaNodeKind.ID, id_name=name, ty=ty)
def _assign(lhs: SemaNode, rhs: SemaNode) -> SemaNode:
return SemaNode(SemaNodeKind.ASSIGN, children=(lhs, rhs))
def _scc_write(expr: SemaNode) -> SemaNode:
return _assign(_id('SCC', SemaType.U1), expr)
def _dtype_to_sema(dtype: str | None) -> SemaType:
"""Map canonical dtype string to SemaType.
Unmapped narrow dtypes (i16, u16, i8, u8, i24, u24, b16) intentionally
fall back to U32 — the wired classes handle narrowing via instruction-
specific inline templates. Add explicit entries here when wiring packed
or sub-32-bit classes through SemaAST.
"""
if dtype is None:
return SemaType.U32
mapping = {
'u32': SemaType.U32,
'i32': SemaType.I32,
'b32': SemaType.B32,
'u64': SemaType.U64,
'i64': SemaType.I64,
'b64': SemaType.B64,
'u16': SemaType('U', 16),
'i16': SemaType('I', 16),
'b16': SemaType('B', 16),
'f32': SemaType.F32,
'f64': SemaType.F64,
'f16': SemaType.F16,
'bf16': SemaType.BF16,
'fp8': SemaType.FP8,
'bf8': SemaType.BF8,
'fp6': SemaType.FP6,
'fp4': SemaType.FP4,
}
return mapping.get(dtype, SemaType.U32)
_OP_TO_KIND: dict[str, SemaNodeKind] = {
'add': SemaNodeKind.ADD,
'sub': SemaNodeKind.SUB,
'mul': SemaNodeKind.MUL,
'mulhi': SemaNodeKind.CALL,
'mul_hi': SemaNodeKind.CALL,
'and': SemaNodeKind.AND,
'or': SemaNodeKind.OR,
'xor': SemaNodeKind.XOR,
'nand': SemaNodeKind.AND,
'nor': SemaNodeKind.OR,
'xnor': SemaNodeKind.XOR,
'andn2': SemaNodeKind.AND,
'orn2': SemaNodeKind.OR,
'shl': SemaNodeKind.SHL,
'shr': SemaNodeKind.SHR,
'ashr': SemaNodeKind.CALL,
'ashrrev': SemaNodeKind.CALL,
'min': SemaNodeKind.CALL,
'max': SemaNodeKind.CALL,
'absdiff': SemaNodeKind.CALL,
'not': SemaNodeKind.BITNEG,
'brev': SemaNodeKind.CALL,
'bcnt1': SemaNodeKind.CALL,
'bcnt0': SemaNodeKind.CALL,
'ff0': SemaNodeKind.CALL,
'ff1': SemaNodeKind.CALL,
'flbit': SemaNodeKind.CALL,
'wqm': SemaNodeKind.CALL,
'quadmask': SemaNodeKind.CALL,
'bitset0': SemaNodeKind.CALL,
'bitset1': SemaNodeKind.CALL,
}
_CMP_OP_TO_KIND: dict[str, SemaNodeKind] = {
'eq': SemaNodeKind.EQ,
'ne': SemaNodeKind.NE,
'neq': SemaNodeKind.NE,
'lt': SemaNodeKind.LT,
'gt': SemaNodeKind.GT,
'le': SemaNodeKind.LE,
'ge': SemaNodeKind.GE,
}
_CMP_NEGATED: dict[str, SemaNodeKind] = {
'nlt': SemaNodeKind.LT,
'ngt': SemaNodeKind.GT,
'nle': SemaNodeKind.LE,
'nge': SemaNodeKind.GE,
'nlg': SemaNodeKind.NE,
}
def _make_cmp(op: str, src0: SemaNode, src1: SemaNode) -> SemaNode:
"""Build a comparison node, handling ordered, unordered, and constant ops."""
if op == 'f':
return _lit('0', SemaType.U1)
if op == 't':
return _lit('1', SemaType.U1)
if op == 'o':
return SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U1,
call_name='is_ordered',
children=(_id('is_ordered'), src0, src1),
)
if op == 'u':
return SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U1,
call_name='is_unordered',
children=(_id('is_unordered'), src0, src1),
)
if op == 'lg':
lt = SemaNode(SemaNodeKind.LT, ty=SemaType.U1, children=(src0, src1))
gt = SemaNode(SemaNodeKind.GT, ty=SemaType.U1, children=(src0, src1))
return SemaNode(SemaNodeKind.LOR, ty=SemaType.U1, children=(lt, gt))
if op == 'nlg':
lt = SemaNode(SemaNodeKind.LT, ty=SemaType.U1, children=(src0, src1))
gt = SemaNode(SemaNodeKind.GT, ty=SemaType.U1, children=(src0, src1))
lor = SemaNode(SemaNodeKind.LOR, ty=SemaType.U1, children=(lt, gt))
return SemaNode(SemaNodeKind.BOOLNEG, ty=SemaType.U1, children=(lor,))
neg_kind = _CMP_NEGATED.get(op)
if neg_kind is not None:
inner = SemaNode(neg_kind, ty=SemaType.U1, children=(src0, src1))
return SemaNode(SemaNodeKind.BOOLNEG, ty=SemaType.U1, children=(inner,))
kind = _CMP_OP_TO_KIND.get(op, SemaNodeKind.EQ)
return SemaNode(kind, ty=SemaType.U1, children=(src0, src1))
_DERIVE_REGISTRY: dict[str, type['_ScalarDeriver']] = {}
def derive_sema_block(sem: InstructionSemantics) -> SemaBlock | None:
"""Convert an InstructionSemantics into a SemaBlock.
Returns None if the semantic class is not supported yet.
"""
cls = _DERIVE_REGISTRY.get(sem.semantic_class)
if cls is None:
return None
block = cls.derive(sem)
writes_scc = InstructionProperty.WRITES_SCC in derive_properties(block)
declared_scc_write = sem.sets_scc not in (None, 'none')
if writes_scc != declared_scc_write:
raise ValueError(
f'{sem.name}: sets_scc={sem.sets_scc!r} disagrees with '
f'derived SCC write={writes_scc}'
)
return block
class _ScalarDeriver:
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
raise NotImplementedError
def _register(semantic_class: str):
def decorator(cls):
_DERIVE_REGISTRY[semantic_class] = cls
return cls
return decorator
@_register('scalar_mov')
class _ScalarMov(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
body = _assign(_cast(_dst(0, ty), ty), _cast(_src(0, ty), ty))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_cmov')
class _ScalarCmov(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
body = SemaNode(
SemaNodeKind.IF,
children=(
_id('SCC', SemaType.U1),
_assign(_cast(_dst(0, ty), ty), _cast(_src(0, ty), ty)),
),
)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_unary')
class _ScalarUnary(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
op = sem.operation
# bitset0/1 are read-modify-write on dst — not expressible as
# a simple unary template. Fall through to inline handler.
if op in ('bitset0', 'bitset1'):
ty = _dtype_to_sema(sem.data_type)
bit_mask = '63u' if ty.size == 64 else '31u'
one_lit = '1' if ty.size == 64 else '1u'
src0 = _cast(_src(0), SemaType.U32)
dst_read = _cast(_dst(0), ty)
shifted = SemaNode(
SemaNodeKind.SHL,
ty=ty,
children=(
_lit(one_lit, ty),
SemaNode(
SemaNodeKind.AND,
ty=SemaType.U32,
children=(src0, _lit(bit_mask)),
),
),
)
if op == 'bitset0':
neg = SemaNode(SemaNodeKind.BITNEG, ty=ty, children=(shifted,))
result = SemaNode(SemaNodeKind.AND, ty=ty, children=(dst_read, neg))
else:
result = SemaNode(SemaNodeKind.OR, ty=ty, children=(dst_read, shifted))
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
# 64-bit unary ops need 64-bit-specific templates. Use distinct
# call names so the lowering selects the right implementation.
dtype = sem.data_type
if dtype in ('b64', 'u64', 'i64') and op in (
'brev',
'wqm',
'quadmask',
'ff0',
'ff1',
'flbit',
'ctz',
'bcnt1',
'bcnt0',
):
call_name = f'{op}64'
ty64 = _dtype_to_sema(dtype)
src0_64 = _cast(_src(0, ty64), ty64)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty64,
call_name=call_name,
children=(_id(call_name), src0_64),
)
stmts_64: list[SemaNode] = [
_assign(_id('result', ty64), result),
_assign(_cast(_dst(0, ty64), ty64), _id('result', ty64)),
]
if sem.sets_scc and sem.sets_scc != 'none':
stmts_64.append(
_scc_write(
SemaNode(
SemaNodeKind.NE,
ty=SemaType.U1,
children=(
_id('result', ty64),
_lit('0', ty64),
),
),
)
)
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts_64))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
stmts: list[SemaNode] = []
if op == 'not':
result = SemaNode(SemaNodeKind.BITNEG, ty=ty, children=(src0,))
elif op == 'sext8':
result = SemaNode(
SemaNodeKind.SIGNEXT_FROM_BIT, ty=ty, children=(src0, _lit('7'))
)
elif op == 'sext16':
result = SemaNode(
SemaNodeKind.SIGNEXT_FROM_BIT, ty=ty, children=(src0, _lit('15'))
)
elif op in ('ceil', 'floor', 'trunc', 'rndne'):
kind_map = {
'ceil': SemaNodeKind.CALL,
'floor': SemaNodeKind.FLOOR,
'trunc': SemaNodeKind.TRUNC,
'rndne': SemaNodeKind.CALL,
}
k = kind_map[op]
if k == SemaNodeKind.CALL:
fn = 'std::ceil' if op == 'ceil' else 'std::nearbyint'
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=fn,
children=(SemaNode(SemaNodeKind.ID, id_name=fn), src0),
)
else:
result = SemaNode(k, ty=ty, children=(src0,))
elif op and op.startswith('cvt_'):
result = SemaNode(
SemaNodeKind.CALL, ty=ty, call_name=op, children=(_id(op), src0)
)
else:
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=op or 'unknown',
children=(_id(op or 'unknown'), src0),
)
result_ty = SemaType.F32 if ty.base in ('F', 'BF') and ty.size == 16 else ty
stmts.append(_assign(_id('result', result_ty), result))
stmts.append(_assign(_cast(_dst(0), ty), _id('result', result_ty)))
if sem.sets_scc and sem.sets_scc != 'none':
stmts.append(
_scc_write(
SemaNode(
SemaNodeKind.NE,
ty=SemaType.U1,
children=(
_id('result', ty),
_lit('0', ty),
),
),
)
)
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_binop')
class _ScalarBinop(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
op = sem.operation
ty = _dtype_to_sema(sem.data_type)
raw_carry_bits = (
sem.sets_scc in ('carry', 'borrow', 'overflow') and ty.base == 'I'
)
calc_ty = SemaType('U', ty.size) if raw_carry_bits else ty
src0 = _cast(_src(0), calc_ty)
src1 = _cast(_src(1), calc_ty)
stmts: list[SemaNode] = []
kind = _OP_TO_KIND.get(op or '', SemaNodeKind.CALL)
scc_handled_by_template = inline_binary_op_effects(op).writes_scc
needs_cached_ops = (
sem.sets_scc
and sem.sets_scc != 'none'
and not scc_handled_by_template
and sem.sets_scc in ('carry', 'borrow', 'overflow', 'compare')
)
if needs_cached_ops:
stmts.append(_assign(_id('s0', calc_ty), src0))
stmts.append(_assign(_id('s1', calc_ty), src1))
src0 = _id('s0', calc_ty)
src1 = _id('s1', calc_ty)
if op == 'mul' and ty.base == 'I':
mul_ty = SemaType.U32 if ty.size == 32 else SemaType.U64
result = SemaNode(
SemaNodeKind.MUL,
ty=mul_ty,
children=(
_cast(src0, mul_ty),
_cast(src1, mul_ty),
),
)
elif op in ('shl', 'shr'):
mask_val = '63u' if ty.size == 64 else '31u'
masked = SemaNode(
SemaNodeKind.AND, ty=SemaType.U32, children=(src1, _lit(mask_val))
)
shift_kind = SemaNodeKind.SHL if op == 'shl' else SemaNodeKind.SHR
result = SemaNode(shift_kind, ty=ty, children=(src0, masked))
elif op == 'ashr':
call_name = 'ashr_i64' if ty.size == 64 else 'util::arithmetic_shr'
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=call_name,
children=(_id(call_name), src0, src1),
)
elif op in ('nand', 'nor', 'xnor'):
inner = SemaNode(kind, ty=ty, children=(src0, src1))
result = SemaNode(SemaNodeKind.BITNEG, ty=ty, children=(inner,))
elif op == 'andn2':
result = SemaNode(
SemaNodeKind.AND,
ty=ty,
children=(
src0,
SemaNode(SemaNodeKind.BITNEG, ty=ty, children=(src1,)),
),
)
elif op == 'orn2':
result = SemaNode(
SemaNodeKind.OR,
ty=ty,
children=(
src0,
SemaNode(SemaNodeKind.BITNEG, ty=ty, children=(src1,)),
),
)
elif op in ('min', 'max', 'min_num', 'max_num', 'minimum', 'maximum'):
fn = f'std::{op}' if op in ('min', 'max') else op
result = SemaNode(
SemaNodeKind.CALL, ty=ty, call_name=fn, children=(_id(fn), src0, src1)
)
elif op == 'fmac':
result = SemaNode(
SemaNodeKind.FMA,
ty=ty,
children=(src0, src1, _cast(_dst(0), ty)),
)
elif op == 'fma':
result = SemaNode(
SemaNodeKind.FMA,
ty=ty,
children=(src0, src1, _cast(_src(2), ty)),
)
elif op == 'absdiff':
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='ABSDIFF',
children=(_id('ABSDIFF'), src0, src1),
)
elif kind == SemaNodeKind.CALL:
fn = op or 'unknown'
if fn == 'bfe' and ty.size == 64:
fn = 'bfe_i64' if ty.base == 'I' else 'bfe64'
elif fn == 'bfe' and ty.base == 'I':
fn = 'bfe_i32'
elif fn == 'bfm' and ty.size == 64:
fn = 'bfm64'
result = SemaNode(
SemaNodeKind.CALL, ty=ty, call_name=fn, children=(_id(fn), src0, src1)
)
elif op in ('add', 'sub') and ty.base == 'I':
# Emulate the hardware's wrap-around arithmetic in unsigned. A bare
# signed `s0 + s1` is undefined on overflow, which GCC -O1+ exploits
# to fold away the signed-overflow SCC check. Derive SCC from the
# the sign-bit identity
u_ty = SemaType.U32 if ty.size == 32 else SemaType.U64
result = SemaNode(
kind,
ty=u_ty,
children=(_cast(src0, u_ty), _cast(src1, u_ty)),
)
else:
result = SemaNode(kind, ty=ty, children=(src0, src1))
result_ty = calc_ty if raw_carry_bits else ty
if op in ('mul', 'add', 'sub') and ty.base == 'I':
result_ty = result.ty or result_ty
if ty.base in ('F', 'BF') and ty.size == 16:
result_ty = SemaType.F32
stmts.append(_assign(_id('result', result_ty), result))
stmts.append(_assign(_cast(_dst(0), ty), _id('result', result_ty)))
if sem.sets_scc and sem.sets_scc != 'none' and not scc_handled_by_template:
if sem.sets_scc == 'carry':
wide = SemaNode(
SemaNodeKind.ADD,
ty=SemaType.U64,
children=(
_cast(src0, SemaType.U64),
_cast(src1, SemaType.U64),
),
)
scc_expr = SemaNode(
SemaNodeKind.GT,
ty=SemaType.U1,
children=(
wide,
_lit('4294967295', SemaType.U64),
),
)
elif sem.sets_scc == 'borrow':
scc_expr = SemaNode(
SemaNodeKind.LT,
ty=SemaType.U1,
children=(
src0,
src1,
),
)
elif sem.sets_scc == 'overflow':
# Signed-overflow SCC detected in unsigned via the sign-bit
# identity
fn = 'signed_sub_overflows' if op == 'sub' else 'signed_add_overflows'
scc_expr = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U1,
call_name=fn,
children=(_id(fn), src0, src1),
)
elif sem.sets_scc == 'compare':
cmp_kind = SemaNodeKind.GT if op == 'max' else SemaNodeKind.LT
scc_expr = SemaNode(
cmp_kind,
ty=SemaType.U1,
children=(
src0,
src1,
),
)
else:
scc_expr = SemaNode(
SemaNodeKind.NE,
ty=SemaType.U1,
children=(
_id('result', ty),
_lit('0', ty),
),
)
stmts.append(_scc_write(scc_expr))
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_cmp')
class _ScalarCmp(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
op = sem.operation
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
cmp = _make_cmp(op or "", src0, src1)
body = _scc_write(cmp)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_cmpk')
class _ScalarCmpk(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
op = sem.operation
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
imm = _cast(_src(1), ty)
cmp = _make_cmp(op or "", src0, imm)
body = _scc_write(cmp)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_bitcmp')
class _ScalarBitcmp(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
op = sem.operation
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
bit_mask = _lit('63' if ty.size == 64 else '31', SemaType.U32)
bit_index = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U32,
children=(
_cast(_src(1), SemaType.U32),
bit_mask,
),
)
bit_extract = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U1,
children=(
SemaNode(SemaNodeKind.SHR, ty=ty, children=(src0, bit_index)),
_lit('1', ty),
),
)
if op == 'bitcmp0':
cmp = SemaNode(
SemaNodeKind.EQ,
ty=SemaType.U1,
children=(
bit_extract,
_lit('0', SemaType.U1),
),
)
else:
cmp = SemaNode(
SemaNodeKind.NE,
ty=SemaType.U1,
children=(
bit_extract,
_lit('0', SemaType.U1),
),
)
body = _scc_write(cmp)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_bfe')
class _ScalarBfe(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
offset = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U32,
children=(
src1,
_lit('31'),
),
)
width = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U32,
children=(
SemaNode(SemaNodeKind.SHR, ty=ty, children=(src1, _lit('16'))),
_lit('127'),
),
)
extract = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='util::bfe',
children=(_id('util::bfe'), src0, offset, width),
)
stmts = [
_assign(_id('result', ty), extract),
_assign(_cast(_dst(0), ty), _id('result', ty)),
_scc_write(
SemaNode(
SemaNodeKind.NE,
ty=SemaType.U1,
children=(
_id('result', ty),
_lit('0', ty),
),
)
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
_SAVEEXEC_OP_TO_KIND = {
'and': SemaNodeKind.AND,
'or': SemaNodeKind.OR,
'xor': SemaNodeKind.XOR,
'andn1': SemaNodeKind.AND,
'orn1': SemaNodeKind.OR,
'andn2': SemaNodeKind.AND,
'orn2': SemaNodeKind.OR,
'and_not0': SemaNodeKind.AND,
'or_not0': SemaNodeKind.OR,
'and_not1': SemaNodeKind.AND,
'or_not1': SemaNodeKind.OR,
'nand': SemaNodeKind.AND,
'nor': SemaNodeKind.OR,
'xnor': SemaNodeKind.XOR,
}
@_register('scalar_saveexec')
class _ScalarSaveexec(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
op = sem.operation
if op is None:
raise ValueError('Unsupported SAVEEXEC operation: None')
try:
kind = _SAVEEXEC_OP_TO_KIND[op]
except KeyError:
raise ValueError(
f'Unsupported SAVEEXEC operation: {sem.operation}'
) from None
stmts: list[SemaNode] = []
is_b32 = sem.data_type == 'b32'
src_ty = SemaType.U32 if is_b32 else SemaType.U64
exec_id = 'EXEC' if is_b32 else 'EXEC_RAW'
# Cache source and EXEC before any writes (prevents aliasing).
src0 = _cast(_src(0), src_ty)
stmts.append(_assign(_id('src', SemaType.U64), src0))
cached_src = _id('src', SemaType.U64)
stmts.append(_assign(_id('old_exec', SemaType.U64), _id(exec_id, SemaType.U64)))
exec_read = _id('old_exec', SemaType.U64)
saved_exec = _cast(exec_read, SemaType.U32) if is_b32 else exec_read
stmts.append(
_assign(
_cast(_dst(0), src_ty),
saved_exec,
)
)
if op in ('nand', 'nor', 'xnor'):
inner = SemaNode(kind, ty=SemaType.U64, children=(exec_read, cached_src))
new_exec = SemaNode(SemaNodeKind.BITNEG, ty=SemaType.U64, children=(inner,))
elif op == 'andn1':
new_exec = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U64,
children=(
exec_read,
SemaNode(
SemaNodeKind.BITNEG, ty=SemaType.U64, children=(cached_src,)
),
),
)
elif op == 'orn1':
new_exec = SemaNode(
SemaNodeKind.OR,
ty=SemaType.U64,
children=(
exec_read,
SemaNode(
SemaNodeKind.BITNEG, ty=SemaType.U64, children=(cached_src,)
),
),
)
elif op == 'andn2':
new_exec = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U64,
children=(
cached_src,
SemaNode(
SemaNodeKind.BITNEG, ty=SemaType.U64, children=(exec_read,)
),
),
)
elif op == 'orn2':
new_exec = SemaNode(
SemaNodeKind.OR,
ty=SemaType.U64,
children=(
cached_src,
SemaNode(
SemaNodeKind.BITNEG, ty=SemaType.U64, children=(exec_read,)
),
),
)
elif op == 'and_not0':
new_exec = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U64,
children=(
exec_read,
SemaNode(
SemaNodeKind.BITNEG, ty=SemaType.U64, children=(cached_src,)
),
),
)
elif op == 'and_not1':
new_exec = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U64,
children=(
cached_src,
SemaNode(
SemaNodeKind.BITNEG, ty=SemaType.U64, children=(exec_read,)
),
),
)
elif op == 'or_not0':
new_exec = SemaNode(
SemaNodeKind.OR,
ty=SemaType.U64,
children=(
exec_read,
SemaNode(
SemaNodeKind.BITNEG, ty=SemaType.U64, children=(cached_src,)
),
),
)
elif op == 'or_not1':
new_exec = SemaNode(
SemaNodeKind.OR,
ty=SemaType.U64,
children=(
cached_src,
SemaNode(
SemaNodeKind.BITNEG, ty=SemaType.U64, children=(exec_read,)
),
),
)
elif kind == SemaNodeKind.CALL:
new_exec = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U64,
call_name=op,
children=(_id(op), exec_read, cached_src),
)
else:
new_exec = SemaNode(kind, ty=SemaType.U64, children=(exec_read, cached_src))
if is_b32:
new_exec = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U64,
children=(new_exec, _lit('0xffffffff', SemaType.U64)),
)
stmts.append(_assign(_id(exec_id, SemaType.U64), new_exec))
stmts.append(
_scc_write(
SemaNode(
SemaNodeKind.NE,
ty=SemaType.U1,
children=(
_id(exec_id, SemaType.U64),
_lit('0', SemaType.U64),
),
),
)
)
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
# =========================================================================
# Vector ALU + cmp semantic classes
# =========================================================================
_UNARY_KIND_MAP: dict[str, SemaNodeKind] = {
'not': SemaNodeKind.BITNEG,
'bfrev': SemaNodeKind.CALL,
'ceil': SemaNodeKind.CALL,
'floor': SemaNodeKind.FLOOR,
'trunc': SemaNodeKind.TRUNC,
'fract': SemaNodeKind.FRACT,
'rndne': SemaNodeKind.CALL,
'rcp': SemaNodeKind.CALL,
'rcp_iflag': SemaNodeKind.CALL,
'rsq': SemaNodeKind.CALL,
'sqrt': SemaNodeKind.CALL,
'sin': SemaNodeKind.CALL,
'cos': SemaNodeKind.CALL,
'log': SemaNodeKind.CALL,
'log2': SemaNodeKind.CALL,
'exp': SemaNodeKind.CALL,
'bcnt': SemaNodeKind.CALL,
'ffbl': SemaNodeKind.CALL,
'ffbh_u32': SemaNodeKind.CALL,
'ffbh_i32': SemaNodeKind.CALL,
'cls_i32': SemaNodeKind.CALL,
}
_BINOP_KIND_MAP: dict[str, SemaNodeKind] = {
'add': SemaNodeKind.ADD,
'sub': SemaNodeKind.SUB,
'subrev': SemaNodeKind.SUB,
'mul': SemaNodeKind.MUL,
'mul_lo': SemaNodeKind.MUL,
'mul_hi': SemaNodeKind.CALL,
'mulhi': SemaNodeKind.CALL,
'and': SemaNodeKind.AND,
'or': SemaNodeKind.OR,
'xor': SemaNodeKind.XOR,
'shl': SemaNodeKind.SHL,
'shr': SemaNodeKind.SHR,
'ashr': SemaNodeKind.CALL,
'ashrrev': SemaNodeKind.CALL,
'min': SemaNodeKind.CALL,
'max': SemaNodeKind.CALL,
'lshlrev': SemaNodeKind.SHL,
'lshrrev': SemaNodeKind.SHR,
'ldexp': SemaNodeKind.LDEXP,
}
def _vec_unary_expr(op: str | None, src0: SemaNode, ty: SemaType) -> SemaNode:
if op is None:
return src0
kind = _UNARY_KIND_MAP.get(op, SemaNodeKind.CALL)
if op == 'not':
return SemaNode(SemaNodeKind.BITNEG, ty=ty, children=(src0,))
if kind != SemaNodeKind.CALL:
return SemaNode(kind, ty=ty, children=(src0,))
return SemaNode(SemaNodeKind.CALL, ty=ty, call_name=op, children=(_id(op), src0))
def _vec_binop_expr(
op: str | None,
src0: SemaNode,
src1: SemaNode,
ty: SemaType,
) -> SemaNode:
if op is None:
return SemaNode(SemaNodeKind.ADD, ty=ty, children=(src0, src1))
if op in ('subrev', 'rsub'):
return SemaNode(SemaNodeKind.SUB, ty=ty, children=(src1, src0))
if op in ('lshlrev', 'lshrrev', 'shl', 'shr'):
base = {
'lshlrev': SemaNodeKind.SHL,
'lshrrev': SemaNodeKind.SHR,
'shl': SemaNodeKind.SHL,
'shr': SemaNodeKind.SHR,
}
mask_val = '63u' if ty.size == 64 else ('15u' if ty.size == 16 else '31u')
masked_amt = SemaNode(
SemaNodeKind.AND, ty=SemaType.U32, children=(src0, _lit(mask_val))
)
return SemaNode(base[op], ty=ty, children=(src1, masked_amt))
if op == 'fmac':
return SemaNode(
SemaNodeKind.FMA, ty=ty, children=(src0, src1, _cast(_dst(0, ty), ty))
)
if op == 'xnor':
return SemaNode(
SemaNodeKind.BITNEG,
ty=ty,
children=(SemaNode(SemaNodeKind.XOR, ty=ty, children=(src0, src1)),),
)
if op == 'mul_legacy':
return SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='mul_legacy',
children=(_id('mul_legacy'), src0, src1),
)
if op in ('ashr', 'ashrrev'):
if ty.size == 64:
call_name = 'ashr_i64'
elif ty.size == 16:
call_name = 'util::arithmetic_shr_i16'
else:
call_name = 'util::arithmetic_shr'
return SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=call_name,
children=(_id(call_name), src1, src0),
)
kind = _BINOP_KIND_MAP.get(op, SemaNodeKind.CALL)
if kind == SemaNodeKind.CALL:
if op in ('min', 'max'):
fn = f'std::f{op}' if ty.base in ('F', 'BF') else f'std::{op}'
else:
fn = op
return SemaNode(
SemaNodeKind.CALL, ty=ty, call_name=fn, children=(_id(fn), src0, src1)
)
return SemaNode(kind, ty=ty, children=(src0, src1))
@_register('vector_mov')
class _VectorMov(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
body = _assign(_cast(_dst(0), ty), _cast(_src(0), ty))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_unary')
class _VectorUnary(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
op = sem.operation
dtype = sem.data_type
if op == 'frexp_exp_f32' and dtype == 'f64':
src0 = _cast(_src(0, SemaType.F64), SemaType.F64)
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U32,
call_name='frexp_exp_f64',
children=(_id('frexp_exp_f64'), src0),
)
body = _assign(_cast(_dst(0), SemaType.U32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op == 'frexp_mant_f32' and dtype == 'f64':
src0 = _cast(_src(0, SemaType.F64), SemaType.F64)
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.F64,
call_name='frexp_mant_f64',
children=(_id('frexp_mant_f64'), src0),
)
body = _assign(_cast(_dst(0, SemaType.F64), SemaType.F64), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
_f64_transcendentals = {'rcp': 'rcp_f64', 'rsq': 'rsq_f64', 'sqrt': 'sqrt_f64'}
if op in _f64_transcendentals and ty.size == 64:
call_name = _f64_transcendentals[op]
src0 = _cast(_src(0, ty), ty)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=call_name,
children=(_id(call_name), src0),
)
body = _assign(_cast(_dst(0, ty), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op == 'cvt' and dtype:
call_name = f'cvt_{dtype}'
src0 = _src(0)
body = _assign(
_cast(_dst(0), SemaType.B32),
SemaNode(
SemaNodeKind.CALL,
ty=SemaType.B32,
call_name=call_name,
children=(_id(call_name), src0),
),
)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op in (
'cvt_f16_fp8',
'cvt_f16_bf8',
'cvt_norm_i16_f16',
'cvt_norm_u16_f16',
):
src0 = _src(0)
body = _assign(
_cast(_dst(0), SemaType.B32),
SemaNode(
SemaNodeKind.CALL,
ty=SemaType.B32,
call_name=op,
children=(_id(op), src0),
),
)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op and op.startswith('cvt_'):
src0 = _src(0)
else:
src0 = _cast(_src(0, ty), ty)
result = _vec_unary_expr(op, src0, ty)
body = _assign(_cast(_dst(0, ty), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_binop')
class _VectorBinop(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
op = sem.operation
name_lower = sem.name.lower()
if op == 'ldexp':
src0 = _cast(_src(0), ty)
if ty.size == 16:
src1 = _cast(
_cast(_src(1, SemaType('B', 16)), SemaType('I', 16)), SemaType.I32
)
else:
src1 = _cast(_src(1), SemaType.I32)
result = SemaNode(SemaNodeKind.LDEXP, ty=ty, children=(src0, src1))
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op in ('mul_hi', 'mulhi'):
if 'u24' in name_lower or 'u32_u24' in name_lower:
call_name = 'mul_hi_u24'
elif 'i24' in name_lower or 'i32_i24' in name_lower:
call_name = 'mul_hi_i24'
else:
call_name = 'mul_hi'
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=call_name,
children=(_id(call_name), src0, src1),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op in ('mul', 'mul_lo'):
if 'u24' in name_lower or 'u32_u24' in name_lower:
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='mul_u24',
children=(_id('mul_u24'), src0, src1),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if 'i24' in name_lower or 'i32_i24' in name_lower:
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='mul_i24',
children=(_id('mul_i24'), src0, src1),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op in ('mul', 'mul_lo') and 'u16' in name_lower:
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='mul_lo_u16',
children=(_id('mul_lo_u16'), src0, src1),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op in ('add', 'sub', 'subrev', 'rsub') and ty.base == 'I':
# Integer vector ALU wraps in two's-complement; emit raw unsigned
# arithmetic so generated C++ has no signed-overflow UB.
u_ty = SemaType('U', ty.size)
src0 = _src(0, u_ty)
src1 = _src(1, u_ty)
lhs, rhs = (src1, src0) if op in ('subrev', 'rsub') else (src0, src1)
kind = SemaNodeKind.ADD if op == 'add' else SemaNodeKind.SUB
result = SemaNode(kind, ty=u_ty, children=(lhs, rhs))
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
result = _vec_binop_expr(op, src0, src1, ty)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_ternary')
class _VectorTernary(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
op = sem.operation or 'fma'
dtype = sem.data_type
if op in ('fma', 'mad', 'fmac') and dtype in ('i24', 'u24'):
call_name = 'mad_i24' if dtype == 'i24' else 'mad_u24'
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=call_name,
children=(
_id(call_name),
_cast(_src(0), ty),
_cast(_src(1), ty),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op == 'mad' and dtype == 'u16':
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='mad_lo_u16',
children=(
_id('mad_lo_u16'),
_src(0),
_src(1),
_src(2),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op in ('add_min', 'add_max') and dtype in ('i32', 'u32'):
call_name = f'{op}_{dtype}'
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=call_name,
children=(
_id(call_name),
_cast(_src(0), ty),
_cast(_src(1), ty),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
if op in ('ashr_pk_i8_i32', 'ashr_pk_u8_i32'):
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.B32,
call_name=op,
children=(_id(op), _src(0), _src(1), _src(2)),
)
body = _assign(_cast(_dst(0), SemaType.B32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
src2 = _cast(_src(2), ty)
if op == 'mad':
mul = SemaNode(SemaNodeKind.MUL, ty=ty, children=(src0, src1))
result = SemaNode(SemaNodeKind.ADD, ty=ty, children=(mul, src2))
elif op in ('fma', 'fmac'):
result = SemaNode(SemaNodeKind.FMA, ty=ty, children=(src0, src1, src2))
else:
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=op,
children=(_id(op), src0, src1, src2),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_cmp')
class _VectorCmp(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
cmp = _make_cmp(sem.operation or "", src0, src1)
body = _assign(
SemaNode(
SemaNodeKind.ARRAYDEREF,
ty=SemaType.U1,
children=(
_id('VCC', SemaType.U64),
_id('laneId', SemaType.U32),
),
),
cmp,
)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_cmpx')
class _VectorCmpx(_ScalarDeriver):
# TODO: This derive only writes EXEC, but GFX9 (CDNA) cmpx also writes
# VCC. Must add VCC[laneId] = cmp before the EXEC write, gated on ISA
# generation, before this class can be wired through _SEMA_CLASSES.
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
cmp = _make_cmp(sem.operation or "", src0, src1)
body = _assign(
SemaNode(
SemaNodeKind.ARRAYDEREF,
ty=SemaType.U1,
children=(
_id('EXEC', SemaType.U64),
_id('laneId', SemaType.U32),
),
),
cmp,
)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_cmp_class')
class _VectorCmpClass(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), SemaType.U32)
call_name = 'fp_class_test_f64' if ty.size == 64 else 'fp_class_test'
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U1,
call_name=call_name,
children=(_id(call_name), src0, src1),
)
body = _assign(
SemaNode(
SemaNodeKind.ARRAYDEREF,
ty=SemaType.U1,
children=(
_id('VCC', SemaType.U64),
_id('laneId', SemaType.U32),
),
),
result,
)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_cmpx_class')
class _VectorCmpxClass(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), SemaType.U32)
call_name = 'fp_class_test_f64' if ty.size == 64 else 'fp_class_test'
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U1,
call_name=call_name,
children=(_id(call_name), src0, src1),
)
body = _assign(
SemaNode(
SemaNodeKind.ARRAYDEREF,
ty=SemaType.U1,
children=(
_id('EXEC', SemaType.U64),
_id('laneId', SemaType.U32),
),
),
result,
)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_add_co')
class _VectorAddCo(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
op = sem.operation or 'add'
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
if op == 'sub':
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='sub_co',
children=(_id('sub_co'), src0, src1),
)
elif op == 'rsub':
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='sub_co',
children=(_id('sub_co'), src1, src0),
)
elif op == 'addc':
cin = SemaNode(
SemaNodeKind.ARRAYDEREF,
ty=SemaType.U1,
children=(_id('VCC', SemaType.U64), _id('laneId', SemaType.U32)),
)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='addc_co',
children=(_id('addc_co'), src0, src1, cin),
)
elif op == 'subbc':
cin = SemaNode(
SemaNodeKind.ARRAYDEREF,
ty=SemaType.U1,
children=(_id('VCC', SemaType.U64), _id('laneId', SemaType.U32)),
)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='subbc_co',
children=(_id('subbc_co'), src0, src1, cin),
)
elif op == 'subbrevco':
cin = SemaNode(
SemaNodeKind.ARRAYDEREF,
ty=SemaType.U1,
children=(_id('VCC', SemaType.U64), _id('laneId', SemaType.U32)),
)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='subbc_co',
children=(_id('subbc_co'), src1, src0, cin),
)
else:
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='add_co',
children=(_id('add_co'), src0, src1),
)
stmts = [
_assign(_cast(_dst(0), ty), result),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_cndmask')
class _VectorCndmask(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
src0 = _cast(_src(0), ty)
src1 = _cast(_src(1), ty)
cond = SemaNode(
SemaNodeKind.ARRAYDEREF,
ty=SemaType.U1,
children=(
_id('VCC', SemaType.U64),
_id('laneId', SemaType.U32),
),
)
result = SemaNode(SemaNodeKind.TERNARY, ty=ty, children=(cond, src1, src0))
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_readfirstlane')
class _VectorReadfirstlane(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = _assign(
_cast(_dst(0), SemaType.U32),
SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U32,
call_name='v_readfirstlane',
children=(_id('v_readfirstlane'), _cast(_src(0), SemaType.U32)),
),
)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('vector_readlane')
class _VectorReadlane(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = _assign(
_cast(_dst(0), SemaType.U32),
SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U32,
call_name='v_readlane',
children=(
_id('v_readlane'),
_cast(_src(0), SemaType.U32),
_cast(_src(1), SemaType.U32),
),
),
)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('vector_writelane')
class _VectorWritelane(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U32,
call_name='v_writelane',
children=(
_id('v_writelane'),
_cast(_dst(0), SemaType.U32),
_cast(_src(0), SemaType.U32),
_cast(_src(1), SemaType.U32),
),
)
body = _assign(_cast(_dst(0), SemaType.U32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_swap')
class _VectorSwap(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
stmts = [
_assign(_id('tmp', ty), _cast(_dst(0), ty)),
_assign(_cast(_dst(0), ty), _cast(_src(0), ty)),
_assign(_cast(_src(0), ty), _id('tmp', ty)),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_fmaak')
class _VectorFmaak(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.FMA,
ty=ty,
children=(_cast(_src(0), ty), _cast(_src(1), ty), _cast(_src(2), ty)),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_fmamk')
class _VectorFmamk(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.FMA,
ty=ty,
children=(_cast(_src(0), ty), _cast(_src(1), ty), _cast(_src(2), ty)),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
# =========================================================================
# Memory semantic classes
# =========================================================================
def _elem_type(elem_size: int | None, sign_extend: bool = False) -> SemaType:
"""Map element byte size to SemaType."""
sz = (elem_size or 4) * 8
if sign_extend:
return SemaType('I', sz)
return SemaType('B', sz)
def _mem_read(addr: SemaNode, elem_ty: SemaType) -> SemaNode:
return SemaNode(
SemaNodeKind.ARRAYDEREF,
ty=elem_ty,
children=(
_id('MEM', elem_ty),
addr,
),
)
def _mem_write(addr: SemaNode, value: SemaNode) -> SemaNode:
return _assign(
SemaNode(
SemaNodeKind.ARRAYDEREF,
children=(
_id('MEM'),
addr,
),
),
value,
)
def _addr_call(
calc_name: str, *args: SemaNode, addr_ty: SemaType = SemaType.U32
) -> SemaNode:
return SemaNode(
SemaNodeKind.CALL,
ty=addr_ty,
call_name=calc_name,
children=(_id(calc_name), *args),
)
@_register('smem_load')
class _SmemLoad(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size, sem.sign_extend)
addr = _addr_call(
'CalcScalarGlobalAddr',
_cast(_src(0), SemaType.U64),
_cast(_src(1), SemaType.U32),
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_assign(
_cast(_dst(0), elem_ty), _mem_read(_id('addr', SemaType.U32), elem_ty)
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('smem_store')
class _SmemStore(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
addr = _addr_call(
'CalcScalarGlobalAddr',
_cast(_src(0), SemaType.U64),
_cast(_src(1), SemaType.U32),
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_mem_write(_id('addr', SemaType.U32), _cast(_src(2), elem_ty)),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('buffer_load')
class _BufferLoad(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size, sem.sign_extend)
addr = _addr_call(
'CalcBufferAddr',
_cast(_src(0), SemaType.B32),
_cast(_src(1), SemaType.B32),
_cast(_src(2), SemaType.B32),
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_assign(
_cast(_dst(0), elem_ty), _mem_read(_id('addr', SemaType.U32), elem_ty)
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('buffer_store')
class _BufferStore(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
addr = _addr_call(
'CalcBufferAddr',
_cast(_src(0), SemaType.B32),
_cast(_src(1), SemaType.B32),
_cast(_src(2), SemaType.B32),
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_mem_write(_id('addr', SemaType.U32), _cast(_src(3), elem_ty)),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('buffer_atomic')
class _BufferAtomic(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
addr = _addr_call(
'CalcBufferAddr',
_cast(_src(0), SemaType.B32),
_cast(_src(1), SemaType.B32),
_cast(_src(2), SemaType.B32),
)
op = sem.operation or 'add'
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_assign(_id('old', elem_ty), _mem_read(_id('addr', SemaType.U32), elem_ty)),
SemaNode(
SemaNodeKind.CALL,
ty=elem_ty,
call_name=f'atomic_{op}',
children=(
_id(f'atomic_{op}'),
_id('addr', SemaType.U32),
_cast(_src(3), elem_ty),
),
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('flat_load')
class _FlatLoad(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size, sem.sign_extend)
addr = _addr_call(
'CalcFlatAddr', _cast(_src(0), SemaType.U64), addr_ty=SemaType.U64
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_assign(
_cast(_dst(0), elem_ty), _mem_read(_id('addr', SemaType.U32), elem_ty)
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('flat_store')
class _FlatStore(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
addr = _addr_call(
'CalcFlatAddr', _cast(_src(0), SemaType.U64), addr_ty=SemaType.U64
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_mem_write(_id('addr', SemaType.U32), _cast(_src(1), elem_ty)),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('flat_atomic')
class _FlatAtomic(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
op = sem.operation or 'add'
addr = _addr_call(
'CalcFlatAddr', _cast(_src(0), SemaType.U64), addr_ty=SemaType.U64
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
SemaNode(
SemaNodeKind.CALL,
ty=elem_ty,
call_name=f'atomic_{op}',
children=(
_id(f'atomic_{op}'),
_id('addr', SemaType.U32),
_cast(_src(1), elem_ty),
),
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_read')
class _DsRead(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size, sem.sign_extend)
addr = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET', SemaType.U32)
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_assign(
_cast(_dst(0), elem_ty), _mem_read(_id('addr', SemaType.U32), elem_ty)
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_write')
class _DsWrite(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
addr = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET', SemaType.U32)
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_mem_write(_id('addr', SemaType.U32), _cast(_src(1), elem_ty)),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_read2')
class _DsRead2(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
addr0 = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET0', SemaType.U32)
)
addr1 = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET1', SemaType.U32)
)
stmts = [
_assign(_id('addr0', SemaType.U32), addr0),
_assign(_id('addr1', SemaType.U32), addr1),
_assign(
_cast(_dst(0), elem_ty), _mem_read(_id('addr0', SemaType.U32), elem_ty)
),
_assign(
_cast(_dst(1), elem_ty), _mem_read(_id('addr1', SemaType.U32), elem_ty)
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_write2')
class _DsWrite2(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
addr0 = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET0', SemaType.U32)
)
addr1 = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET1', SemaType.U32)
)
stmts = [
_assign(_id('addr0', SemaType.U32), addr0),
_assign(_id('addr1', SemaType.U32), addr1),
_mem_write(_id('addr0', SemaType.U32), _cast(_src(1), elem_ty)),
_mem_write(_id('addr1', SemaType.U32), _cast(_src(2), elem_ty)),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_atomic')
class _DsAtomic(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
op = sem.operation or 'add'
addr = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET', SemaType.U32)
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
SemaNode(
SemaNodeKind.CALL,
ty=elem_ty,
call_name=f'atomic_{op}',
children=(
_id(f'atomic_{op}'),
_id('addr', SemaType.U32),
_cast(_src(1), elem_ty),
),
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_atomic2')
class _DsAtomic2(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
op = sem.operation or 'swap'
addr0 = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET0', SemaType.U32)
)
addr1 = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET1', SemaType.U32)
)
def atomic_swap(addr_name: str, source_index: int) -> SemaNode:
return SemaNode(
SemaNodeKind.CALL,
ty=elem_ty,
call_name=f'atomic_{op}',
children=(
_id(f'atomic_{op}'),
_id(addr_name, SemaType.U32),
_cast(_src(source_index), elem_ty),
),
)
stmts = [
_assign(_id('addr0', SemaType.U32), addr0),
_assign(_id('addr1', SemaType.U32), addr1),
_assign(_cast(_dst(0), elem_ty), atomic_swap('addr0', 1)),
_assign(_cast(_dst(1), elem_ty), atomic_swap('addr1', 2)),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_read_addtid')
class _DsReadAddtid(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
addr = _addr_call(
'CalcDsAddr', _id('laneId', SemaType.U32), _id('OFFSET', SemaType.U32)
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_assign(
_cast(_dst(0), elem_ty), _mem_read(_id('addr', SemaType.U32), elem_ty)
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_write_addtid')
class _DsWriteAddtid(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
elem_ty = _elem_type(sem.elem_size)
addr = _addr_call(
'CalcDsAddr', _id('laneId', SemaType.U32), _id('OFFSET', SemaType.U32)
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_mem_write(_id('addr', SemaType.U32), _cast(_src(0), elem_ty)),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_permute')
class _DsPermute(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
op = sem.operation or 'bpermute'
call_name = f'ds_{op}'
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U32,
call_name=call_name,
children=(
_id(call_name),
_cast(_src(0), SemaType.U32),
_cast(_src(1), SemaType.U32),
),
)
body = _assign(_cast(_dst(0), SemaType.U32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_swizzle')
class _DsSwizzle(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U32,
call_name='ds_swizzle',
children=(
_id('ds_swizzle'),
_cast(_src(0), SemaType.U32),
_id('OFFSET', SemaType.U32),
),
)
body = _assign(_cast(_dst(0), SemaType.U32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_read_tr_b4')
class _DsReadTrB4(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
addr = _addr_call(
'CalcDsAddr', _cast(_src(0), SemaType.U32), _id('OFFSET', SemaType.U32)
)
stmts = [
_assign(_id('addr', SemaType.U32), addr),
_assign(
_cast(_dst(0), SemaType.B32),
_mem_read(_id('addr', SemaType.U32), SemaType.B32),
),
]
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('ds_read_tr_b6')
class _DsReadTrB6(_DsReadTrB4):
pass
@_register('ds_read_tr_b8')
class _DsReadTrB8(_DsReadTrB4):
pass
@_register('ds_read_tr_b16')
class _DsReadTrB16(_DsReadTrB4):
pass
# =========================================================================
# Packed, matrix, special, and remaining semantic classes
# =========================================================================
# --- Packed 16-bit / 32-bit ops ---
@_register('pk_binop_f32')
class _PkBinopF32(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = _vec_binop_expr(
sem.operation, _cast(_src(0), ty), _cast(_src(1), ty), ty
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('pk_ternary')
class _PkTernary(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.FMA,
ty=ty,
children=(_cast(_src(0), ty), _cast(_src(1), ty), _cast(_src(2), ty)),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('pk_ternary_f32')
class _PkTernaryF32(_PkTernary):
pass
@_register('pk_mov_b32')
class _PkMovB32(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = _assign(_cast(_dst(0), SemaType.B32), _cast(_src(0), SemaType.B32))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
# --- Dot product ops ---
def _derive_dot(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=sem.operation or 'dot',
children=(
_id(sem.operation or 'dot'),
_cast(_src(0), ty),
_cast(_src(1), ty),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_dot')
class _VectorDot(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
return _derive_dot(sem)
@_register('vector_dot2c_bf16')
class _VectorDot2cBf16(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
return _derive_dot(sem)
for _dot_cls in (
'dot2_f32_f16',
'dot2_f32_bf16',
'dot2_i32_i16',
'dot2_u32_u16',
'dot4_i32_i8',
'dot4_i32_iu8',
'dot4_u32_u8',
'dot4_f32_fp8',
'dot8_i32_i4',
'dot8_i32_iu4',
'dot8_u32_u4',
):
@_register(_dot_cls)
class _DotN(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
return _derive_dot(sem)
# --- Mad mix ops ---
@_register('mad_mix_f32')
class _MadMixF32(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = SemaType.F32
result = SemaNode(
SemaNodeKind.FMA,
ty=ty,
children=(_cast(_src(0), ty), _cast(_src(1), ty), _cast(_src(2), ty)),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('mad_mixlo_f16')
class _MadMixloF16(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = SemaType.F32
result = SemaNode(
SemaNodeKind.FMA,
ty=ty,
children=(_cast(_src(0), ty), _cast(_src(1), ty), _cast(_src(2), ty)),
)
body = _assign(_cast(_dst(0), SemaType.F16), _cast(result, SemaType.F16))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('mad_mixhi_f16')
class _MadMixhiF16(_MadMixloF16):
pass
# --- Matrix ops ---
@_register('mfma')
class _Mfma(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='mfma_compute',
children=(
_id('mfma_compute'),
_cast(_src(0), ty),
_cast(_src(1), ty),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('accvgpr_read')
class _AccvgprRead(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = _assign(_cast(_dst(0), SemaType.B32), _cast(_src(0), SemaType.B32))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('accvgpr_write')
class _AccvgprWrite(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = _assign(_cast(_dst(0), SemaType.B32), _cast(_src(0), SemaType.B32))
return SemaBlock(sem.name, ExecModel.VECTOR, body)
# --- Cross-lane / special vector ops ---
@_register('vector_permlane16')
class _VectorPermlane16(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U32,
call_name='v_permlane16',
children=(
_id('v_permlane16'),
_cast(_src(0), SemaType.U32),
_cast(_src(1), SemaType.U32),
_cast(_src(2), SemaType.U32),
),
)
body = _assign(_cast(_dst(0), SemaType.U32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_permlanex16')
class _VectorPermlanex16(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U32,
call_name='v_permlanex16',
children=(
_id('v_permlanex16'),
_cast(_src(0), SemaType.U32),
_cast(_src(1), SemaType.U32),
_cast(_src(2), SemaType.U32),
),
)
body = _assign(_cast(_dst(0), SemaType.U32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_mbcnt')
class _VectorMbcnt(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U32,
call_name='mbcnt',
children=(
_id('mbcnt'),
_cast(_src(0), SemaType.U32),
_cast(_src(1), SemaType.U32),
),
)
body = _assign(_cast(_dst(0), SemaType.U32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_bitop3')
class _VectorBitop3(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='bitop3',
children=(
_id('bitop3'),
_cast(_src(0), ty),
_cast(_src(1), ty),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_pack_b32_f16')
class _VectorPackB32F16(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.B32,
call_name='pack_b32_f16',
children=(
_id('pack_b32_f16'),
_cast(_src(0), SemaType.F16),
_cast(_src(1), SemaType.F16),
),
)
body = _assign(_cast(_dst(0), SemaType.B32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_mad_32_16')
class _VectorMad3216(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='mad_32_16',
children=(
_id('mad_32_16'),
_cast(_src(0), ty),
_cast(_src(1), ty),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_mad_64_32')
class _VectorMad6432(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = SemaType.U64
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='mad_64_32',
children=(
_id('mad_64_32'),
_cast(_src(0), SemaType.U32),
_cast(_src(1), SemaType.U32),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_div_fixup')
class _VectorDivFixup(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='div_fixup',
children=(
_id('div_fixup'),
_cast(_src(0), ty),
_cast(_src(1), ty),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_div_fmas')
class _VectorDivFmas(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='div_fmas',
children=(
_id('div_fmas'),
_cast(_src(0), ty),
_cast(_src(1), ty),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('vector_div_scale')
class _VectorDivScale(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='div_scale',
children=(
_id('div_scale'),
_cast(_src(0), ty),
_cast(_src(1), ty),
_cast(_src(2), ty),
),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
# --- Conversion pack ops ---
def _derive_cvt_pk(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
op = sem.operation or sem.semantic_class
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.B32,
call_name=op,
children=(_id(op), _cast(_src(0), ty), _cast(_src(1), ty)),
)
body = _assign(_cast(_dst(0), SemaType.B32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
for _cvt_cls in (
'vector_cvt_pk',
'vector_cvt_pk_bf16_f32',
'vector_cvt_pk_f16_f32',
'vector_cvt_pk_u8_f32',
'vector_cvt_pknorm',
'vector_cvt_pkrtz_f16_f32',
'vector_cvt_sr_bf16_f32',
'vector_cvt_sr_f16_f32',
):
@_register(_cvt_cls)
class _CvtPk(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
return _derive_cvt_pk(sem)
@_register('scalar_cvt_pkrtz_f16_f32')
class _ScalarCvtPkRtzF16F32(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.B32,
call_name='cvt_pkrtz_f16_f32',
children=(
_id('cvt_pkrtz_f16_f32'),
_cast(_src(0), SemaType.F32),
_cast(_src(1), SemaType.F32),
),
)
body = _assign(_cast(_dst(0), SemaType.B32), result)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
# --- Remaining scalar ops ---
@_register('scalar_movk')
class _ScalarMovk(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = _assign(_cast(_dst(0), SemaType.U32), _cast(_src(0), SemaType.U32))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_cmovk')
class _ScalarCmovk(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = SemaNode(
SemaNodeKind.IF,
children=(
_id('SCC', SemaType.U1),
_assign(_cast(_dst(0), SemaType.U32), _cast(_src(0), SemaType.U32)),
),
)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_addk')
class _ScalarAddk(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
src = SemaNode(
SemaNodeKind.SIGNEXT_FROM_BIT,
ty=SemaType.I32,
children=(_cast(_src(0), SemaType.U32), _lit('15')),
)
old_dst = _cast(_dst(0), SemaType.U32)
result = SemaNode(
SemaNodeKind.ADD,
ty=SemaType.U32,
children=(old_dst, _cast(src, SemaType.U32)),
)
overflow = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U1,
call_name='signed_add_overflows',
children=(_id('signed_add_overflows'), old_dst, _cast(src, SemaType.U32)),
)
body = SemaNode(
SemaNodeKind.SEQ,
children=(
_scc_write(overflow),
_assign(_cast(_dst(0), SemaType.U32), result),
),
)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_mulk')
class _ScalarMulk(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.MUL,
ty=SemaType.U32,
children=(_cast(_dst(0), SemaType.U32), _cast(_src(0), SemaType.U32)),
)
body = _assign(_cast(_dst(0), SemaType.U32), result)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_cselect')
class _ScalarCselect(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.TERNARY,
ty=ty,
children=(
_id('SCC', SemaType.U1),
_cast(_src(0, ty), ty),
_cast(_src(1, ty), ty),
),
)
body = _assign(_cast(_dst(0, ty), ty), result)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_wrexec')
class _ScalarWrexec(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
is_b32 = sem.data_type == 'b32'
src_ty = SemaType.U32 if is_b32 else SemaType.U64
exec_id = 'EXEC' if is_b32 else 'EXEC_RAW'
stmts = [
_assign(_id('src', SemaType.U64), _cast(_src(0), src_ty)),
_assign(_id('old_exec', SemaType.U64), _id(exec_id, SemaType.U64)),
]
src = _id('src', SemaType.U64)
old_exec = _id('old_exec', SemaType.U64)
if sem.operation in ('andn1', 'and_not0'):
result = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U64,
children=(
old_exec,
SemaNode(SemaNodeKind.BITNEG, ty=SemaType.U64, children=(src,)),
),
)
elif sem.operation in ('andn2', 'and_not1'):
result = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U64,
children=(
src,
SemaNode(
SemaNodeKind.BITNEG, ty=SemaType.U64, children=(old_exec,)
),
),
)
else:
raise ValueError(f'Unsupported WREXEC operation: {sem.operation}')
if is_b32:
result = SemaNode(
SemaNodeKind.AND,
ty=SemaType.U64,
children=(result, _lit('0xffffffff', SemaType.U64)),
)
stmts.extend(
(
_assign(_id('result', SemaType.U64), result),
_assign(_cast(_dst(0), src_ty), _cast(_id('result'), src_ty)),
_assign(_id(exec_id, SemaType.U64), _id('result', SemaType.U64)),
_scc_write(
SemaNode(
SemaNodeKind.NE,
ty=SemaType.U1,
children=(
_id('result', SemaType.U64),
_lit('0', SemaType.U64),
),
)
),
)
)
body = SemaNode(SemaNodeKind.SEQ, children=tuple(stmts))
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_call')
class _ScalarCall(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U64,
call_name='scalar_call',
children=(_id('scalar_call'), _cast(_src(0), SemaType.U64)),
)
body = _assign(_cast(_dst(0), SemaType.U64), result)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('scalar_sendmsg_rtn')
class _ScalarSendmsgRtn(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
ty = _dtype_to_sema(sem.data_type)
result = SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name='scalar_sendmsg_rtn',
children=(_id('scalar_sendmsg_rtn'), _cast(_src(0), SemaType.U32)),
)
body = _assign(_cast(_dst(0), ty), result)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
# --- Control flow / system ---
@_register('branch')
class _Branch(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U64,
call_name='branch',
children=(_id('branch'), _cast(_src(0), SemaType.U64)),
)
return SemaBlock(sem.name, ExecModel.BRANCH, body)
@_register('cbranch')
class _Cbranch(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
cond = sem.branch_condition or 'scc1'
body = SemaNode(
SemaNodeKind.IF,
children=(
SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U1,
call_name=f'eval_{cond}',
children=(_id(f'eval_{cond}'),),
),
SemaNode(
SemaNodeKind.CALL,
ty=SemaType.U64,
call_name='branch',
children=(_id('branch'), _cast(_src(0), SemaType.U64)),
),
),
)
return SemaBlock(sem.name, ExecModel.BRANCH, body)
@_register('endpgm')
class _Endpgm(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = SemaNode(
SemaNodeKind.CALL, call_name='endpgm', children=(_id('endpgm'),)
)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('nop')
class _Nop(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
return SemaBlock(
sem.name, ExecModel.SCALAR, SemaNode(SemaNodeKind.SEQ, children=())
)
@_register('true_nop')
class _TrueNop(_Nop):
pass
@_register('barrier')
class _Barrier(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = SemaNode(
SemaNodeKind.CALL, call_name='barrier', children=(_id('barrier'),)
)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('waitcnt')
class _Waitcnt(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = SemaNode(
SemaNodeKind.CALL, call_name='waitcnt', children=(_id('waitcnt'),)
)
return SemaBlock(sem.name, ExecModel.SCALAR, body)
@_register('wait_counter')
class _WaitCounter(_Waitcnt):
pass
@_register('dcache_inv')
class _DcacheInv(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
return SemaBlock(
sem.name, ExecModel.SCALAR, SemaNode(SemaNodeKind.SEQ, children=())
)
@_register('dcache_wb')
class _DcacheWb(_DcacheInv):
pass
@_register('gl1_inv')
class _Gl1Inv(_DcacheInv):
pass
@_register('gl1_wbinv')
class _Gl1Wbinv(_DcacheInv):
pass
@_register('export')
class _Export(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = SemaNode(
SemaNodeKind.CALL,
call_name='export_pixel',
children=(_id('export_pixel'), _cast(_src(0), SemaType.B32)),
)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('interp')
class _Interp(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
result = SemaNode(
SemaNodeKind.CALL,
ty=SemaType.F32,
call_name='interp',
children=(_id('interp'), _cast(_src(0), SemaType.F32)),
)
body = _assign(_cast(_dst(0), SemaType.F32), result)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
@_register('lds_direct')
class _LdsDirect(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
body = _assign(
_cast(_dst(0), SemaType.B32),
SemaNode(
SemaNodeKind.CALL,
ty=SemaType.B32,
call_name='lds_direct_load',
children=(_id('lds_direct_load'),),
),
)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
# --- Image ops ---
def _derive_image(sem: InstructionSemantics, is_store: bool = False) -> SemaBlock:
op = sem.semantic_class
ty = SemaType.B32
if is_store:
body = SemaNode(
SemaNodeKind.CALL,
call_name=op,
children=(_id(op), _cast(_src(0), ty), _cast(_src(1), ty)),
)
else:
body = _assign(
_cast(_dst(0), ty),
SemaNode(
SemaNodeKind.CALL,
ty=ty,
call_name=op,
children=(_id(op), _cast(_src(0), ty), _cast(_src(1), ty)),
),
)
return SemaBlock(sem.name, ExecModel.VECTOR, body)
for _img_cls in ('image_load', 'image_sample', 'image_query', 'image_bvh'):
@_register(_img_cls)
class _ImageLoad(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
return _derive_image(sem, is_store=False)
@_register('image_store')
class _ImageStore(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
return _derive_image(sem, is_store=True)
@_register('image_atomic')
class _ImageAtomic(_ScalarDeriver):
@staticmethod
def derive(sem: InstructionSemantics) -> SemaBlock:
return _derive_image(sem, is_store=False)