Files
TheRock/rocm-libraries/shared/tensile/Tensile/Tests/unit/test_TensileCreateLibrary.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

1240 lines
47 KiB
Python

################################################################################
#
# Copyright (C) 2020-2024 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
# copies 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
# IMPLIED, 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 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
################################################################################
import contextlib
import functools
import glob
import logging
import os
import shutil
import uuid
from pathlib import Path
from typing import List, Tuple
from unittest.mock import MagicMock, call, mock_open, patch
import pytest
import yaml
import Tensile.ClientWriter as ClientWriter
import Tensile.Common as Common
import Tensile.LibraryIO as LibraryIO
import Tensile.SolutionLibrary as SolutionLibrary
import Tensile.TensileCreateLibrary as tcl
from Tensile.KernelWriterAssembly import KernelWriterAssembly
from Tensile.KernelWriterBase import KernelWriterBase
from Tensile.KernelWriterSource import KernelWriterSource
from Tensile.SolutionStructs import ProblemSizes, Solution
from Tensile.Utilities.ConditionalImports import yamlLoader
from Tensile.Utilities.Toolchain import ToolchainDefaults, validateToolchain
mylogger = logging.getLogger()
@pytest.fixture
def mock_openFile():
with patch("builtins.open", mock_open()) as mock:
yield mock
@pytest.fixture
def mock_toFile():
with patch("Tensile.TensileCreateLibrary.toFile") as mock:
yield mock
@pytest.fixture
def mock_getKernelSourceAndHeaderCode():
with patch("Tensile.TensileCreateLibrary.getKernelSourceAndHeaderCode") as mock:
yield mock
@pytest.fixture
def mock_printWarning():
with patch("Tensile.TensileCreateLibrary.printWarning") as mock:
yield mock
@pytest.fixture
def mock_kernelSourceAndHeaderFiles():
return MagicMock(name="source_file_mock"), MagicMock(name="header_file_mock")
@pytest.fixture
def mock_KernelWriterBase():
mock = MagicMock(spec=KernelWriterBase)
mock.getKernelName.return_value = "TestKernelName"
return mock
def test_loadSolutions(caplog, useGlobalParameters):
with useGlobalParameters():
mylogger.debug("this is a test of debug log")
mylogger.info("this is some info")
scriptDir = os.path.dirname(os.path.realpath(__file__))
dataDir = os.path.realpath(os.path.join(scriptDir, "..", "test_data", "unit"))
solutionsFilePath = os.path.join(dataDir, "solutions", "solutions_nn_3.yaml")
fileSolutions = LibraryIO.parseSolutionsFile(solutionsFilePath)
solutions = fileSolutions[1]
kernels, _, _ = tcl.generateKernelObjectsFromSolutions(solutions)
assert len(solutions) == 3
assert len(kernels) == 3
_, kernelWriterAssembly, _, _ = tcl.getKernelWriters(
solutions, kernels, removeTemporaries=False
)
expectedKernelName0 = "Cijk_Ailk_Bljk_SB_MT128x128x2_SE_K1_TT8_8_WG16_16_1"
expectedKernelName1 = "Cijk_Ailk_Bljk_SB_MT64x64x2_SE_K1_TT4_4_WG16_16_1"
expectedKernelName2 = "Cijk_Ailk_Bljk_SB_MT64x64x2_SE_K1_TT4_8_WG16_8_1"
actualKernelName0 = kernelWriterAssembly.getKernelName(kernels[0])
actualKernelName1 = kernelWriterAssembly.getKernelName(kernels[1])
actualKernelName2 = kernelWriterAssembly.getKernelName(kernels[2])
assert expectedKernelName0 == actualKernelName0
assert expectedKernelName1 == actualKernelName1
assert expectedKernelName2 == actualKernelName2
@pytest.mark.skip(reason="System issue with find assempler called when assigning defaults")
def test_WriteClientLibraryFromSolutions(tmpdir):
Common.globalParameters["MergeFiles"] = True
Common.globalParameters["CodeObjectVersion"] = "default"
Common.globalParameters["YAML"] = True
Common.globalParameters["CxxCompiler"] = "amdclang++"
Common.assignGlobalParameters({})
libraryWorkingPath = tmpdir.mkdir("lib")
buildWorkingPath = tmpdir.mkdir("build")
scriptDir = os.path.dirname(os.path.realpath(__file__))
dataDir = os.path.realpath(os.path.join(scriptDir, "..", "test_data", "unit"))
solutionsFilePath = os.path.join(dataDir, "solutions", "solutions_nn_3.yaml")
fileSolutions = LibraryIO.parseSolutionsFile(solutionsFilePath)
solutions = fileSolutions[1]
Common.setWorkingPath(buildWorkingPath)
tcl.WriteClientLibraryFromSolutions(solutions, libraryWorkingPath)
Common.popWorkingPath()
tensileLibraryPath = os.path.join(libraryWorkingPath, "library")
hsacoFiles = glob.glob(tensileLibraryPath + "/*hsaco")
assert len(hsacoFiles) > 0
coFiles = glob.glob(tensileLibraryPath + "/*TensileLibrary*co")
assert len(coFiles) > 0
tensileYamlFilePath = os.path.join(tensileLibraryPath, "TensileLibrary.yaml")
assert os.path.exists(tensileYamlFilePath) == 1
config = None
try:
stream = open(tensileYamlFilePath, "r")
except IOError:
mylogger.error("Cannot open file: %s" % tensileYamlFilePath)
config = yaml.load(stream, yamlLoader)
stream.close()
actualSolutions = config["solutions"]
assert len(actualSolutions) == 3
metadataYamlFilePath = os.path.join(tensileLibraryPath, "metadata.yaml")
assert os.path.exists(metadataYamlFilePath) == 1
metadata = None
try:
stream = open(metadataYamlFilePath, "r")
except IOError:
mylogger.error("Cannot open file: %s" % metadataYamlFilePath)
metadata = yaml.load(stream, yamlLoader)
stream.close()
actualProblemType = metadata["ProblemType"]
assert len(actualProblemType) > 0
def test_CreateBenchmarkClientParametersForSizes(tmpdir):
Common.globalParameters["CurrentISA"] = (9, 0, 6)
dataWorkingPath = tmpdir.mkdir("Data")
configWorkingPath = tmpdir.mkdir("run_configs")
scriptDir = os.path.dirname(os.path.realpath(__file__))
dataDir = os.path.realpath(os.path.join(scriptDir, "..", "test_data", "unit"))
testDataPath = os.path.join(dataDir, "library_data")
libraryPath = os.path.join(testDataPath, "library")
metadataFilepath = os.path.join(libraryPath, "metadata.yaml")
metadataFile = LibraryIO.readYAML(metadataFilepath)
problemTypeDict = metadataFile["ProblemType"]
sizes = [{"Exact": [196, 256, 64, 1024]}]
problemSizes = ProblemSizes(problemTypeDict, sizes)
dataFilePath = os.path.join(dataWorkingPath, "results.csv")
configFile = os.path.join(configWorkingPath, "ClientParameters.ini")
ClientWriter.CreateBenchmarkClientParametersForSizes(
testDataPath, problemSizes, dataFilePath, configFile
)
assert os.path.exists(configFile) == 1
def test_verifyManifest():
manifestFile = Path("test_manifest.txt")
testFoo = Path("foo.asm")
testBar = Path("bar.asm")
# ensure clean state before running test
with contextlib.suppress(FileNotFoundError):
os.remove(manifestFile)
os.remove(testFoo)
os.remove(testBar)
# Verification should fail if the manifest can't be found
with pytest.raises(
FileNotFoundError, match=r"(.*) No such file or directory: 'test_manifest.txt'"
):
tcl.verifyManifest(manifestFile)
# Create an empty manifest
with open(manifestFile, mode="x") as manifest:
assert tcl.verifyManifest(manifestFile), "an empty manifest should always succeed"
# add to file manifest that is not on disk
manifest.write("foo.asm\n")
manifest.flush()
assert not tcl.verifyManifest(
manifestFile
), "file in manifest are on disk, but shouldn't be"
with open(testFoo, mode="x"):
assert tcl.verifyManifest(manifestFile), "file in manifest isn't on disk, but should be"
manifest.write("bar.asm\n")
manifest.flush()
assert not tcl.verifyManifest(manifestFile), "bar.asm in manifest should not be on disk"
with open(testBar, mode="x"):
assert tcl.verifyManifest(manifestFile), "files in manifest isn't on disk, but should be"
with open(manifestFile, "a") as generatedFile:
for filePath in range(5):
generatedFile.write("%s\n" % (filePath))
assert not tcl.verifyManifest(manifestFile), "files in manifest are on disk, but shouldn't be"
def test_findLogicFiles():
def setup():
baseDir = Path("no-commit-test-logic-files")
# Start with clean state
with contextlib.suppress(FileNotFoundError):
shutil.rmtree(baseDir)
baseDir.mkdir()
lazyLoading = False
experimentalDir = "/experimental/"
return baseDir, lazyLoading, experimentalDir
def outputMatchesOldLogic1():
baseDir, lazyLoading, experimentalDir = setup()
logicArchs = set(Common.architectureMap.values())
logicArchs.add("hip")
logicArchs.remove("_") # Remove the value `_` associated with key `all`
for d in logicArchs:
createDirectoryWithYamls(baseDir / d, "foo", "yaml")
result = tcl.findLogicFiles(baseDir, logicArchs, lazyLoading, experimentalDir)
expected = findLogicFiles_oldLogic(baseDir, logicArchs, lazyLoading, experimentalDir)
return set(result) == set(expected)
def outputMatchesOldLogic2():
baseDir, lazyLoading, experimentalDir = setup()
logicArchs = set(Common.architectureMap.values())
logicArchs.add("hip")
logicArchs.remove("_") # Remove the value `_` associated with key `all`
for d in logicArchs:
createDirectoryWithYamls(baseDir / d, d, "yaml")
result = tcl.findLogicFiles(baseDir, logicArchs, lazyLoading, experimentalDir)
expected = findLogicFiles_oldLogic(baseDir, logicArchs, lazyLoading, experimentalDir)
return set(result) == set(expected)
def outputMatchesOldLogic3():
baseDir, lazyLoading, experimentalDir = setup()
logicArchs = set(Common.architectureMap["all"])
for d in logicArchs:
createDirectoryWithYamls(baseDir / d, d, "yaml")
result = tcl.findLogicFiles(baseDir, logicArchs, lazyLoading, experimentalDir)
expected = findLogicFiles_oldLogic(baseDir, logicArchs, lazyLoading, experimentalDir)
return set(result) == set(expected)
def verifyYamlAndYml():
baseDir, lazyLoading, experimentalDir = setup()
# Create directory structure with files that *don't* have arch in filename
logicArchs = set(Common.architectureMap.values())
logicArchs.add("hip")
logicArchs.remove("_")
for d in logicArchs:
createDirectoryWithYamls(baseDir / "yaml" / d, d, "yaml")
createDirectoryWithYamls(baseDir / "yml" / d, d, "yml")
result = tcl.findLogicFiles(baseDir, logicArchs, lazyLoading, experimentalDir)
expected = findLogicFiles_oldLogic(baseDir, logicArchs, lazyLoading, experimentalDir)
return len(result) == len(expected) * 2
assert outputMatchesOldLogic1(), "Output differs from old logic, not backwards compatible."
assert outputMatchesOldLogic2(), "Output differs from old logic, not backwards compatible."
assert outputMatchesOldLogic3(), "Output differs from old logic, not backwards compatible."
assert (
verifyYamlAndYml()
), "Output should have twice as many files as old logic (which only parses .yaml)"
def test_sanityCheck():
# setup some dummy lists with files
srcPaths = ["foo.hsaco", "bar.hsaco"]
asmPaths = ["baz.co", "gru.co"]
coPathsMatch = ["foo.hsaco", "bar.hsaco", "baz.co", "gru.co"]
coPathsExtra = ["foo.hsaco", "bar.hsaco", "baz.co", "gru.co", "tux.hsaco"]
coPathsMissing = ["foo.hsaco", "bar.hsaco", "baz.co"]
tcl.sanityCheck(srcPaths, asmPaths, coPathsMatch, False)
# Ensure that old logic also succeeds
sanityCheck_oldLogic(srcPaths, asmPaths, coPathsMatch, False)
with pytest.raises(ValueError, match=r"(.*) unexpected code object files: \['tux.hsaco'\]"):
tcl.sanityCheck(srcPaths, asmPaths, coPathsExtra, False)
# Ensure that old logic also fails
with pytest.raises(Exception):
try:
sanityCheck_oldLogic(srcPaths, asmPaths, coPathsExtra, False)
except:
raise Exception
with pytest.raises(ValueError, match=r"(.*) missing expected code object files: \['gru.co'\]"):
tcl.sanityCheck(srcPaths, asmPaths, coPathsMissing, False)
# Ensure that old logic also fails
with pytest.raises(Exception):
try:
sanityCheck_oldLogic(srcPaths, asmPaths, coPathsMissing, False)
except:
raise Exception
def test_generateClientConfig():
outputPath: Path = Path.cwd() / "my-output"
masterLibrary: Path = outputPath / "masterlib.data"
codeObjectFiles = ["library/foo.hsaco", "library/bar.co"]
configFile = outputPath / "best-solution.ini"
cleanClientConfigTest(outputPath, masterLibrary, codeObjectFiles, configFile)
with pytest.raises(
FileNotFoundError,
match=r"(.*) No such file or directory: '(.*)/my-output/best-solution.ini'",
):
tcl.generateClientConfig(outputPath, masterLibrary, codeObjectFiles)
setupClientConfigTest(outputPath, masterLibrary, codeObjectFiles)
tcl.generateClientConfig(outputPath, masterLibrary, codeObjectFiles)
assert configFile.is_file(), "{configFile} was not generated"
with open(configFile, "r") as f:
result = f.readlines()
assert "library-file" in result[0], "missing library-file entry"
assert "code-object" in result[1], "missing code-object entry"
assert "code-object" in result[2], "missing code-object entry"
assert "best-solution" in result[3], "missing best-solution entry"
cleanClientConfigTest(outputPath, masterLibrary, codeObjectFiles, configFile)
class MasterLibraryMock:
def __init__(self, libraries, data):
self.lazyLibraries = libraries
self.data = data
def test_generateMasterFileList():
archs = ["arch0", "arch1"]
libraries = {
"arch0": MasterLibraryMock({"mylib0": MasterLibraryMock(None, 2)}, 0),
"arch1": MasterLibraryMock({"mylib1": MasterLibraryMock(None, 3)}, 1),
}
result = tcl.generateMasterFileList(libraries, archs, lazy=False)
for idx, t in enumerate(result):
assert t[0] == "TensileLibrary_arch" + str(idx), "Incorrect naming for key."
assert isinstance(t[1], MasterLibraryMock), "Incorrect type for value."
assert t[1].data == idx, "Incorrect data."
result = tcl.generateMasterFileList(libraries, archs, lazy=True)
for idx, t in enumerate(result[0:2]):
assert t[0] == "TensileLibrary_lazy_arch" + str(idx), "Incorrect naming for key."
assert isinstance(t[1], MasterLibraryMock), "Incorrect type for value."
assert t[1].data == idx, "Incorrect data."
for idx, t in enumerate(result[2:4]):
assert t[0] == "mylib" + str(idx), "Incorrect naming for key."
assert isinstance(t[1], MasterLibraryMock), "Incorrect type for value."
assert t[1].data == (idx + 2), "Incorrect data."
def test_logicDataAndSolutionsConstruction(initGlobalParametersForTCL):
def testCase1(logicFiles: List[LibraryIO.LibraryLogic], separateArch: bool):
# clear the set to prevent testing errors caused
# by the fact that ArchitectureSet is shared across
# instances of MasterSolutionLibrary.
SolutionLibrary.MasterSolutionLibrary.ArchitectureSet.clear()
masterLibraries = tcl.makeMasterLibraries(logicFiles, separate=separateArch)
arch = "gfx900" if separateArch else "full"
if separateArch:
assert (
len(masterLibraries[arch].solutions.values()) == 17
), f"There should be 17 solutions prior to adding the fallback for {arch}."
tcl.addFallback(masterLibraries)
assert (
len(masterLibraries[arch].solutions.values()) == 19
), f"There should be 19 solutions after adding the fallback for {arch}."
else:
assert (
len(masterLibraries[arch].solutions.values()) == 19
), f"There should be 19 solutions for {arch}."
solutions = tcl.generateSolutions(masterLibraries, separate=separateArch)
assert isinstance(solutions, list), "generateSolutions should return a list."
assert len(solutions) == 19, "There should be 19 solutions after adding the fallback."
def testCase2(yamlFiles: List[str], separateArch: bool):
# clear the set to prevent testing errors caused
# by the fact that ArchitectureSet is shared across
# instances of MasterSolutionLibrary.
SolutionLibrary.MasterSolutionLibrary.ArchitectureSet.clear()
masterLibraries = tcl.generateLogicData(
yamlFiles, version="foo", printLevel=0, separate=separateArch
)
arch = "gfx900" if separateArch else "full"
assert (
len(masterLibraries[arch].solutions.values()) == 19
), f"There should be 19 solutions for {arch}."
solutions = tcl.generateSolutions(masterLibraries, separate=separateArch)
assert isinstance(solutions, list), "generateSolutions should return a list."
assert len(solutions) == 19, "There should be 19 solutions after adding the fallback."
def testCase3(logicFiles: List[LibraryIO.LibraryLogic]):
# clear the set to prevent testing errors caused
# by the fact that ArchitectureSet is shared across
# instances of MasterSolutionLibrary.
SolutionLibrary.MasterSolutionLibrary.ArchitectureSet.clear()
masterLibraries = tcl.makeMasterLibraries(logicFiles, separate=True)
arch = "gfx900"
assert (
len(masterLibraries[arch].lazyLibraries.keys()) == 1
), f"There should be 1 key prior to adding the fallback for {arch}."
assert (
len(next(iter(masterLibraries[arch].lazyLibraries.values())).solutions.values()) == 17
), f"There should be 17 solutions prior to adding the fallback for {arch}."
tcl.addFallback(masterLibraries)
assert (
len(masterLibraries[arch].lazyLibraries.keys()) == 2
), f"There should be 2 keys after adding the fallback for {arch}."
for name, lib in masterLibraries[arch].lazyLibraries.items():
if "fallback" in name:
assert len(lib.solutions.values()) == 2, "There should be 2 fallback solutions."
else:
assert len(lib.solutions.values()) == 17, "There should be 17 gfx900 solutions."
solutions = tcl.generateSolutions(masterLibraries, separate=True)
assert isinstance(solutions, list), "generateSolutions should return a list."
assert len(solutions) == 19, "There should be 19 solutions after adding the fallback."
requiredArgs = ["--jobs=2", "/unused/logic/path", "/unused/output/path", "HIP"]
rootPath = Path(__file__).parent.parent / "test_data" / "unit" / "solutions"
yamlFiles = [
rootPath / f for f in ["vega10_Cijk_Ailk_Bjlk_CB_GB.yaml", "hip_Cijk_Ailk_Bjlk_CB_GB.yaml"]
]
with initGlobalParametersForTCL(["--architecture=gfx900"] + requiredArgs):
logicFiles = tcl.parseLibraryLogicFiles(yamlFiles)
assert len(logicFiles) == 2, "The length of the logic files list is incorrect."
testCase1(logicFiles, separateArch=True)
testCase2(yamlFiles, separateArch=True)
with initGlobalParametersForTCL(["--architecture=gfx900"] + requiredArgs):
logicFiles = tcl.parseLibraryLogicFiles(yamlFiles)
assert len(logicFiles) == 2, "The length of the logic files list is incorrect."
testCase1(logicFiles, separateArch=False)
testCase2(yamlFiles, separateArch=False)
with initGlobalParametersForTCL(
["--architecture=gfx900", "--lazy-library-loading"] + requiredArgs
):
logicFiles = tcl.parseLibraryLogicFiles(yamlFiles)
assert len(logicFiles) == 2, "The length of the logic files list is incorrect."
testCase3(logicFiles)
@pytest.fixture
def unittestPath(request):
"""Returns the path to the directory containing the current test file"""
return request.path.parent
@pytest.fixture
def setupSolutionsAndKernels(
unittestPath,
) -> Tuple[List[Solution], List[Solution], KernelWriterAssembly, KernelWriterSource]:
"""Reusable logic for setting up testable solutions and kernels"""
(cxxCompiler, cCompiler, assembler, offloadBundler, hipconfig, deviceEnumerator) = (
validateToolchain(
ToolchainDefaults.CXX_COMPILER,
ToolchainDefaults.C_COMPILER,
ToolchainDefaults.ASSEMBLER,
ToolchainDefaults.OFFLOAD_BUNDLER,
ToolchainDefaults.HIP_CONFIG,
ToolchainDefaults.DEVICE_ENUMERATOR,
)
)
params = {
"CxxCompiler": cxxCompiler,
"CCompiler": cCompiler,
"Assembler": assembler,
"OffloadBundler": offloadBundler,
"HipConfig": hipconfig,
"ROCmAgentEnumeratorPath": deviceEnumerator,
}
Common.assignGlobalParameters(params)
_, _, _, _, _, lib = LibraryIO.parseLibraryLogicFile(
unittestPath.parent / "test_data" / "unit" / "aldebaran_Cijk_AlikC_Bljk_ZB_GB.yaml"
)
solutions = [sol.originalSolution for sol in lib.solutions.values()]
kernels, _, _ = tcl.generateKernelObjectsFromSolutions(solutions)
kernelWriterSource, kernelWriterAssembly, _, _ = tcl.getKernelWriters(
solutions, kernels, removeTemporaries=False
)
return solutions, kernels, kernelWriterAssembly, kernelWriterSource
def test_prepAsm(setupSolutionsAndKernels):
solutions, kernels, kernelWriterAssembly, kernelWriterSource = setupSolutionsAndKernels
buildPath = Path("no-commit-prep-asm")
buildPath.mkdir(exist_ok=True)
def testLinux():
tcl.prepAsm(kernelWriterAssembly, True, Path("no-commit-prep-asm"), (9, 0, 10), 1)
expected = """#!/bin/sh
# usage: asm-new.sh kernelName(no extension) [--wave32]
f=$1
shift
if [ ! -z "$1" ] && [ "$1" = "--wave32" ]; then
wave=32
shift
else
wave=64
fi
h=gfx90a
if [ $wave -eq 32 ]; then
/opt/rocm/bin/amdclang++ -x assembler -target amdgcn-amd-amdhsa -mcode-object-version=4 -mcpu=gfx90a -mno-wavefrontsize64 -c -o $f.o $f.s
else
/opt/rocm/bin/amdclang++ -x assembler -target amdgcn-amd-amdhsa -mcode-object-version=4 -mcpu=gfx90a -mwavefrontsize64 -c -o $f.o $f.s
fi
/opt/rocm/bin/amdclang++ -target amdgcn-amd-amdhsa -Xlinker --build-id -o $f.co $f.o
cp $f.co ../../../library/${f}_$h.co
mkdir -p ../../../asm_backup && cp $f.s ../../../asm_backup/$f.s
"""
with open(buildPath / "assembly" / "asm-new.sh", "r") as f:
contents = f.read()
assert contents == expected, "Assembler script doesn't match expectation"
def testWindows():
tcl.prepAsm(kernelWriterAssembly, False, Path("no-commit-prep-asm"), (9, 0, 10), 1)
expected = """@echo off
set f=%1
set arg2=--wave64
if [%2] NEQ [] set arg2=%2
set /A wave=64
if %arg2% EQU --wave32 set /A wave=32
set h=gfx90a
if %wave% == 32 (/opt/rocm/bin/amdclang++ -x assembler -target amdgcn-amd-amdhsa -mcode-object-version=4 -mcpu=gfx90a -mno-wavefrontsize64 -c -o %f%.o %f%.s) else (/opt/rocm/bin/amdclang++ -x assembler -target amdgcn-amd-amdhsa -mcode-object-version=4 -mcpu=gfx90a -mwavefrontsize64 -c -o %f%.o %f%.s)
/opt/rocm/bin/amdclang++ -target amdgcn-amd-amdhsa -Xlinker --build-id -o %f%.co %f%.o
copy %f%.co ..\\..\\..\library\%f%_%h%.co
"""
with open(buildPath / "assembly" / "asm-new.bat", "r") as f:
contents = f.read()
assert contents == expected, "Assembler script doesn't match expectation"
testLinux()
testWindows()
class MockSolution:
def __init__(self, name: str, lang: str):
self.name = name
self.lang = lang
def __getitem__(self, key):
if key == "KernelLanguage":
return self.lang
raise KeyError(f"Key {key} not found")
class MockKernelWriter:
def getKernelFileBase(self, kernel: MockSolution):
return kernel.name
def getKernelName(self, kernel: MockSolution):
return f"{kernel.name}_name"
def test_markDuplicateKernels():
kernelsAsm = [MockSolution(name, "Assembly") for name in ["A", "B", "C"]]
kernelWriterAssembly = MockKernelWriter()
kernelsOut = tcl.markDuplicateKernels(kernelsAsm, kernelWriterAssembly)
assert len(kernelsOut) == len(kernelsAsm), "Lengths of input and output should match"
assert all([not k.duplicate for k in kernelsOut]), "All kernels should be unique"
kernelsAsm = [MockSolution(name, "Assembly") for name in ["A", "B", "B", "C"]]
kernelsOut = tcl.markDuplicateKernels(kernelsAsm, kernelWriterAssembly)
assert len(kernelsOut) == len(kernelsAsm), "Lengths of input and output should match"
for i in range(len(kernelsOut)):
isDup = kernelsOut[i].duplicate
assert isDup if i == 2 else not isDup, "Duplicate status is incorrect"
kernelsSrc = [MockSolution(name, "Source") for name in ["D", "E", "E", "F"]]
kernelsOut = tcl.markDuplicateKernels(kernelsSrc, kernelWriterAssembly)
assert len(kernelsOut) == len(kernelsSrc), "Lengths of input and output should match"
for i in range(len(kernelsOut)):
with pytest.raises(
AttributeError, match="'MockSolution' object has no attribute 'duplicate'"
):
kernelsOut[i].duplicate
kernelsAll = kernelsSrc + kernelsAsm
kernelsOut = tcl.markDuplicateKernels(kernelsAll, kernelWriterAssembly)
assert len(kernelsOut) == len(kernelsAll), "Lengths of input and output should match"
for i in range(len(kernelsAll)):
if i < len(kernelsSrc):
with pytest.raises(
AttributeError, match="'MockSolution' object has no attribute 'duplicate'"
):
kernelsOut[i].duplicate
else:
isDup = kernelsOut[i].duplicate
assert isDup if i == 6 else not isDup, "Duplicate status is incorrect"
@pytest.mark.skip(reason="Debugging function")
def test_filterProcessingErrors(setupSolutionsAndKernels):
solutions, kernels, kernelWriterAssembly, kernelWriterSource = setupSolutionsAndKernels
kernels = tcl.markDuplicateKernels(kernels, kernelWriterAssembly)
results = [(-2, 0, 0, 0, 0)] * len(kernels)
kernelsOut, solutionsOut, resultsOut = tcl.filterProcessingErrors(
kernels, solutions, results, printLevel=1, ignoreErr=True
)
assert len(kernelsOut) == 0, "Kernels should be of length zero"
assert len(solutionsOut) == 0, "Solutions should be of length zero"
assert len(resultsOut) == 0, "Results should be of length zero"
results = [(-2, 0, 0, 0, 0)] + [(0, 0, 0, 0, 0)] * (len(kernels) - 1)
with pytest.raises(ValueError, match=r"Found 1 error\(s\) (.*)"):
tcl.filterProcessingErrors(kernels, solutions, results, printLevel=1, ignoreErr=False)
def test_processKernelSource(setupSolutionsAndKernels):
_, kernels, kernelWriterAssembly, kernelWriterSource = setupSolutionsAndKernels
kernels = tcl.markDuplicateKernels(kernels, kernelWriterAssembly)
fn = functools.partial(
tcl.processKernelSource,
kernelWriterSource=kernelWriterSource,
kernelWriterAssembly=kernelWriterAssembly,
)
results = list(map(fn, kernels))
expected = [
(
0,
"",
"",
"Cijk_AlikC_Bljk_ZB_GB_MT16x16x16_MI16x16x4x1_SN_w7sFP__lQopabvLxzkL9V_rkJczrhhWwRy4dGREBaJY=",
None,
),
(
0,
"",
"",
"Cijk_AlikC_Bljk_ZB_GB_MT64x96x8_MI16x16x4x1_SN_BT-2Hl1ATHdIekMN_ygeV-CN0NYIERWlaWZvB-9FqxI0=",
None,
),
(
0,
"",
"",
"Cijk_AlikC_Bljk_ZB_GB_MT128x64x8_MI16x16x4x1_SN_jLNBWWn_GYkesnvb5x6jwDhR80VmCUqMaBE6X8-iojw=",
None,
),
]
assert results == expected, "Assembly files shouldn't have any header or source content"
def test_generateKernelSourceAndHeaderFiles_generic():
outputPath = Path("no-commit-kernel-build-files")
outputPath.mkdir(exist_ok=True)
results: List[tcl.ProcessedKernelResult] = [
(-2, "", "", "asm1", None),
(0, "", "", "asm2", None),
(0, "", "", "asm3", None),
(-2, '#include "Kernels1.h"', "#pragma once", "src1", None),
(0, '#include "Kernels2.h"', "#pragma twice", "src2", None),
(0, '#include "Kernels3.h"', "#pragma thrice", "src3", None),
]
filesToWrite = tcl.collectFilesToWrite(results, Path(outputPath), True, True, 1)
kernelFiles = tcl.generateKernelSourceAndHeaderFiles(filesToWrite)
# Undocumented internal logic of generateKernelSourceAndHeaderFiles
assert len(kernelFiles) == 1, "Only one file should be created when mergeFiles == True"
assert (
kernelFiles[0] == "no-commit-kernel-build-files/Kernels.cpp"
), "Cpp source file doesn't match"
def test_generateKernelSourceAndHeaderFiles_noLazyMergeFallbackNames():
outputPath = Path("no-commit-kernel-build-files")
outputPath.mkdir(exist_ok=True)
results = [
(-2, "", "", "asm1", None),
(0, "", "", "asm2", None),
(0, "", "", "asm3", None),
(-2, '#include "Kernels1.h"', "#pragma once", "src1", None),
(0, '#include "Kernels2.h"', "#pragma twice", "src2", "kfile2"),
(0, '#include "Kernels3.h"', "#pragma thrice", "src3", None),
]
filesToWrite = tcl.collectFilesToWrite(results, Path(outputPath), False, False, 1)
kernelFiles = tcl.generateKernelSourceAndHeaderFiles(filesToWrite)
assert (
len(kernelFiles) == 3
), "3 files should be created. Since some filenames are present and some not, we use the kernel name directly as a fallback, e.g., src1"
assert kernelFiles == [
"no-commit-kernel-build-files/src1.cpp",
"no-commit-kernel-build-files/kfile2.cpp",
"no-commit-kernel-build-files/src3.cpp",
]
def test_generateKernelSourceAndHeaderFiles_mergeWithNonEmptyAsm():
outputPath = Path("no-commit-kernel-build-files")
outputPath.mkdir(exist_ok=True)
results = [
(-2, "A1", "#pragma 1", "asm1", None),
(0, "A2", "#pragma 2", "asm2", "afile2"),
(0, "A3", "#pragma 3", "asm3", None),
]
filesToWrite = tcl.collectFilesToWrite(results, Path(outputPath), False, True, 1)
kernelFiles = tcl.generateKernelSourceAndHeaderFiles(filesToWrite)
assert (
len(kernelFiles) == 2
), "2 files should be created. Since one filename is present and merge files is True => Kernels.cpp will be created."
assert kernelFiles == [
"no-commit-kernel-build-files/Kernels.cpp",
"no-commit-kernel-build-files/afile2.cpp",
]
with open(outputPath / "Kernels.cpp", "r") as f:
contents = f.readlines()
assert contents[-1] == "A1A3"
print(contents[-2])
assert contents[-2] == '#include "no-commit-kernel-build-files/Kernels.h"\n'
with open(outputPath / "Kernels.h", "r") as f:
contents = f.readlines()
assert contents[-1] == "#pragma 1#pragma 3"
def test_generateKernelSourceAndHeaderFiles_noMerge_WithNonEmptyAsm():
outputPath = Path("no-commit-kernel-build-files")
outputPath.mkdir(exist_ok=True)
results = [
(-2, "A1", "#pragma 1", "asm1", None),
(0, "A2", "#pragma 2", "asm2", "afile2"),
(0, "A3", "#pragma 3", "asm3", None),
]
filesToWrite = tcl.collectFilesToWrite(results, Path(outputPath), False, False, 1)
kernelFiles = tcl.generateKernelSourceAndHeaderFiles(filesToWrite)
assert (
len(kernelFiles) == 3
), "3 files should be created. Since some filenames are present and some not (src kernels), we use the kernel name directly as a fallback, e.g., src1"
assert kernelFiles == [
"no-commit-kernel-build-files/asm1.cpp",
"no-commit-kernel-build-files/afile2.cpp",
"no-commit-kernel-build-files/asm3.cpp",
]
def test_generateKernelSourceAndHeaderFiles_lazyMerge3Src():
outputPath = Path("no-commit-kernel-build-files")
outputPath.mkdir(exist_ok=True)
results = [
(0, "", "", "asm1", None),
(0, "", "", "asm2", None),
(0, "", "", "asm3", "blah.asm"),
(0, '#include "Kernels1.h"', "#pragma once", "src1", "kfile1"),
(0, '#include "Kernels2.h"', "#pragma twice", "src2", "kfile2"),
(0, '#include "Kernels3.h"', "#pragma thrice", "src3", "kfile3"),
]
filesToWrite = tcl.collectFilesToWrite(results, Path(outputPath), True, True, 1)
kernelFiles = tcl.generateKernelSourceAndHeaderFiles(filesToWrite)
assert (
len(kernelFiles) == 4
), "4 files should be created because they have file names AND some source code AND lazy loading/merge files is True"
assert kernelFiles == [
"no-commit-kernel-build-files/kfile1.cpp",
"no-commit-kernel-build-files/kfile2.cpp",
"no-commit-kernel-build-files/kfile3.cpp",
"no-commit-kernel-build-files/Kernels.cpp",
]
def test_generateKernelSourceAndHeaderFiles_noLazyMerge3Src():
outputPath = Path("no-commit-kernel-build-files")
outputPath.mkdir(exist_ok=True)
results = [
(0, "", "", "asm1", None),
(0, "", "", "asm2", None),
(0, "", "", "asm3", "blah.asm"),
(0, '#include "Kernels1.h"', "#pragma once", "src1", "kfile1"),
(0, '#include "Kernels2.h"', "#pragma twice", "src2", "kfile2"),
(0, '#include "Kernels3.h"', "#pragma thrice", "src3", "kfile3"),
]
filesToWrite = tcl.collectFilesToWrite(results, Path(outputPath), False, False, 1)
kernelFiles = tcl.generateKernelSourceAndHeaderFiles(filesToWrite)
assert (
len(kernelFiles) == 3
), "3 files should be created because they have file names AND some source code, but lazy loading/merge files is False"
assert kernelFiles == [
"no-commit-kernel-build-files/kfile1.cpp",
"no-commit-kernel-build-files/kfile2.cpp",
"no-commit-kernel-build-files/kfile3.cpp",
]
def test_filterBuildErrors():
kernels = [MockSolution(name, "Assembly") for name in ["A", "B", "C"]]
kernels += [MockSolution(name, "Source") for name in ["D", "E", "F"]]
kernelWriter = MockKernelWriter()
kernelsWithBuildErrors = {
"A_name": -2,
"D_name": -2,
}
writerSelector = lambda lang: kernelWriter
def noBuildFailures():
kernelsToBuild = tcl.filterBuildErrors(kernels, {}, writerSelector, ignoreErr=False)
assert kernelsToBuild == kernels, "All kernels should be built without failure"
def buildFailuresIgnoreErr():
expectedKernelsToBuild = [MockSolution(name, "Assembly") for name in ["B", "C"]]
expectedKernelsToBuild += [MockSolution(name, "Source") for name in ["E", "F"]]
kernelsToBuild = tcl.filterBuildErrors(
kernels, kernelsWithBuildErrors, writerSelector, ignoreErr=True
)
print("Kernels wtih build errors:", [k for k in kernelsWithBuildErrors])
print("Kernels to build:", [k.name for k in kernelsToBuild])
assert len(kernelsToBuild) == len(
expectedKernelsToBuild
), "Length of built kernels is incorrect"
assert all(
[
k.name == e.name and k.lang == e.lang
for k, e in zip(kernelsToBuild, expectedKernelsToBuild)
]
), "Kernels should be filtered to only those without build errors"
def buildFailuresNoIgnoreErr():
with pytest.raises(RuntimeError, match=r"Kernel compilation failed (.*)"):
tcl.filterBuildErrors(kernels, kernelsWithBuildErrors, writerSelector, ignoreErr=False)
noBuildFailures()
buildFailuresIgnoreErr()
buildFailuresNoIgnoreErr()
@pytest.fixture
def setup_writeKernelHelpersTests():
kernelFiles = []
kernWriter = MockKernelWriter()
basepath = Path("/fake/path")
return kernelFiles, kernWriter, basepath
def test_writeKernelHelpers_createFiles(
setup_writeKernelHelpersTests, mock_toFile, mock_openFile, mock_getKernelSourceAndHeaderCode
):
kernelFiles, kernWriter, basepath = setup_writeKernelHelpersTests
mock_getKernelSourceAndHeaderCode.return_value = (
0,
["source_code", "abc"],
["header_code", "def"],
"kernelName",
)
tcl.writeKernelHelpers(kernWriter, None, None, basepath, kernelFiles)
assert mock_toFile.call_args_list == [
call(basepath / "Kernels" / "kernelName.cpp", ["source_code", "abc"]),
call(basepath / "Kernels" / "kernelName.h", ["header_code", "def"]),
]
assert kernelFiles == [
"/fake/path/Kernels/kernelName.cpp"
], "kernelFiles should be updated with the path to the new kernel"
def test_writeKernelHelpers_withOpenFiles(
setup_writeKernelHelpersTests,
mock_toFile,
mock_getKernelSourceAndHeaderCode,
mock_kernelSourceAndHeaderFiles,
):
kernelSourceFile, kernelHeaderFile = mock_kernelSourceAndHeaderFiles
kernelFiles, kernWriter, basepath = setup_writeKernelHelpersTests
mock_getKernelSourceAndHeaderCode.return_value = (
0,
["source_code", "abc"],
["header_code", "def"],
"kernelName",
)
tcl.writeKernelHelpers(kernWriter, kernelSourceFile, kernelHeaderFile, basepath, kernelFiles)
expected_calls = [
call(kernelSourceFile, ["source_code", "abc"]),
call(kernelHeaderFile, ["header_code", "def"]),
]
assert mock_toFile.call_args_list == expected_calls
assert kernelFiles == [], "kernelFiles should remain unchanged when opened files are provided"
def test_writeKernelHelpers_failure(
setup_writeKernelHelpersTests, mock_toFile, mock_printWarning, mock_getKernelSourceAndHeaderCode
):
kernelFiles, kernWriter, basepath = setup_writeKernelHelpersTests
mock_getKernelSourceAndHeaderCode.return_value = (
-2,
["// src comment", ""],
["// hdr comment", ""],
"kernelName",
)
tcl.writeKernelHelpers(kernWriter, None, None, basepath, kernelFiles)
mock_printWarning.assert_called_once_with("Invalid kernel: kernelName may be corrupt")
expected_calls = [
call(basepath / "Kernels" / "kernelName.cpp", ["// src comment", ""]),
call(basepath / "Kernels" / "kernelName.h", ["// hdr comment", ""]),
]
assert mock_toFile.call_args_list == expected_calls
assert kernelFiles == [
"/fake/path/Kernels/kernelName.cpp"
], "kernelFiles should be updated with the new kernel name"
def test_getKernelSourceAndHeaderCode_success(mock_KernelWriterBase):
mock_KernelWriterBase.getSourceFileString.return_value = (0, "source_code")
mock_KernelWriterBase.getHeaderFileString.return_value = "header_code"
err, src, hdr, name = tcl.getKernelSourceAndHeaderCode(mock_KernelWriterBase)
mock_KernelWriterBase.getKernelName.assert_called_once_with()
mock_KernelWriterBase.getSourceFileString.assert_called_once_with()
mock_KernelWriterBase.getHeaderFileString.assert_called_once_with()
assert err == 0
assert src == [Common.CHeader, "source_code"]
assert hdr == [Common.CHeader, "header_code"]
assert name == "TestKernelName"
def test_getKernelSourceAndHeaderCode_sourceFailure(mock_KernelWriterBase):
mock_KernelWriterBase.getSourceFileString.return_value = (-1, "")
mock_KernelWriterBase.getHeaderFileString.return_value = "header_code"
err, src, hdr, name = tcl.getKernelSourceAndHeaderCode(mock_KernelWriterBase)
mock_KernelWriterBase.getKernelName.assert_called_once_with()
mock_KernelWriterBase.getSourceFileString.assert_called_once_with()
mock_KernelWriterBase.getHeaderFileString.assert_called_once_with()
assert err == -1
assert src == [Common.CHeader, ""]
assert hdr == [Common.CHeader, "header_code"]
assert name == "TestKernelName"
# ----------------
# Helper functions
# ----------------
def setupClientConfigTest(outputPath, masterLibrary, codeObjectFiles):
outputPath.mkdir()
with open(masterLibrary, "w") as testFile:
testFile.write("foo")
(outputPath / "library").mkdir()
for f in codeObjectFiles:
with open(outputPath / f, "w") as testFile:
testFile.write("foo")
def cleanClientConfigTest(outputPath: Path, masterLibrary, codeObjectFiles, configFile):
with contextlib.suppress(FileNotFoundError):
os.remove(configFile)
os.remove(masterLibrary)
for f in codeObjectFiles:
os.remove(outputPath / f)
next(outputPath.iterdir()).rmdir()
outputPath.rmdir()
def createDirectoryWithYamls(currentDir, prefix, ext, depth=3, nChildren=3):
def recurse(currentDir, depth, nChildren):
if depth == 0:
return
currentDir.mkdir(parents=True, exist_ok=True)
file = f"{prefix}_{str(uuid.uuid4().hex)}.{ext}"
with open(currentDir / file, mode="w"):
pass
for n in range(nChildren):
recurse(currentDir / str(n), depth - 1, nChildren)
recurse(currentDir, depth, nChildren)
def findLogicFiles_oldLogic(logicPath, logicArchs, lazyLoading, experimentalDir):
# Recursive directory search
logicFiles = []
for root, dirs, files in os.walk(str(logicPath)):
logicFiles += [
os.path.join(root, f)
for f in files
if os.path.splitext(f)[1] == ".yaml"
and (
any(logicArch in os.path.splitext(f)[0] for logicArch in logicArchs)
or "hip" in os.path.splitext(f)[0]
)
]
if not lazyLoading:
logicFiles = [f for f in logicFiles if not experimentalDir in f]
return logicFiles
def sanityCheck_oldLogic(sourceLibPaths, asmLibPaths, codeObjectFiles, genSourcesAndExit):
bothLibSet = set(sourceLibPaths + asmLibPaths)
setA = set(map(os.path.normcase, set(codeObjectFiles)))
setB = set(map(os.path.normcase, bothLibSet))
sanityCheck0 = setA - setB
sanityCheck1 = setB - setA
assert len(sanityCheck0) == 0, "Unexpected code object files: {}".format(sanityCheck0)
if not genSourcesAndExit:
assert len(sanityCheck1) == 0, "Missing expected code object files: {}".format(sanityCheck1)
###############################################################################
# Discrimination tests for renameFallbacksPerArch (kpack-collision regression).
#
# Each test below must FAIL under code that lacks renameFallbacksPerArch and
# its idempotent consumer counterpart. They prove the rename actually
# de-collides per-arch fallback file references — not just incidentally.
###############################################################################
def _makeMasterWithFallbackPlaceholder(prefix: str) -> SolutionLibrary.MasterSolutionLibrary:
"""Build a tiny master containing one PlaceholderLibrary in 'fallback' shape."""
ph = SolutionLibrary.PlaceholderLibrary(prefix)
pml = SolutionLibrary.ProblemMapLibrary(mappingProperty=None, mapping={"opId": ph})
pl = SolutionLibrary.PredicateLibrary(tag="Hardware", rows=[{"predicate": "p", "library": pml}])
fakeLazy = SolutionLibrary.MasterSolutionLibrary({}, None)
m = SolutionLibrary.MasterSolutionLibrary({}, pl)
m.lazyLibraries = {prefix: fakeLazy}
return m
def test_renameFallbacksPerArch_perArchKeysDiverge():
"""
Discrimination: under buggy/legacy producer (no rename), the per-arch
masters share the SAME lazyLibraries key (..._fallback) and would
serialize as a single shared *_fallback.dat. The patched producer must
emit distinct per-arch keys so each arch gets its own file.
"""
base = "TensileLibrary_lazy_Bjlk_fallback"
m_alias = _makeMasterWithFallbackPlaceholder(base)
masters = {"gfx1100": m_alias, "gfx1101": m_alias} # alias case from addFallback
tcl.renameFallbacksPerArch(masters)
keys_1100 = set(masters["gfx1100"].lazyLibraries.keys())
keys_1101 = set(masters["gfx1101"].lazyLibraries.keys())
# Buggy code (no rename) leaves both as {base}; patched code must
# produce {base + "_<arch>"} per arch with no overlap.
assert keys_1100 == {
base + "_gfx1100"
}, "gfx1100 lazy key not arch-suffixed; legacy unrouted name still present"
assert keys_1101 == {
base + "_gfx1101"
}, "gfx1101 lazy key not arch-suffixed; legacy unrouted name still present"
assert keys_1100.isdisjoint(
keys_1101
), "per-arch lazy keys must not collide on a shared fallback filename"
def test_renameFallbacksPerArch_placeholderInTreeMatchesLazyKey():
"""
Discrimination: the C++ loader builds the .dat path from the
PlaceholderLibrary's filenamePrefix embedded in the master library tree.
If only the lazyLibraries dict key is renamed but the in-tree placeholder
is not, the runtime would attempt to load the OLD shared name and the
serialized master would be inconsistent with the on-disk file. The patch
must keep both in sync.
"""
base = "TensileLibrary_lazy_Bjlk_fallback"
masters = {"gfx1100": _makeMasterWithFallbackPlaceholder(base)}
tcl.renameFallbacksPerArch(masters)
placeholder = masters["gfx1100"].library.rows[0]["library"].mapping["opId"]
lazyKey = next(iter(masters["gfx1100"].lazyLibraries.keys()))
assert placeholder.filenamePrefix == base + "_gfx1100", (
"placeholder filenamePrefix not arch-suffixed — runtime would load "
"the legacy shared *_fallback.dat path"
)
assert placeholder.filenamePrefix == lazyKey, (
"placeholder filenamePrefix and lazyLibraries key drifted; "
"serialized master would point at a file the writer never produced"
)
def test_renameFallbacksPerArch_aliasedFallbackIsDeepCopied():
"""
Discrimination: addFallback() aliases the same MasterSolutionLibrary
across multiple arch keys when an arch lacks tuned logic
(masterLibraries[archName] = masterLibraries["fallback"]). A naive
in-place rename would mutate the SAME object twice, leaving both arch
masters pointing at whichever rename ran last. Patched code must
deep-copy so the per-arch trees are independent.
"""
base = "TensileLibrary_lazy_Bjlk_fallback"
shared = _makeMasterWithFallbackPlaceholder(base)
masters = {"gfx1100": shared, "gfx1101": shared}
tcl.renameFallbacksPerArch(masters)
assert (
masters["gfx1100"] is not masters["gfx1101"]
), "per-arch masters still alias the same object — rename leaks across arches"
# Each placeholder must hold its own arch's name.
ph_1100 = masters["gfx1100"].library.rows[0]["library"].mapping["opId"]
ph_1101 = masters["gfx1101"].library.rows[0]["library"].mapping["opId"]
assert ph_1100.filenamePrefix.endswith(
"_gfx1100"
), "gfx1100 placeholder not arch-correct after rename"
assert ph_1101.filenamePrefix.endswith(
"_gfx1101"
), "gfx1101 placeholder not arch-correct after rename"