Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
87353dc
added d16 loads to _dst_is_also_source function in codegen so their p…
theSK2005 Jul 8, 2026
23b0ab6
moved from using _dst_is_also_source to track uses to implicit_uses a…
theSK2005 Jul 10, 2026
d4d4a1c
pre-commit fix
theSK2005 Jul 10, 2026
42aa6b0
adding store negative case
theSK2005 Jul 13, 2026
fbdcdc4
fixed semantics error causing D16 buffer loads to not receive implici…
theSK2005 Jul 30, 2026
dc5fc9b
fixed d16 xyz case
theSK2005 Jul 30, 2026
88b7264
fixed support for gfx1250, use implicit_use_operands
theSK2005 Jul 30, 2026
11a9d75
Merge branch 'develop' into users/skushwah/d16-partial-load
theSK2005 Aug 4, 2026
650e263
added requested assert and regression tests
theSK2005 Aug 4, 2026
f8046c8
Merge branch 'develop' into users/skushwah/d16-partial-load
theSK2005 Aug 4, 2026
4a1458d
Merge branch 'develop' into users/skushwah/d16-partial-load
theSK2005 Aug 4, 2026
e444a9a
addressing Tony's review
theSK2005 Aug 5, 2026
381e067
pre-commit fix
theSK2005 Aug 5, 2026
8c86ef8
addressing Jakub's comments
theSK2005 Aug 6, 2026
1fcf40b
addressed Jakub's comments
theSK2005 Aug 7, 2026
f37993e
Merge remote-tracking branch 'origin/develop' into users/skushwah/d16…
theSK2005 Aug 7, 2026
1bba5e0
[rocjitsu] Fixing Bad Merge from EXEC Tracking PR
theSK2005 Aug 7, 2026
adfad6a
merge conflict resolutions
theSK2005 Aug 7, 2026
4ff6ba8
Merge branch 'develop' into users/skushwah/d16-partial-load
theSK2005 Aug 7, 2026
3da3e77
Merge branch 'develop' into users/skushwah/d16-partial-load
theSK2005 Aug 7, 2026
ba3368d
Merge branch 'develop' into users/skushwah/d16-partial-load
theSK2005 Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions emulation/rocjitsu/lib/python/amdisa/codegen/_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2862,6 +2862,21 @@ def _op_sel_hi_2_expr(self, enc_name: str) -> str:
'V_DOT4C_',
'V_DOT8C_',
)
# D16 load semantic classes. Stores share the d16_hi/d16_lo flags, so the
# class gate is what restricts the partial-def treatment to loads.
# global/scratch loads use the 'flat_load' class. Byte/short buffer D16 loads
# (e.g. buffer_load_ubyte_d16) use 'buffer_load'/'tbuffer_load' and execute
# correctly; packed D16 FORMAT loads use the non-executable
# 'buffer_load_format_d16' class (metadata-only, see semantics._derive_buffer_data).
_D16_LOAD_CLASSES = frozenset(
Comment thread
theSK2005 marked this conversation as resolved.
Resolved
{
'flat_load',
'buffer_load',
'tbuffer_load',
'ds_read',
'buffer_load_format_d16',
}
)

def _dst_is_also_source(self, inst: Instruction) -> bool:
"""Return True if the instruction reads from its destination operand.
Expand Down Expand Up @@ -2891,6 +2906,24 @@ def _instruction_reads_primary_dst(
or name.startswith(self._READS_DST_DOT_PREFIXES)
)

def _d16_load_reads_dst(self, inst: Instruction) -> bool:
"""Return True for a D16(_HI) load whose last dst register is partial.

