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
1292 lines
44 KiB
C++
1292 lines
44 KiB
C++
/* ************************************************************************
|
|
* Copyright (C) 2025-2026 Advanced Micro Devices, Inc.
|
|
*
|
|
* 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.
|
|
*
|
|
* ************************************************************************ */
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
#include "stinkytofu/core/Function.hpp"
|
|
#include "stinkytofu/hardware/ArchHelper.hpp"
|
|
#include "stinkytofu/ir/asm/StinkyAsmIR.hpp"
|
|
#include "stinkytofu/serialization/asm/StinkyAsmEmitter.hpp"
|
|
|
|
using namespace stinkytofu;
|
|
|
|
// Helper class for building test IR
|
|
class AsmEmitterTest : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
arch = getGfxArchID(12, 5, 0); // GFX1250
|
|
gemmConfig.arch[0] = 12;
|
|
gemmConfig.arch[1] = 5;
|
|
gemmConfig.arch[2] = 0;
|
|
|
|
gemmConfig.TileA0 = 0;
|
|
gemmConfig.TileB0 = 0;
|
|
gemmConfig.TileM0 = 0;
|
|
gemmConfig.NumGRA = 0;
|
|
gemmConfig.NumGRB = 0;
|
|
gemmConfig.NumGRM = 0;
|
|
gemmConfig.NumWaves = 0;
|
|
|
|
passCtx.setGemmTileConfig(gemmConfig);
|
|
|
|
func.setGemmTileConfig(gemmConfig);
|
|
bb = func.createBasicBlock("entry");
|
|
irBuilder = std::make_unique<AsmIRBuilder>(*bb, arch);
|
|
}
|
|
|
|
StinkyInstruction* createInstruction(const std::string& mnemonic) {
|
|
auto opcode = getMnemonicToIsaOpcode(mnemonic, arch);
|
|
const HwInstDesc* hwInstDesc = getMCIDByIsaOp(opcode, arch);
|
|
|
|
if (!hwInstDesc) {
|
|
return nullptr;
|
|
}
|
|
|
|
return irBuilder->create(hwInstDesc);
|
|
}
|
|
|
|
Function func;
|
|
BasicBlock* bb = nullptr;
|
|
PassContext passCtx;
|
|
GemmTileConfig gemmConfig;
|
|
GfxArchID arch;
|
|
std::unique_ptr<AsmIRBuilder> irBuilder;
|
|
};
|
|
|
|
// ============================================================================
|
|
// Basic Emission Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, EmitSingleInstruction) {
|
|
// Create a simple ds_load_b128 instruction
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
// Add destination and source registers
|
|
inst->addDestReg(StinkyRegister("v", 0, 4)); // v[0:3]
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1)); // v[40]
|
|
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
// Emit with default options (emitCycleInfo = false by default)
|
|
StinkyAsmEmitter emitter;
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// Compare exact assembly output (no cycle info with default options)
|
|
std::string expected = " ds_load_b128 v[0:3], v40\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitWithCycleInfo) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = true;
|
|
options.commentAlignColumn = 0; // Disable comment alignment for this test
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = " ds_load_b128 v[0:3], v40 // issue=4 latency=52\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitWithoutCycleInfo) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = " ds_load_b128 v[0:3], v40\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Register Format Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, EmitVectorRegisterRange) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 10, 4)); // v[10:13]
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = " ds_load_b128 v[10:13]\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitSingleVectorRegister) {
|
|
StinkyInstruction* inst = createInstruction("v_mov_b32");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 5, 1)); // v5
|
|
inst->addSrcReg(StinkyRegister("v", 3, 1)); // v3
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = " v_mov_b32 v5, v3\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitAccumulatorRegister) {
|
|
StinkyInstruction* inst = createInstruction("v_wmma_scale_f32_16x16x128_f8f6f4");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 0, 8)); // v[0:7]
|
|
inst->addSrcReg(StinkyRegister("v", 8, 8)); // v[8:15]
|
|
inst->addSrcReg(StinkyRegister("v", 16, 8)); // v[16:23]
|
|
inst->addSrcReg(StinkyRegister("v", 0, 8)); // v[0:7]
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected =
|
|
" v_wmma_scale_f32_16x16x128_f8f6f4 v[0:7], v[8:15], v[16:23], v[0:7]\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitScalarRegister) {
|
|
StinkyInstruction* inst = createInstruction("s_add_i32");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("s", 10, 1)); // s10
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = " s_add_i32 s10\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitLiteralInt) {
|
|
StinkyInstruction* inst = createInstruction("s_waitcnt");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addSrcReg(StinkyRegister(0)); // literal int 0
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = " s_waitcnt 0\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Label Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, EmitLabel) {
|
|
StinkyInstruction* label = irBuilder->createLabel("loop_start");
|
|
ASSERT_NE(label, nullptr);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*label);
|
|
|
|
// Label should not be indented and should end with colon
|
|
std::string expected = "loop_start:\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// ============================================================================
|
|
// IRList Emission Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, EmitIRList) {
|
|
// Create a label
|
|
irBuilder->createLabel("loop_start");
|
|
|
|
// Create two ds_read instructions
|
|
StinkyInstruction* inst1 = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst1, nullptr);
|
|
inst1->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst1->addSrcReg(StinkyRegister("v", 40, 1));
|
|
|
|
StinkyInstruction* inst2 = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst2, nullptr);
|
|
inst2->addDestReg(StinkyRegister("v", 4, 4));
|
|
inst2->addSrcReg(StinkyRegister("v", 41, 1));
|
|
|
|
ASSERT_EQ(bb->size(), 3);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = true;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(func);
|
|
|
|
std::string expected =
|
|
"loop_start:\n"
|
|
" ds_load_b128 v[0:3], v40\n"
|
|
" ds_load_b128 v[4:7], v41\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitIRListWithoutComments) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(func);
|
|
|
|
std::string expected = " ds_load_b128 v[0:3], v40\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Indentation Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, CustomIndentation) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
|
|
AsmEmitterOptions options;
|
|
options.indent = 8;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = " ds_load_b128 v[0:3]\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Stream Emission Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, EmitToStream) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
std::ostringstream oss;
|
|
StinkyAsmEmitter emitter;
|
|
emitter.emit(oss, *inst);
|
|
|
|
std::string assembly = oss.str();
|
|
std::string expected = " ds_load_b128 v[0:3], v40\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitToStreamWithCycleInfo) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = true;
|
|
options.commentAlignColumn = 0; // Disable comment alignment for this test
|
|
|
|
std::ostringstream oss;
|
|
StinkyAsmEmitter emitter(options);
|
|
emitter.emit(oss, *inst);
|
|
|
|
std::string assembly = oss.str();
|
|
std::string expected = " ds_load_b128 v[0:3], v40 // issue=4 latency=52\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Utility Function Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, ToAssemblyUtility) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
|
|
std::string assembly = toAssembly(func, options);
|
|
std::string expected = " ds_load_b128 v[0:3], v40\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, ToAssemblyUtilityWithCycleInfo) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = true;
|
|
options.commentAlignColumn = 0; // Disable comment alignment for this test
|
|
|
|
std::string assembly = toAssembly(func, options);
|
|
std::string expected = " ds_load_b128 v[0:3], v40 // issue=4 latency=52\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, ToAssemblyUtilityWithOptions) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
|
|
std::string assembly = toAssembly(func, options);
|
|
std::string expected = " ds_load_b128 v[0:3], v40\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Options Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, GetSetOptions) {
|
|
StinkyAsmEmitter emitter;
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 2;
|
|
|
|
emitter.setOptions(options);
|
|
|
|
const AsmEmitterOptions& retrievedOptions = emitter.getOptions();
|
|
EXPECT_EQ(retrievedOptions.emitComments, false);
|
|
EXPECT_EQ(retrievedOptions.emitCycleInfo, false);
|
|
EXPECT_EQ(retrievedOptions.indent, 2);
|
|
}
|
|
|
|
// ============================================================================
|
|
// User Comment Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, EmitWithUserComment) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
|
|
// Add a user comment
|
|
inst->addModifier(CommentData("load C"));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = true;
|
|
options.emitCycleInfo = false;
|
|
options.commentAlignColumn = 0; // Disable comment alignment for this test
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = " ds_load_b128 v[0:3], v40 // load C\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitWithCycleInfoAndUserComment) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
// Add a user comment
|
|
inst->addModifier(CommentData("load C"));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = true;
|
|
options.emitCycleInfo = true;
|
|
options.commentAlignColumn = 0; // Disable comment alignment for this test
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// Cycle info should come first, then user comment
|
|
std::string expected = " ds_load_b128 v[0:3], v40 // issue=4 latency=52, load C\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitUserCommentDisabled) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
|
|
// Add a user comment
|
|
inst->addModifier(CommentData("load C"));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false; // Comments disabled
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// User comment should not appear
|
|
std::string expected = " ds_load_b128 v[0:3], v40\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitCycleInfoOnlyWithUserComment) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->setDestRegs({StinkyRegister("v", 0, 4)});
|
|
inst->setSrcRegs({StinkyRegister("v", 40, 1)});
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
// Add a user comment
|
|
inst->addModifier(CommentData("load C"));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false; // User comments disabled
|
|
options.emitCycleInfo = true; // But cycle info enabled
|
|
options.commentAlignColumn = 0; // Disable comment alignment for this test
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// Only cycle info should appear, no user comment
|
|
std::string expected = " ds_load_b128 v[0:3], v40 // issue=4 latency=52\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, VOP3ModifierNegation) {
|
|
StinkyInstruction* inst = createInstruction("v_add_f32");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->setDestRegs({StinkyRegister("v", 0, 1)});
|
|
inst->setSrcRegs({StinkyRegister("v", 1, 1), StinkyRegister("v", 2, 1)});
|
|
|
|
// Add VOP3 modifier to negate src0
|
|
VOP3Modifiers mod;
|
|
mod.neg_src0 = true;
|
|
inst->addModifier(mod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// Should render as: v_add_f32 v0, -v1, v2
|
|
std::string expected = " v_add_f32 v0, -v1, v2\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, VOP3ModifierAbsoluteValue) {
|
|
StinkyInstruction* inst = createInstruction("v_add_f32");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->setDestRegs({StinkyRegister("v", 10, 1)});
|
|
inst->setSrcRegs({StinkyRegister("v", 11, 1), StinkyRegister("v", 12, 1)});
|
|
|
|
// Add VOP3 modifier for absolute value of src0
|
|
VOP3Modifiers mod;
|
|
mod.abs_src0 = true;
|
|
inst->addModifier(mod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// Should render as: v_add_f32 v10, abs(v11), v12
|
|
std::string expected = " v_add_f32 v10, abs(v11), v12\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, VOP3ModifierNegatedAbsoluteValue) {
|
|
StinkyInstruction* inst = createInstruction("v_add_f32");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->setDestRegs({StinkyRegister("v", 20, 1)});
|
|
inst->setSrcRegs({StinkyRegister("v", 21, 1), StinkyRegister("v", 22, 1)});
|
|
|
|
// Add VOP3 modifier for negated absolute value of src0
|
|
VOP3Modifiers mod;
|
|
mod.neg_src0 = true;
|
|
mod.abs_src0 = true;
|
|
inst->addModifier(mod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// Should render as: v_add_f32 v20, -abs(v21), v22
|
|
// This follows LLVM syntax: "-" before "abs()" is allowed
|
|
std::string expected = " v_add_f32 v20, -abs(v21), v22\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, VOP3ModifierMultipleSources) {
|
|
StinkyInstruction* inst = createInstruction("v_fma_f32");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->setDestRegs({StinkyRegister("v", 30, 1)});
|
|
inst->setSrcRegs(
|
|
{StinkyRegister("v", 31, 1), StinkyRegister("v", 32, 1), StinkyRegister("v", 33, 1)});
|
|
|
|
// Add VOP3 modifiers: neg src0, abs src1, neg+abs src2
|
|
VOP3Modifiers mod;
|
|
mod.neg_src0 = true;
|
|
mod.abs_src1 = true;
|
|
mod.neg_src2 = true;
|
|
mod.abs_src2 = true;
|
|
inst->addModifier(mod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// Should render with modifiers on each source according to LLVM syntax
|
|
std::string expected = " v_fma_f32 v30, -v31, abs(v32), -abs(v33)\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// ============================================================================
|
|
// MUBUF "off" vaddr Tests
|
|
//
|
|
// When a BufferStore/Load is created with isOff=true (rocisa vgpr("off",
|
|
// isOff=True)), the conversion layer produces StinkyRegister("off") — a
|
|
// literal-string register — instead of a normal VGPR. The emitter must
|
|
// write the keyword "off", not "v[vgproff]".
|
|
// ============================================================================
|
|
|
|
// buffer_store_b32 vdata=v12, vaddr=off, srsrc=s[60:63], soffset=s46
|
|
TEST_F(AsmEmitterTest, MUBUFStoreB32_OffVAddr) {
|
|
StinkyInstruction* inst = createInstruction("buffer_store_b32");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
// buffer_store_b32 has no dest — only sources: vdata, vaddr, rsrc, soffset
|
|
inst->addSrcReg(StinkyRegister("v", 12, 1)); // vdata
|
|
inst->addSrcReg(StinkyRegister("off")); // vaddr = off keyword
|
|
inst->addSrcReg(StinkyRegister("s", 60, 4)); // rsrc s[60:63]
|
|
inst->addSrcReg(StinkyRegister("s", 46, 1)); // soffset s46
|
|
|
|
// glc+slc on an arch that uses sc0/sc1 naming (gfx1250, hasMUBUFConst=false)
|
|
MUBUFModifiers mubufMod(/*offen=*/false, /*offset12=*/0, /*glc=*/true, /*slc=*/true,
|
|
/*nt=*/false, /*lds=*/false, /*isStore=*/true,
|
|
/*hasMUBUFConst=*/false, /*hasGLCModifier=*/false,
|
|
/*hasSC0Modifier=*/true);
|
|
inst->addModifier(mubufMod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = "buffer_store_b32 v12, off, s[60:63], s46 sc0 sc1\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// buffer_load_b32 vdst=v0, vaddr=off, srsrc=s[4:7], soffset=s3
|
|
TEST_F(AsmEmitterTest, MUBUFLoadB32_OffVAddr) {
|
|
StinkyInstruction* inst = createInstruction("buffer_load_b32");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 0, 1)); // vdst
|
|
inst->addSrcReg(StinkyRegister("off")); // vaddr = off keyword
|
|
inst->addSrcReg(StinkyRegister("s", 4, 4)); // rsrc s[4:7]
|
|
inst->addSrcReg(StinkyRegister("s", 3, 1)); // soffset s3
|
|
|
|
MUBUFModifiers mubufMod(/*offen=*/false, /*offset12=*/0, /*glc=*/false, /*slc=*/false,
|
|
/*nt=*/false, /*lds=*/false, /*isStore=*/false,
|
|
/*hasMUBUFConst=*/false, /*hasGLCModifier=*/false,
|
|
/*hasSC0Modifier=*/false);
|
|
inst->addModifier(mubufMod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected = "buffer_load_b32 v0, off, s[4:7], s3\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, MUBUFScopeModifier) {
|
|
StinkyInstruction* inst = createInstruction("buffer_store_b32");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addSrcReg(StinkyRegister("v", 12, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 32, 1));
|
|
inst->addSrcReg(StinkyRegister("s", 60, 4));
|
|
inst->addSrcReg(StinkyRegister("s", 46, 1));
|
|
|
|
MUBUFModifiers mubufMod(/*offen=*/true, /*offset12=*/0, /*glc=*/false, /*slc=*/false,
|
|
/*nt=*/false, /*lds=*/false, /*isStore=*/true,
|
|
/*hasMUBUFConst=*/false, /*hasGLCModifier=*/false,
|
|
/*hasSC0Modifier=*/false, /*scope=*/MUBUFScope::SCOPE_DEV);
|
|
inst->addModifier(mubufMod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected =
|
|
"buffer_store_b32 v12, v32, s[60:63], s46 offen offset:0 scope:SCOPE_DEV\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, MUBUFTemporalHintModifier) {
|
|
StinkyInstruction* inst = createInstruction("buffer_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 8, 1));
|
|
inst->addSrcReg(StinkyRegister("s", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("s", 0, 1));
|
|
|
|
MUBUFModifiers mubufMod(/*offen=*/true, /*offset12=*/0, /*glc=*/false, /*slc=*/false,
|
|
/*nt=*/false, /*lds=*/false, /*isStore=*/false,
|
|
/*hasMUBUFConst=*/false, /*hasGLCModifier=*/false,
|
|
/*hasSC0Modifier=*/false, /*scope=*/MUBUFScope::SCOPE_CU,
|
|
/*th=*/TemporalHint::TH_RT);
|
|
inst->addModifier(mubufMod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
std::string expected =
|
|
"buffer_load_b128 v[0:3], v8, s[0:3], s0 offen offset:0 scope:SCOPE_CU th:TH_LOAD_RT\n";
|
|
EXPECT_EQ(assembly, expected);
|
|
}
|
|
|
|
// ============================================================================
|
|
// DS Modifier Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, DSModifierSingleOffset) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->addModifier(DSModifiers(/*na=*/1, /*offset=*/128));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_EQ(assembly, "ds_load_b128 v[0:3], v40 offset:128\n");
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, DSModifierDualOffset) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
// na=2 triggers offset0/offset1 emission
|
|
inst->addModifier(DSModifiers(/*na=*/2, /*offset=*/0, /*offset0=*/4, /*offset1=*/8));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_EQ(assembly, "ds_load_b128 v[0:3], v40 offset0:4 offset1:8\n");
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, DSModifierGds) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->addModifier(DSModifiers(/*na=*/1, /*offset=*/0, /*offset0=*/0, /*offset1=*/0, /*gds=*/true));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("gds"), std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// SDWA Modifier Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, SDWAModifierDstSel) {
|
|
StinkyInstruction* inst = createInstruction("v_mov_b32");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 1, 1));
|
|
|
|
SDWAModifiers sdwa(SDWAModifiers::SelectBit::BYTE_0, SDWAModifiers::UnusedBit::UNUSED_PAD,
|
|
SDWAModifiers::SelectBit::BYTE_1, SDWAModifiers::SelectBit::SEL_NONE);
|
|
inst->addModifier(sdwa);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("dst_sel:BYTE_0"), std::string::npos);
|
|
EXPECT_NE(assembly.find("dst_unused:UNUSED_PAD"), std::string::npos);
|
|
EXPECT_NE(assembly.find("src0_sel:BYTE_1"), std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// DPP Modifier Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, DPPModifierRowShr) {
|
|
StinkyInstruction* inst = createInstruction("v_add_f32");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 1, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 2, 1));
|
|
|
|
DPPModifiers dpp(dppRowShr(3));
|
|
inst->addModifier(dpp);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("row_shr:3"), std::string::npos);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, DPPModifierRowMaskAndBankMask) {
|
|
StinkyInstruction* inst = createInstruction("v_add_f32");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 1, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 2, 1));
|
|
|
|
// rowMask != 0xF and bankMask != 0xF trigger emission
|
|
DPPModifiers dpp(dppRowShl(1), /*rowMask=*/0xA, /*bankMask=*/0x5);
|
|
inst->addModifier(dpp);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("row_mask:0xa"), std::string::npos);
|
|
EXPECT_NE(assembly.find("bank_mask:0x5"), std::string::npos);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, DPPModifierBoundCtrlAndFi) {
|
|
StinkyInstruction* inst = createInstruction("v_add_f32");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 1, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 2, 1));
|
|
|
|
DPPModifiers dpp(dppRowShr(1), /*rowMask=*/0xF, /*bankMask=*/0xF, /*boundCtrl=*/1, /*fi=*/true);
|
|
inst->addModifier(dpp);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("bound_ctrl:1"), std::string::npos);
|
|
EXPECT_NE(assembly.find("fi:1"), std::string::npos);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, DPPModifierDPP8) {
|
|
StinkyInstruction* inst = createInstruction("v_add_f32");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 1, 1));
|
|
inst->addSrcReg(StinkyRegister("v", 2, 1));
|
|
|
|
DPPModifiers dpp;
|
|
dpp.isDPP8 = true;
|
|
dpp.dpp8 = {0, 1, 2, 3, 4, 5, 6, 7};
|
|
inst->addModifier(dpp);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("dpp8:[0,1,2,3,4,5,6,7]"), std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// MatrixFmt Modifier Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, MatrixFmtModifier) {
|
|
StinkyInstruction* inst = createInstruction("v_wmma_scale_f32_16x16x128_f8f6f4");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 8));
|
|
inst->addSrcReg(StinkyRegister("v", 8, 8));
|
|
inst->addSrcReg(StinkyRegister("v", 16, 8));
|
|
inst->addSrcReg(StinkyRegister("v", 0, 8));
|
|
|
|
MatrixFmtModifiers fmtMod;
|
|
fmtMod.fmtA = MatrixFmt::FP8;
|
|
fmtMod.fmtB = MatrixFmt::BF8;
|
|
inst->addModifier(fmtMod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("matrix_a_fmt:MATRIX_FMT_FP8"), std::string::npos);
|
|
EXPECT_NE(assembly.find("matrix_b_fmt:MATRIX_FMT_BF8"), std::string::npos);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, MatrixScaleFmtModifier) {
|
|
StinkyInstruction* inst = createInstruction("v_wmma_scale_f32_16x16x128_f8f6f4");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 8));
|
|
inst->addSrcReg(StinkyRegister("v", 8, 8));
|
|
inst->addSrcReg(StinkyRegister("v", 16, 8));
|
|
inst->addSrcReg(StinkyRegister("v", 0, 8));
|
|
|
|
MatrixFmtModifiers fmtMod;
|
|
fmtMod.scaleFmtA = MatrixScaleFmt::E8;
|
|
fmtMod.scaleFmtB = MatrixScaleFmt::E4M3;
|
|
inst->addModifier(fmtMod);
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// Scale formats are emitted as raw integers
|
|
EXPECT_NE(assembly.find("matrix_a_scale_fmt:0"), std::string::npos);
|
|
EXPECT_NE(assembly.find("matrix_b_scale_fmt:2"), std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// s_delay_alu Emission Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, DelayAluSingleDep) {
|
|
StinkyInstruction* inst = createInstruction("s_delay_alu");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addModifier(SDelayAluData(SDelayAluData::InstType::VALU, /*distance=*/1));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("s_delay_alu"), std::string::npos);
|
|
EXPECT_NE(assembly.find("VALU"), std::string::npos);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, DelayAluDualDep) {
|
|
StinkyInstruction* inst = createInstruction("s_delay_alu");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addModifier(SDelayAluData(SDelayAluData::InstType::VALU, /*id0Dist=*/1,
|
|
/*skip=*/1,
|
|
SDelayAluData::InstType::SALU, /*id1Dist=*/2));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("s_delay_alu"), std::string::npos);
|
|
EXPECT_NE(assembly.find("VALU"), std::string::npos);
|
|
EXPECT_NE(assembly.find("SALU"), std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// s_waitcnt with SWaitCntData Emission Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, WaitCntBothZeroEmitsLiteral0) {
|
|
StinkyInstruction* inst = createInstruction("s_waitcnt");
|
|
ASSERT_NE(inst, nullptr);
|
|
// vlcnt=0, dscnt=0 → lgkmcnt=0, vmcnt=0 → emits " 0"
|
|
inst->addModifier(SWaitCntData(/*vlcnt=*/0, /*vscnt=*/-1, /*dlcnt=*/-1, /*dscnt=*/0));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_EQ(assembly, "s_waitcnt 0\n");
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, WaitCntLgkmcntOnly) {
|
|
StinkyInstruction* inst = createInstruction("s_waitcnt");
|
|
ASSERT_NE(inst, nullptr);
|
|
// dscnt=2 → lgkmcnt=2, vmcnt=-1
|
|
inst->addModifier(SWaitCntData(/*vlcnt=*/-1, /*vscnt=*/-1, /*dlcnt=*/-1, /*dscnt=*/2));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_EQ(assembly, "s_waitcnt lgkmcnt(2)\n");
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, WaitCntVmcntOnly) {
|
|
StinkyInstruction* inst = createInstruction("s_waitcnt");
|
|
ASSERT_NE(inst, nullptr);
|
|
// vlcnt=4 → lgkmcnt=-1, vmcnt=4
|
|
inst->addModifier(SWaitCntData(/*vlcnt=*/4));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_EQ(assembly, "s_waitcnt vmcnt(4)\n");
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, WaitCntBothCounts) {
|
|
StinkyInstruction* inst = createInstruction("s_waitcnt");
|
|
ASSERT_NE(inst, nullptr);
|
|
// dscnt=1 → lgkmcnt=1, vlcnt=3 → vmcnt=3
|
|
inst->addModifier(SWaitCntData(/*vlcnt=*/3, /*vscnt=*/-1, /*dlcnt=*/-1, /*dscnt=*/1));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("lgkmcnt(1)"), std::string::npos);
|
|
EXPECT_NE(assembly.find("vmcnt(3)"), std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// GLOBAL Modifier Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, GLOBALModifierOffset) {
|
|
// global_prefetch_b8 is the gfx1250 VGLOBAL instruction that carries GLOBALModifiers
|
|
StinkyInstruction* inst = createInstruction("global_prefetch_b8");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addSrcReg(StinkyRegister("v", 2, 2)); // vaddr
|
|
inst->addSrcReg(StinkyRegister("s", 0, 2)); // saddr
|
|
inst->addModifier(GLOBALModifiers(/*offset=*/256));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("offset:256"), std::string::npos);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, GLOBALModifierScopeAndTh) {
|
|
StinkyInstruction* inst = createInstruction("global_prefetch_b8");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addSrcReg(StinkyRegister("v", 2, 2));
|
|
inst->addSrcReg(StinkyRegister("s", 0, 2));
|
|
inst->addModifier(GLOBALModifiers(/*offset=*/0, TemporalHint::TH_NT, MUBUFScope::SCOPE_SE));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("scope:SCOPE_SE"), std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// VOP3P Modifier Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, VOP3PModifierOpSel) {
|
|
StinkyInstruction* inst = createInstruction("v_wmma_f32_16x16x16_f16");
|
|
if (!inst) GTEST_SKIP() << "v_wmma_f32_16x16x16_f16 not available on this arch";
|
|
|
|
// Operands must be present so emitOperands() runs and emits the VOP3P modifier
|
|
inst->addDestReg(StinkyRegister("v", 0, 8));
|
|
inst->addSrcReg(StinkyRegister("v", 8, 8));
|
|
inst->addSrcReg(StinkyRegister("v", 16, 8));
|
|
inst->addSrcReg(StinkyRegister("v", 0, 8));
|
|
inst->addModifier(VOP3PModifiers({0, 1}, {1, 0}, {}));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("op_sel:"), std::string::npos);
|
|
EXPECT_NE(assembly.find("op_sel_hi:"), std::string::npos);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Multiple Basic Blocks and Blank Lines
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, MultipleBasicBlocks) {
|
|
BasicBlock* bb2 = func.createBasicBlock("exit");
|
|
|
|
irBuilder->createLabel("entry");
|
|
StinkyInstruction* inst1 = createInstruction("v_mov_b32");
|
|
ASSERT_NE(inst1, nullptr);
|
|
inst1->addDestReg(StinkyRegister("v", 0, 1));
|
|
inst1->addSrcReg(StinkyRegister("v", 1, 1));
|
|
|
|
AsmIRBuilder builder2(*bb2, arch);
|
|
builder2.createLabel("exit");
|
|
builder2.create(getMCIDByUOp(GFX::s_nop, arch));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(func);
|
|
|
|
EXPECT_NE(assembly.find("entry:"), std::string::npos);
|
|
EXPECT_NE(assembly.find("exit:"), std::string::npos);
|
|
EXPECT_NE(assembly.find("v_mov_b32"), std::string::npos);
|
|
EXPECT_NE(assembly.find("s_nop"), std::string::npos);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, CommentAlignColumn) {
|
|
StinkyInstruction* inst = createInstruction("ds_load_b128");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister("v", 0, 4));
|
|
inst->addSrcReg(StinkyRegister("v", 40, 1));
|
|
inst->issueCycles = 4;
|
|
inst->latencyCycles = 52;
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitCycleInfo = true;
|
|
options.commentAlignColumn = 60;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
// Comment should appear after alignment padding
|
|
auto commentPos = assembly.find("//");
|
|
EXPECT_NE(commentPos, std::string::npos);
|
|
EXPECT_GE(commentPos, 40u);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Special Register Emission Tests
|
|
// ============================================================================
|
|
|
|
TEST_F(AsmEmitterTest, EmitExecRegister) {
|
|
StinkyInstruction* inst = createInstruction("s_mov_b64");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister(RegType::EXEC, 0, 1));
|
|
inst->addSrcReg(StinkyRegister("s", 0, 2));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("exec"), std::string::npos);
|
|
}
|
|
|
|
TEST_F(AsmEmitterTest, EmitVccRegister) {
|
|
StinkyInstruction* inst = createInstruction("s_mov_b32");
|
|
ASSERT_NE(inst, nullptr);
|
|
inst->addDestReg(StinkyRegister(RegType::VCC_LO, 0, 1));
|
|
inst->addSrcReg(StinkyRegister("s", 0, 1));
|
|
|
|
AsmEmitterOptions options;
|
|
options.emitComments = false;
|
|
options.emitCycleInfo = false;
|
|
options.indent = 0;
|
|
options.emitBlankLines = false;
|
|
|
|
StinkyAsmEmitter emitter(options);
|
|
std::string assembly = emitter.emit(*inst);
|
|
|
|
EXPECT_NE(assembly.find("vcc"), std::string::npos);
|
|
}
|