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
274 lines
10 KiB
Python
274 lines
10 KiB
Python
################################################################################
|
|
#
|
|
# Copyright (C) 2026 Advanced Micro Devices, Inc. All rights reserved.
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell cop-
|
|
# ies of the Software, and to permit persons to whom the Software is furnished
|
|
# to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IM-
|
|
# PLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNE-
|
|
# CTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
################################################################################
|
|
|
|
"""Regressions for StreamK fence/wait rocisa -> StinkyTofu lowering (gfx1250).
|
|
|
|
Covers the three new gfx1250 instructions used by the StreamK release/acquire
|
|
fence sequences:
|
|
|
|
* ``SWaitXCnt`` -> ``s_wait_xcnt N`` (SOPP_WAIT16, immediate via getSrcParams)
|
|
* ``GlobalWb`` -> ``global_wb scope:*`` (SOPP, scope carried via CacheScopeModifiers)
|
|
* ``GlobalInv`` -> ``global_inv scope:*`` (SOPP, scope carried via CacheScopeModifiers)
|
|
|
|
The boilerplate (ISA init fixture, SignatureBase setup, toStinkyTofuModule
|
|
invocation) mirrors ``test_mubuf.py`` so that a regression in either the
|
|
rocisa-side classes or the stinkytofu-side mapping is caught here.
|
|
"""
|
|
|
|
import re
|
|
|
|
import pytest
|
|
import rocisa
|
|
from rocisa.code import Module, SignatureBase
|
|
from rocisa.enum import CacheScope
|
|
from rocisa.instruction import (
|
|
GlobalInv,
|
|
GlobalWb,
|
|
MacroInstruction,
|
|
SWaitCnt,
|
|
SWaitXCnt,
|
|
)
|
|
|
|
_ISA = (12, 5, 0)
|
|
|
|
# Skip entire module when the target backend isn't compiled into the registry.
|
|
pytestmark = pytest.mark.skipif(
|
|
not rocisa.isSupportedByStinkyTofu(_ISA),
|
|
reason=f"gfx{''.join(str(v) for v in _ISA)} not registered in StinkyTofu BackendRegistry",
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="module", autouse=True)
|
|
def _isa_context():
|
|
import os
|
|
import shutil
|
|
|
|
rocm_path = os.environ.get("ROCM_PATH", "/opt/rocm")
|
|
search_path = os.pathsep.join([
|
|
os.path.join(rocm_path, "bin"),
|
|
os.path.join(rocm_path, "lib", "llvm", "bin"),
|
|
])
|
|
assembler = shutil.which("amdclang++", path=search_path) or "amdclang++"
|
|
rocisa.rocIsa.getInstance().init(_ISA, assembler, False)
|
|
rocisa.rocIsa.getInstance().setKernel(_ISA, 32)
|
|
|
|
|
|
def _emit(mod: Module, name: str) -> str:
|
|
"""Convert ``mod`` to a stinkytofu module and return the emitted assembly."""
|
|
mod.setParent() # resolves symbolic register names before conversion
|
|
|
|
sig = SignatureBase(
|
|
kernelName=name,
|
|
kernArgsVersion=1,
|
|
codeObjectVersion="4",
|
|
groupSegmentSize=0,
|
|
sgprWorkGroup=(1, 1, 0),
|
|
vgprWorkItem=0,
|
|
flatWorkGroupSize=64,
|
|
numSgprPreload=0,
|
|
)
|
|
|
|
st = rocisa.toStinkyTofuModule(
|
|
mod, _ISA, name, signature=sig, options={"OptLevel": 0}
|
|
)
|
|
st.runOptimizationPipeline()
|
|
return st.emitAssembly()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# s_wait_xcnt
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_swait_xcnt_zero_emits_to_stinkytofu():
|
|
mod = Module("swait_xcnt_zero")
|
|
mod.add(SWaitXCnt(xcnt=0))
|
|
|
|
asm = _emit(mod, "swait_xcnt_zero")
|
|
|
|
assert re.search(r"s_wait_xcnt 0", asm), (
|
|
f"expected 's_wait_xcnt 0' in emitted assembly, got:\n{asm}"
|
|
)
|
|
|
|
|
|
def test_swait_xcnt_nonzero_emits_to_stinkytofu():
|
|
mod = Module("swait_xcnt_five")
|
|
mod.add(SWaitXCnt(xcnt=5))
|
|
|
|
asm = _emit(mod, "swait_xcnt_five")
|
|
|
|
assert re.search(r"s_wait_xcnt 5", asm), (
|
|
f"expected 's_wait_xcnt 5' in emitted assembly, got:\n{asm}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# global_wb / global_inv
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_global_wb_scope_dev_emits_to_stinkytofu():
|
|
mod = Module("global_wb_scope_dev")
|
|
mod.add(GlobalWb(scope=CacheScope.SCOPE_DEV))
|
|
|
|
asm = _emit(mod, "global_wb_scope_dev")
|
|
|
|
assert re.search(r"global_wb scope:SCOPE_DEV", asm), (
|
|
f"expected 'global_wb scope:SCOPE_DEV' in emitted assembly, got:\n{asm}"
|
|
)
|
|
|
|
|
|
def test_global_inv_scope_dev_emits_to_stinkytofu():
|
|
mod = Module("global_inv_scope_dev")
|
|
mod.add(GlobalInv(scope=CacheScope.SCOPE_DEV))
|
|
|
|
asm = _emit(mod, "global_inv_scope_dev")
|
|
|
|
assert re.search(r"global_inv scope:SCOPE_DEV", asm), (
|
|
f"expected 'global_inv scope:SCOPE_DEV' in emitted assembly, got:\n{asm}"
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("scope_enum,scope_text", [
|
|
(CacheScope.SCOPE_SE, "SCOPE_SE"),
|
|
(CacheScope.SCOPE_SYS, "SCOPE_SYS"),
|
|
])
|
|
def test_global_wb_non_dev_scope_emits_to_stinkytofu(scope_enum, scope_text):
|
|
"""Non-DEV scopes flow through the same CacheScopeModifiers path used by SCOPE_DEV.
|
|
|
|
Locks the rocisa->stinkytofu scope mapping so a future regression in either
|
|
`convertMUBUFScope` or `addModifiersToInstruction` is caught here.
|
|
"""
|
|
mod = Module(f"global_wb_{scope_text.lower()}")
|
|
mod.add(GlobalWb(scope=scope_enum))
|
|
|
|
asm = _emit(mod, f"global_wb_{scope_text.lower()}")
|
|
|
|
assert re.search(rf"global_wb scope:{scope_text}", asm), (
|
|
f"expected 'global_wb scope:{scope_text}' in emitted assembly, got:\n{asm}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Byte-for-byte equivalence with the legacy MacroInstruction text
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@pytest.mark.parametrize("typed,legacy_text", [
|
|
(SWaitXCnt(xcnt=0), "s_wait_xcnt 0"),
|
|
(GlobalWb(scope=CacheScope.SCOPE_DEV), "global_wb scope:SCOPE_DEV"),
|
|
(GlobalInv(scope=CacheScope.SCOPE_DEV), "global_inv scope:SCOPE_DEV"),
|
|
])
|
|
def test_typed_instruction_matches_legacy_macro_str(typed, legacy_text):
|
|
"""Lock the rocisa __str__ output of the new typed instructions to the
|
|
exact text the previous `MacroInstruction(name=..., args=[])` calls in
|
|
`Tensile/Components/StreamK.py` produced. If this fails, the StreamK
|
|
consumer change has drifted from the byte-for-byte invariant and the
|
|
generated kernels will differ.
|
|
"""
|
|
legacy = MacroInstruction(name=legacy_text, args=[])
|
|
assert str(typed) == str(legacy), (
|
|
f"typed={str(typed)!r} legacy={str(legacy)!r}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# StreamK release / acquire fence sequences
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def test_streamk_release_fence_sequence():
|
|
"""Release fence: ``global_wb scope:SCOPE_DEV`` with the StreamK comment.
|
|
|
|
Mirrors ``StreamKMemoryOrderingDevScopeFences.releaseFence`` minus the
|
|
surrounding ``SWaitCnt``s (which are not part of the new instruction set
|
|
under test).
|
|
"""
|
|
mod = Module("streamk_release_fence")
|
|
mod.add(GlobalWb(
|
|
scope=CacheScope.SCOPE_DEV,
|
|
comment="release: writeback partials to L2-coherent point",
|
|
))
|
|
|
|
asm = _emit(mod, "streamk_release_fence")
|
|
|
|
assert re.search(r"global_wb scope:SCOPE_DEV", asm), (
|
|
f"expected 'global_wb scope:SCOPE_DEV' in emitted assembly, got:\n{asm}"
|
|
)
|
|
# Allow other content / whitespace between the mnemonic and the comment,
|
|
# but stay on the same line (no DOTALL) to avoid matching unrelated text.
|
|
assert re.search(
|
|
r"global_wb scope:SCOPE_DEV.*release: writeback partials to L2-coherent point",
|
|
asm,
|
|
), (
|
|
"expected release-fence comment to appear next to the global_wb "
|
|
f"line, got:\n{asm}"
|
|
)
|
|
|
|
|
|
def test_streamk_acquire_fence_sequence():
|
|
"""Acquire fence: ``global_inv scope:SCOPE_DEV`` with the StreamK comment."""
|
|
mod = Module("streamk_acquire_fence")
|
|
mod.add(GlobalInv(
|
|
scope=CacheScope.SCOPE_DEV,
|
|
comment="acquire: invalidate partials after flag",
|
|
))
|
|
|
|
asm = _emit(mod, "streamk_acquire_fence")
|
|
|
|
assert re.search(r"global_inv scope:SCOPE_DEV", asm), (
|
|
f"expected 'global_inv scope:SCOPE_DEV' in emitted assembly, got:\n{asm}"
|
|
)
|
|
assert re.search(
|
|
r"global_inv scope:SCOPE_DEV.*acquire: invalidate partials after flag",
|
|
asm,
|
|
), (
|
|
"expected acquire-fence comment to appear next to the global_inv "
|
|
f"line, got:\n{asm}"
|
|
)
|
|
|
|
|
|
def test_streamk_full_release_sequence_preserves_ordering():
|
|
"""The full release-fence sequence must keep the ``global_wb`` between the
|
|
pre- and post-fence ``s_wait_*cnt`` instructions after stinkytofu's DAG
|
|
scheduler runs. The ``HasSideEffect`` flag on ``GlobalWbInst`` is what
|
|
prevents the scheduler from reordering across the fence; this test locks
|
|
that contract.
|
|
"""
|
|
mod = Module("streamk_full_release_fence")
|
|
mod.add(SWaitCnt(vlcnt=0,
|
|
comment="release: drain in-flight loads before global_wb"))
|
|
mod.add(SWaitCnt(vscnt=0, comment="wait for data store"))
|
|
mod.add(GlobalWb(scope=CacheScope.SCOPE_DEV,
|
|
comment="release: writeback partials to L2-coherent point"))
|
|
mod.add(SWaitCnt(vlcnt=0, vscnt=0,
|
|
comment="release: wait for global_wb"))
|
|
|
|
asm = _emit(mod, "streamk_full_release_fence")
|
|
|
|
# Verify the global_wb appears AFTER a pre-fence drain wait and BEFORE
|
|
# the post-fence wait; use re.DOTALL because we are matching across lines.
|
|
assert re.search(
|
|
r"s_wait_(loadcnt|storecnt) 0.*global_wb scope:SCOPE_DEV.*s_wait_(loadcnt|storecnt) 0",
|
|
asm,
|
|
re.DOTALL,
|
|
), (
|
|
"expected global_wb to remain between drain and post-fence waits, "
|
|
f"got:\n{asm}"
|
|
)
|