The last register is partial when the written bytes (num_elems *
elem_size) do not fill whole 32-bit registers (e.g. ushort_d16, or
format_d16_xyz).
"""
if not self.semantics:
return False
sem = self.semantics.instructions.get(inst.name)
if not sem or sem.num_elems is None or sem.elem_size is None:
return False
return (
(sem.d16_hi or sem.d16_lo)
and (sem.num_elems * sem.elem_size) % 4 != 0
and sem.semantic_class in self._D16_LOAD_CLASSES
Comment thread
theSK2005 marked this conversation as resolved.
Resolved
)

def _operand_size_override(
self,
enc_name: str,
Expand Down Expand Up @@ -4649,6 +4682,16 @@ def _semantic_reg_class(opnd: Operand) -> RegClass:
if cls in ('buffer_store', 'tbuffer_store'):
return self._gen_buffer_store(dst_ops, src_ops, sem, cls)

if cls in ('buffer_load_format_d16', 'buffer_store_format_d16'):
Comment thread
theSK2005 marked this conversation as resolved.
Resolved
# Packed D16 FORMAT execution (two 16-bit components per VGPR) is not
# modeled by the memory pipeline; the class is metadata-only so the
# partial-def liveness of D16 FORMAT loads is still emitted.
return (
' (void)wf;\n'
' throw util::UnimplementedInst(mnemonic()); '
'// packed D16 FORMAT execution intentionally unimplemented'
)

if cls in (
'ds_read',
'ds_read2',
Expand Down Expand Up @@ -6760,6 +6803,43 @@ def gen_insts(self) -> None:
dst_idx = 0
vgpr_msb_src_role_idx = 0
reads_dst = self._dst_is_also_source(inst)
# D16(_HI) loads read the destination they partially write.
# Model it as an implicit use so vdst stays out of the
# printed operand list (see _d16_load_reads_dst). The
# override declaration/definition is emitted below, sharing
# the path with generic partial defs (buffer/tbuffer loads
# name the dest 'vdata' and are sized as a full 32-bit
# register, so _partial_def_outputs does not catch them).
d16_implicit_use_opnd = None
# Offset (in 32-bit registers) of the one partially-written
# register within the destination: only the last register of
# an odd-count FORMAT load (e.g. format_d16_xyz) is partial.
d16_partial_reg_offset = 0
if self._d16_load_reads_dst(inst):
# FLAT/GLOBAL/SCRATCH/DS name the dest 'vdst'; MUBUF/MTBUF
# (buffer/tbuffer) name it 'vdata'.
d16_implicit_use_opnd = next(
Comment thread
theSK2005 marked this conversation as resolved.
Resolved
(
o.name
for o in inst.operands
if o.is_output and o.name in ('vdst', 'sdst', 'vdata')
),
None,
)
# Fail loudly: _d16_load_reads_dst() matched, so a
# destination read must be modeled. A None here (e.g. a
# future dest rename) would silently skip both the
# implicit_uses declaration and definition below, losing
# the preserved-destination read from liveness.
assert d16_implicit_use_opnd is not None, (
f'{inst.name}: D16 load reads its destination, but no '
"'vdst'/'sdst'/'vdata' output operand was found to model "
'the preserved-destination read'
)
d16_sem = self.semantics.instructions.get(inst.name)
d16_total_bytes = d16_sem.num_elems * d16_sem.elem_size
d16_vgpr_count = (d16_total_bytes + 3) // 4 # round up
d16_partial_reg_offset = d16_vgpr_count - 1
# These gfx1250-only WMMA source-format fields derive the
# src0/src1 operand sizes from the instruction shape.
gfx1250_f8f6f4_shape = self._gfx1250_f8f6f4_wmma_shape(inst)
Expand Down Expand Up @@ -7078,6 +7158,23 @@ def gen_insts(self) -> None:
'override'
)
)
elif d16_implicit_use_opnd:
public_members.append(
cgen.Statement(
'void implicit_uses(RegisterSet &uses) const override'
)
)
# Operand-backed twin for VGPR-MSB banked profiles
# (gfx1250), which read banked VGPRs only from
# implicit_use_operands(); see _partial_def_outputs above.
if self.isa_spec.profile.uses_vgpr_msb_indexing:
public_members.append(
cgen.Statement(
'void implicit_use_operands('
'std::vector<const ::rocjitsu::Operand *> &operands) const '
'override'
)
)
elif _writelane_implicit_use_opnd:
public_members.append(
cgen.Statement(
Expand Down Expand Up @@ -7146,6 +7243,8 @@ def gen_insts(self) -> None:
'buffer_atomic',
'tbuffer_load',
'tbuffer_store',
'buffer_load_format_d16',
'buffer_store_format_d16',
'ds_read',
'ds_read2',
'ds_write',
Expand Down Expand Up @@ -8205,6 +8304,59 @@ def _dpp_write_mask_lines(
f'}}'
)
)
elif d16_implicit_use_opnd:
# Single-register loads read the whole destination; a
# multi-register FORMAT load only reads its last (partial)
# register, so expand just that one.
if d16_partial_reg_offset:
d16_expand_stmt = (
f'uses.expand(RegisterRef{{r->cls, '
f'static_cast<uint16_t>(r->index + '
f'{d16_partial_reg_offset}), 1}});'
)
else:
d16_expand_stmt = 'uses.expand(*r);'
# Older MUBUF encodings redirect the load to LDS when
# inst_.lds is set, leaving no VGPR destination; only guard
# the preserved-destination read where such a field exists
# (newer encodings have no lds field).
d16_lds_guarded = 'lds' in inst_field_names
d16_guard = ' if (!inst_.lds)\n' if d16_lds_guarded else ''
d16_ind = ' ' if d16_lds_guarded else ' '
inst_impls.append(
cgen.Line(
f'void {inst.fmt_name}::implicit_uses'
f'(RegisterSet &uses) const {{\n'
f' {inst.fmt_true_enc_name}::implicit_uses(uses);\n'
f'{d16_guard}'
f'{d16_ind}if (auto r = {d16_implicit_use_opnd}.to_register_ref())\n'
f'{d16_ind} {d16_expand_stmt}\n'
f'}}'
)
)
# Operand-backed twin (see header decl): gfx1250 clears
# the VGPR class from implicit_uses() and reads banked
# VGPRs only from implicit_use_operands(). gfx1250 has
# only single-register D16 loads, so the whole operand is
# the partial register; the operand hook cannot express a
# partial last register of a multi-register def.
if self.isa_spec.profile.uses_vgpr_msb_indexing:
assert d16_partial_reg_offset == 0, (
'multi-register D16 partial def is not '
'expressible via implicit_use_operands'
)
inst_impls.append(
cgen.Line(
f'void {inst.fmt_name}::implicit_use_operands'
f'(std::vector<const ::rocjitsu::Operand *> &operands) '
f'const {{\n'
f' {inst.fmt_true_enc_name}::implicit_use_operands(operands);\n'
f'{d16_guard}'
f'{d16_ind}if ({d16_implicit_use_opnd}.to_register_ref())\n'
f'{d16_ind} operands.push_back(&{d16_implicit_use_opnd});\n'
f'}}'
)
)
elif _writelane_implicit_use_opnd:
inst_impls.append(
cgen.Line(
Expand Down
138 changes: 88 additions & 50 deletions emulation/rocjitsu/lib/python/amdisa/semantics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2028,18 +2028,82 @@ def _derive_flat(name: str) -> InstructionSemantics | None:
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),
_BUFFER_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),
'FORMAT_D16_HI_X': (2, 1, False),
}


def _normalize_buffer_format_suffix(suffix: str) -> str:
"""Map RDNA3+ ``D16[_HI]_FORMAT_*`` ordering back to legacy ``FORMAT_D16[_HI]_*``."""
if suffix.startswith('D16_HI_FORMAT'):
return 'FORMAT_D16_HI' + suffix[len('D16_HI_FORMAT') :]
if suffix.startswith('D16_FORMAT'):
return 'FORMAT_D16' + suffix[len('D16_FORMAT') :]
return suffix


def _derive_buffer_data(
name: str,
suffix: str,
is_store: bool,
typed: bool,
typed_format_executable: bool = True,
) -> InstructionSemantics | None:
"""Classify a buffer/typed-buffer LOAD/STORE data suffix.

