Files
TheRock/rocm-libraries/projects/hipblaslt/tensilelite/rocisa/test/test_streamk_fences.py
T
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

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}"
)