Untyped (MUBUF) and typed (MTBUF) buffers -- and both under the unified RDNA4
``ENC_VBUFFER`` -- share identical data-layout logic; only the executable
class prefix differs (``buffer_*`` vs ``tbuffer_*``).

D16 FORMAT loads/stores pack two 16-bit components per VGPR. That packed
layout is not modeled by the memory pipeline, so they are given dedicated
non-executable classes (``buffer_{load,store}_format_d16``) that carry the
partial-def metadata (num_elems/elem_size/d16 flags) for liveness without
activating incorrect execution. Non-D16 FORMAT (one dword per component) and
byte/short/dword data execute correctly and keep their normal classes; the
FORMAT variants are modeled as raw dword moves without DFMT/NFMT
(data/numeric format) conversion.

``typed_format_executable`` gates the executable ``tbuffer_*`` class for
non-D16 typed FORMAT ops. Real MTBUF encodings supply mtbuf-style addressing
and keep it enabled; RDNA4 folds typed buffers into VBUFFER (mubuf-style
addressing, no ``mtbuf_calculate_addresses``), so it is disabled there,
leaving non-D16 typed ops unclassified (``nop``) as before -- only the D16
typed loads need the partial-def metadata.
"""
normalized = _normalize_buffer_format_suffix(suffix)
flat_info = _FLAT_DATA_MAP.get(normalized)
fmt_info = None if flat_info else _BUFFER_FORMAT_MAP.get(normalized)
info = flat_info or fmt_info
if not info:
return None
esz, ne, se = info
if fmt_info is not None and esz == 2: # D16 FORMAT: packed, metadata-only
cls = 'buffer_store_format_d16' if is_store else 'buffer_load_format_d16'
elif typed and not typed_format_executable:
return None # non-D16 typed FORMAT under VBUFFER: leave as nop
else:
base = 'tbuffer' if typed else 'buffer'
cls = f'{base}_store' if is_store else f'{base}_load'
Comment thread
theSK2005 marked this conversation as resolved.
Resolved
return InstructionSemantics(
name,
cls,
elem_size=esz,
num_elems=ne,
sign_extend=se,
d16_hi='D16_HI' in normalized,
d16_lo='D16' in normalized and 'D16_HI' not in normalized,
)


def _derive_mubuf(name: str) -> InstructionSemantics | None:
"""Derive semantics for a MUBUF (Untyped Buffer memory) instruction."""
upper = name.upper()
Expand Down Expand Up @@ -2074,57 +2138,31 @@ def _derive_mubuf(name: str) -> InstructionSemantics | None:
return InstructionSemantics(name, 'buffer_atomic')

is_store = '_STORE_' in upper
for prefix in ('BUFFER_LOAD_', 'BUFFER_STORE_'):
# RDNA4 folds typed buffers into ENC_VBUFFER, which routes here, so accept
# TBUFFER_* as well; harmless for pre-RDNA4 MUBUF, which has no such names.
for prefix in ('BUFFER_LOAD_', 'BUFFER_STORE_', 'TBUFFER_LOAD_', 'TBUFFER_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,
)
sem = _derive_buffer_data(
name,
upper[len(prefix) :],
is_store,
typed=prefix.startswith('TBUFFER'),
typed_format_executable=False,
)
if sem:
return sem
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,
)
sem = _derive_buffer_data(name, upper[len(prefix) :], is_store, typed=True)
if sem:
return sem
return InstructionSemantics(name, 'nop')


Expand Down
Loading
Loading