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
827 lines
36 KiB
C++
827 lines
36 KiB
C++
// Copyright (c) 2026 Advanced Micro Devices, Inc.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
/// @file v_add_simd_benchmark.cpp
|
|
/// @brief Microbenchmark of v_add_f32 / v_add_u32 (CDNA4, VOP2 form). The
|
|
/// execute mode is fixed per process by `RJ_FORCE_SCALAR` (force_scalar() is
|
|
/// immutable), so each run times one mode and reports ns/instruction. Compare
|
|
/// SIMD vs scalar throughput by running twice: `RJ_FORCE_SCALAR=1` (scalar)
|
|
/// then unset (SIMD). Scalar-vs-SIMD bit-equivalence is verified separately by
|
|
/// the *SimdCorrectness* suites (in-process double-run + EXPECT_EQ), not here.
|
|
|
|
#include "rocjitsu/code/rj_code.h"
|
|
#include "rocjitsu/isa/arch/amdgpu/shared/execute_shared.h"
|
|
#include "rocjitsu/isa/decoder.h"
|
|
#include "rocjitsu/isa/instruction.h"
|
|
#include "rocjitsu/vm/amdgpu/compute_unit.h"
|
|
#include "rocjitsu/vm/amdgpu/gpu_memory.h"
|
|
#include "rocjitsu/vm/amdgpu/l2_cache.h"
|
|
#include "rocjitsu/vm/amdgpu/wavefront.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <array>
|
|
#include <bit>
|
|
#include <chrono>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <memory>
|
|
#include <random>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
using namespace rocjitsu;
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
constexpr uint32_t WF_SIZE = 64;
|
|
constexpr uint32_t SGPRS_PER_WF = 106;
|
|
constexpr uint32_t VGPRS_PER_WF = 256;
|
|
constexpr int ITERATIONS = 200'000;
|
|
|
|
// CDNA4 VOP2 encoding helper: opcode in [30:25], vdst in [24:17],
|
|
// vsrc1 in [16:9], src0 in [8:0]. Bit 31 = 0 for VOP2.
|
|
constexpr uint32_t vop2_encode(uint32_t opcode, uint32_t vdst, uint32_t vsrc1, uint32_t src0) {
|
|
return ((opcode & 0x3F) << 25) | ((vdst & 0xFF) << 17) | ((vsrc1 & 0xFF) << 9) | (src0 & 0x1FF);
|
|
}
|
|
|
|
// CDNA4 VOP1 encoding: [31:25]=0b0111111, vdst[24:17], op[16:9], src0[8:0].
|
|
constexpr uint32_t vop1_encode(uint32_t opcode, uint32_t vdst, uint32_t src0) {
|
|
return (0x3Fu << 25) | ((vdst & 0xFF) << 17) | ((opcode & 0xFF) << 9) | (src0 & 0x1FF);
|
|
}
|
|
|
|
// CDNA4 VOP3 binary encoding ([31:26]=0x34): word0 = vdst[7:0] | abs[10:8] |
|
|
// clamp[15] | op[25:16]; word1 = src0[8:0] | src1[17:9] | omod[28:27] | neg[31:29].
|
|
constexpr void vop3_bin_encode(uint32_t op, uint32_t vdst, uint32_t src0, uint32_t src1,
|
|
uint32_t abs, uint32_t neg, uint32_t omod, uint32_t clamp,
|
|
uint32_t &w0, uint32_t &w1) {
|
|
w0 = (vdst & 0xFF) | ((abs & 0x7) << 8) | ((clamp & 0x1) << 15) | ((op & 0x3FF) << 16) |
|
|
(0x34u << 26);
|
|
w1 = (src0 & 0x1FF) | ((src1 & 0x1FF) << 9) | ((omod & 0x3) << 27) | ((neg & 0x7) << 29);
|
|
}
|
|
|
|
// CDNA4 VOP3 ternary encoding (src0/src1/src2, no modifiers). Same word0 as
|
|
// vop3_bin_encode; word1 adds src2 in [26:18].
|
|
constexpr void vop3_tern_encode(uint32_t op, uint32_t vdst, uint32_t src0, uint32_t src1,
|
|
uint32_t src2, uint32_t &w0, uint32_t &w1) {
|
|
w0 = (vdst & 0xFF) | ((op & 0x3FF) << 16) | (0x34u << 26);
|
|
w1 = (src0 & 0x1FF) | ((src1 & 0x1FF) << 9) | ((src2 & 0x1FF) << 18);
|
|
}
|
|
|
|
// CDNA4 VOP3P encoding (matches Vop3pMachineInst bitfields). word0 = vdst[7:0] |
|
|
// neg_hi[10:8] | op_sel[13:11] | op_sel_hi_2[14] | clamp[15] | op[22:16] |
|
|
// 0x1A7<<23; word1 = src0[8:0] | src1[17:9] | src2[26:18] | op_sel_hi[28:27] |
|
|
// neg[31:29]. Default packing for the f32 pk fast path is op_sel = 0,
|
|
// op_sel_hi = 3 (the binary-f32 probe bails to scalar otherwise).
|
|
constexpr void vop3p_encode(uint32_t op, uint32_t vdst, uint32_t src0, uint32_t src1, uint32_t src2,
|
|
uint32_t op_sel, uint32_t op_sel_hi, uint32_t op_sel_hi_2, uint32_t neg,
|
|
uint32_t neg_hi, uint32_t clamp, uint32_t &w0, uint32_t &w1) {
|
|
w0 = (vdst & 0xFFu) | ((neg_hi & 0x7u) << 8) | ((op_sel & 0x7u) << 11) |
|
|
((op_sel_hi_2 & 0x1u) << 14) | ((clamp & 0x1u) << 15) | ((op & 0x7Fu) << 16) |
|
|
(0x1A7u << 23);
|
|
w1 = (src0 & 0x1FFu) | ((src1 & 0x1FFu) << 9) | ((src2 & 0x1FFu) << 18) |
|
|
((op_sel_hi & 0x3u) << 27) | ((neg & 0x7u) << 29);
|
|
}
|
|
|
|
struct BenchFixture {
|
|
amdgpu::GpuMemory gpu_mem;
|
|
amdgpu::L2Cache l2;
|
|
std::unique_ptr<amdgpu::ComputeUnitCore> cu;
|
|
std::unique_ptr<Decoder> decoder;
|
|
amdgpu::Wavefront *wf = nullptr;
|
|
|
|
BenchFixture() : gpu_mem("vadd_simd_bench_mem"), l2("vadd_simd_bench_l2") {
|
|
amdgpu::ComputeUnitCore::Config cfg{};
|
|
cfg.arch = ROCJITSU_CODE_ARCH_CDNA4;
|
|
cfg.num_wf_slots = 1;
|
|
cfg.sgprs_per_wf = SGPRS_PER_WF;
|
|
cfg.vgprs_per_wf = VGPRS_PER_WF;
|
|
cfg.lds_size_kb = 64;
|
|
cu = amdgpu::ComputeUnitCore::create("cu_vadd_simd", cfg, &gpu_mem, &l2);
|
|
decoder = Decoder::create(ROCJITSU_CODE_ARCH_CDNA4);
|
|
wf = cu->dispatch_wf(0, 0, SGPRS_PER_WF, VGPRS_PER_WF);
|
|
}
|
|
|
|
// Fill v0 and v1 with deterministic random uint32 values across all lanes.
|
|
// If `sanitize_finite` is set, force each lane to a finite normal IEEE-754
|
|
// binary32 value (no NaN, no Inf, no denormal): clear the sign bit (sign
|
|
// stays zero — we don't need negatives for the host-SIMD equivalence
|
|
// check), then remap the exponent field into a finite-normal range.
|
|
// Mantissa untouched. v_add_f32 over these inputs produces a bit-identical
|
|
// result on host SIMD vs the scalar generated body.
|
|
void seed_inputs(uint64_t seed, bool sanitize_finite = false) {
|
|
std::mt19937_64 rng(seed);
|
|
uint32_t vbase = wf->vgpr_alloc().base;
|
|
auto sanitize = [](uint32_t raw) -> uint32_t {
|
|
uint32_t mantissa = raw & 0x007FFFFFu;
|
|
uint32_t raw_exp = (raw >> 23) & 0xFFu;
|
|
uint32_t exp = 0x40u | (raw_exp & 0x7Eu);
|
|
return (exp << 23) | mantissa;
|
|
};
|
|
for (uint32_t lane = 0; lane < WF_SIZE; ++lane) {
|
|
uint32_t r0 = static_cast<uint32_t>(rng());
|
|
uint32_t r1 = static_cast<uint32_t>(rng());
|
|
if (sanitize_finite) {
|
|
r0 = sanitize(r0);
|
|
r1 = sanitize(r1);
|
|
}
|
|
cu->write_vgpr(vbase + 0, lane, r0);
|
|
cu->write_vgpr(vbase + 1, lane, r1);
|
|
cu->write_vgpr(vbase + 2, lane, 0u); // dst
|
|
// Extra inputs for the ternary / 64-bit-pair / VOP3P cases below
|
|
// (v2..v5). Seeded with the same deterministic stream so the f64 / pk
|
|
// forms read finite (sanitized when requested) values, not stale VGPRs.
|
|
uint32_t r2 = static_cast<uint32_t>(rng());
|
|
uint32_t r3 = static_cast<uint32_t>(rng());
|
|
uint32_t r4 = static_cast<uint32_t>(rng());
|
|
uint32_t r5 = static_cast<uint32_t>(rng());
|
|
if (sanitize_finite) {
|
|
r2 = sanitize(r2);
|
|
r3 = sanitize(r3);
|
|
r4 = sanitize(r4);
|
|
r5 = sanitize(r5);
|
|
}
|
|
cu->write_vgpr(vbase + 2, lane, r2);
|
|
cu->write_vgpr(vbase + 3, lane, r3);
|
|
cu->write_vgpr(vbase + 4, lane, r4);
|
|
cu->write_vgpr(vbase + 5, lane, r5);
|
|
}
|
|
wf->set_exec(~0ULL); // All lanes active.
|
|
}
|
|
|
|
std::array<uint32_t, WF_SIZE> snapshot_v2() const {
|
|
std::array<uint32_t, WF_SIZE> out{};
|
|
uint32_t vbase = wf->vgpr_alloc().base;
|
|
for (uint32_t lane = 0; lane < WF_SIZE; ++lane)
|
|
out[lane] = cu->read_vgpr(vbase + 2, lane);
|
|
return out;
|
|
}
|
|
};
|
|
|
|
struct ModeStats {
|
|
double ns_per_inst = 0;
|
|
double mips = 0;
|
|
uint64_t total_ns = 0;
|
|
};
|
|
|
|
ModeStats time_mode(BenchFixture &fx, Instruction *inst, uint64_t seed, bool sanitize_finite) {
|
|
fx.seed_inputs(seed, sanitize_finite);
|
|
// Warmup.
|
|
for (int i = 0; i < 100; ++i)
|
|
fx.cu->execute_instruction(inst, *fx.wf);
|
|
fx.seed_inputs(seed, sanitize_finite);
|
|
auto t0 = Clock::now();
|
|
for (int i = 0; i < ITERATIONS; ++i)
|
|
fx.cu->execute_instruction(inst, *fx.wf);
|
|
auto t1 = Clock::now();
|
|
ModeStats s;
|
|
s.total_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(t1 - t0).count();
|
|
s.ns_per_inst = static_cast<double>(s.total_ns) / ITERATIONS;
|
|
s.mips = (1e9 / s.ns_per_inst) / 1e6;
|
|
return s;
|
|
}
|
|
|
|
void run_words(const char *label, uint32_t w0, uint32_t w1, bool sanitize_finite) {
|
|
BenchFixture fx;
|
|
ASSERT_NE(fx.cu, nullptr);
|
|
ASSERT_NE(fx.decoder, nullptr);
|
|
ASSERT_NE(fx.wf, nullptr);
|
|
|
|
uint32_t words[4] = {w0, w1, 0u, 0u};
|
|
Instruction *inst = fx.decoder->decode(words);
|
|
ASSERT_NE(inst, nullptr) << label << ": decode failed";
|
|
|
|
constexpr uint64_t SEED = 0xC0FFEE'1234'5678ULL;
|
|
|
|
// This benchmark times whichever execute mode the process starts in (seeded
|
|
// from RJ_FORCE_SCALAR). Scalar-vs-SIMD bit-equivalence is checked separately
|
|
// by the *SimdCorrectness* suites (in-process double-run + EXPECT_EQ). To
|
|
// compare throughput, run the benchmark twice: `RJ_FORCE_SCALAR=1 ...`
|
|
// (scalar) and unset (SIMD).
|
|
const char *mode = amdgpu::simd_force_scalar() ? "scalar" : "simd";
|
|
ModeStats s = time_mode(fx, inst, SEED, sanitize_finite);
|
|
|
|
std::printf("\n === %s (CDNA4, wave64) [%s] ===\n"
|
|
" iterations: %d enc: 0x%08x 0x%08x\n"
|
|
" %-6s : %7.1f ns/inst (%6.2f MIPS) wall %.1f ms\n",
|
|
label, mode, ITERATIONS, w0, w1, mode, s.ns_per_inst, s.mips,
|
|
static_cast<double>(s.total_ns) / 1e6);
|
|
|
|
EXPECT_GT(s.mips, 0.01) << label << ": " << mode << " path too slow";
|
|
|
|
delete inst;
|
|
}
|
|
|
|
// Single-word (VOP1/VOP2/VOPC) wrapper.
|
|
void run_one(const char *label, uint32_t encoding_word, bool sanitize_finite) {
|
|
run_words(label, encoding_word, 0u, sanitize_finite);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// v_add_f32 v2, v0, v1 (CDNA4 VOP2 opcode 1)
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddF32_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
// src0 = 256 (= v0 in VOP2 SRC encoding), vsrc1 = 1 (= v1), vdst = 2 (= v2).
|
|
uint32_t enc = vop2_encode(/*opcode=*/1, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_add_f32 v2, v0, v1", enc, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_add_u32 v2, v0, v1 (CDNA4 VOP2 opcode 52, no carry)
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddU32_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop2_encode(/*opcode=*/52, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_add_u32 v2, v0, v1", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_add_u16 v2, v0, v1 (CDNA4 VOP2 opcode 38) — 16-bit integer, low-16 path.
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddU16_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop2_encode(/*opcode=*/38, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_add_u16 v2, v0, v1", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_add_f16 v2, v0, v1 (CDNA4 VOP2 opcode 31) — f16 op via f32 intermediate.
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddF16_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop2_encode(/*opcode=*/31, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_add_f16 v2, v0, v1", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_add_co_u32 v2, v0, v1 (CDNA4 VOP2 opcode 25) — carry-out into VCC. The
|
|
// SIMD path pays a per-chunk VCC pack/merge (and a scalar carry-in expand)
|
|
// on top of the binary add, so this shows the carry-glue overhead vs v_add_u32.
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddCoU32_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop2_encode(/*opcode=*/25, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_add_co_u32 v2, v0, v1", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_fmaak_f32 v2, v0, v1, K (CDNA4 VOP2 opcode 24) — dst = fma(v0, v1, K). The
|
|
// inline literal K is words[1] (0.0f here), so this exercises the literal-form
|
|
// ternary glue. Inputs sanitized to finite for bit-exact comparison.
|
|
//
|
|
// NOTE: the dst-accumulate forms (v_fmac/v_mac, dst = fma(s0, s1, dst)) are not
|
|
// benchmarked here. Looping the *same* instruction re-creates a loop-carried
|
|
// RAW dependency on the accumulator register, which serializes both the scalar
|
|
// and SIMD runs on store->load latency rather than compute throughput, so the
|
|
// measured "speedup" (~1x) reflects the microbenchmark shape, not the fast path
|
|
// (which does engage — see Vop2FmaSimdCorrectness). The literal form below has
|
|
// no such loop-carried dependency and shows the representative speedup.
|
|
TEST(VAddSimdBenchmark, Cdna4_VFmaakF32_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop2_encode(/*opcode=*/24, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_fmaak_f32 v2, v0, v1, 0", enc, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_cndmask_b32 v2, v0, v1 (CDNA4 VOP2 opcode 0) — VCC-driven per-lane select.
|
|
// VCC defaults to 0 here (all lanes pick src0); still drives the SIMD blend.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCndmaskB32_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop2_encode(/*opcode=*/0, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_cndmask_b32 v2, v0, v1", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_mul_hi_u32_u24 v2, v0, v1 (CDNA4 VOP2 opcode 9) — 48-bit product, high 32
|
|
// bits via the 64-bit-lane widening path. Shows the widening-mul overhead.
|
|
TEST(VAddSimdBenchmark, Cdna4_VMulHiU32U24_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop2_encode(/*opcode=*/9, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_mul_hi_u32_u24 v2, v0, v1", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_max_f32 v2, v0, v1 (CDNA4 VOP2 opcode 11) — std::fmax; inputs sanitized to
|
|
// finite normals for the bit-exact pre-check.
|
|
TEST(VAddSimdBenchmark, Cdna4_VMaxF32_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop2_encode(/*opcode=*/11, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_max_f32 v2, v0, v1", enc, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_ldexp_f16 v2, v0, v1 (CDNA4 VOP2 opcode 51) — f16 ldexp; vsrc1 is the
|
|
// signed exponent. Raw inputs (ldexp NaN handling is bit-exact).
|
|
TEST(VAddSimdBenchmark, Cdna4_VLdexpF16_Vop2) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop2_encode(/*opcode=*/51, /*vdst=*/2, /*vsrc1=*/1, /*src0=*/256);
|
|
run_one("v_ldexp_f16 v2, v0, v1", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_ceil_f16 v2, v0 (CDNA4 VOP1 opcode 69) — f16 op via f32 intermediate.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCeilF16_Vop1) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop1_encode(/*opcode=*/69, /*vdst=*/2, /*src0=*/256);
|
|
run_one("v_ceil_f16 v2, v0", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cmp_lt_f64 (VOPC64 opcode 97): a real (non-accumulate) 64-bit-lane SIMD op
|
|
// exercising the split lo/hi VGPR-pair read path. src0 = v0:v1, vsrc1 = v2:v3;
|
|
// the compare result lands in VCC (run_one's v2 correctness pre-check is a no-op
|
|
// here, but the timing is what matters). f64 fmac is NOT benched: its dst-
|
|
// accumulate loop-carried RAW serializes both modes into a ~1x artifact.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpLtF64_Vopc) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
// VOPC: [31:25]=0x3E, op[24:17], vsrc1[16:9], src0[8:0].
|
|
uint32_t enc = (0x3Eu << 25) | (97u << 17) | (2u << 9) | 256u;
|
|
run_one("v_cmp_lt_f64 v0:v1, v2:v3", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cvt_i32_f64 v2, v0:v1 (CDNA4 VOP1 opcode 3) — f64 src -> 32-bit i32 dst via
|
|
// the mixed-width cvt glue (8-wide f64 chunk, scalar lo/hi gather, clamp/NaN in
|
|
// the double domain). Exact for any input (NaN->0, saturating clamp), so the
|
|
// strict bit-exact pre-check holds on raw random doubles.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCvtI32F64_Vop1) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop1_encode(/*opcode=*/3, /*vdst=*/2, /*src0=*/256);
|
|
run_one("v_cvt_i32_f64 v2, v0:v1", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cvt_f64_i32 v2:v3, v0 (CDNA4 VOP1 opcode 4) — 32-bit i32 src -> f64 dst
|
|
// (exact widening) via the b32->f64 cvt glue (narrow32 load, write_simd64).
|
|
TEST(VAddSimdBenchmark, Cdna4_VCvtF64I32_Vop1) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = vop1_encode(/*opcode=*/4, /*vdst=*/2, /*src0=*/256);
|
|
run_one("v_cvt_f64_i32 v2:v3, v0", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cmp_class_f32 (VOPC opcode 16): src0 = v0, vsrc1 = v1 (10-bit class mask);
|
|
// result -> VCC. The class-decode functor over the 32-bit VOPC glue. v2 is
|
|
// untouched, so run_one's correctness pre-check is a no-op; timing is the point.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpClassF32_Vopc) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = (0x3Eu << 25) | (16u << 17) | (1u << 9) | 256u;
|
|
run_one("v_cmp_class_f32 v0, v1", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cmp_class_f64 (VOPC opcode 18): src0 = v0:v1 (f64), vsrc1 = v2 (32-bit mask);
|
|
// result -> VCC. The mixed-width class glue (read_simd64 value + narrow32 mask).
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpClassF64_Vopc) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t enc = (0x3Eu << 25) | (18u << 17) | (2u << 9) | 256u;
|
|
run_one("v_cmp_class_f64 v0:v1, v2", enc, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cmp_class_f32_e64 (VOP3 opcode 16): the VOP3 form of v_cmp_class — abs/neg
|
|
// modifiers (0 here), mask from src1=v1, result into the SGPR-pair dst (VCC).
|
|
// Shows the VOP3 class glue cost vs the VOPC form. word0[31:26]=0x34.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpClassF32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0 = (106u & 0xFF) | ((16u & 0x3FF) << 16) | (0x34u << 26); // vdst=VCC, op=16
|
|
uint32_t w1 = 256u | (257u << 9); // src0=v0, src1=v1
|
|
run_words("v_cmp_class_f32_e64 v0, v1 -> vcc", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// === VOP3-encoded binary twins (src0/src1, abs/neg/omod/clamp modifiers) ===
|
|
|
|
// v_add_f32_e64 v2, v0, v1 (CDNA4 VOP3 opcode 257) — no modifiers. Baseline
|
|
// VOP3 f32 binary; should match the VOP2 v_add_f32 speedup (same functor, the
|
|
// fp glue's modifier reads are zero-cost when all modifier fields are 0).
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddF32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(257, /*vdst=*/2, /*src0=*/256, /*src1=*/257, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_add_f32_e64 v2, v0, v1", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_add_f32_e64 v2, |v0|, -v1 *2 clamp (CDNA4 VOP3 opcode 257) — all four
|
|
// modifier kinds active. Shows the in-vector abs/neg/omod/clamp cost vs the
|
|
// no-modifier form above. abs=src0, neg=src1, omod=1 (*2), clamp=1.
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddF32_Vop3_Modifiers) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(257, /*vdst=*/2, /*src0=*/256, /*src1=*/257, /*abs=*/0x1, /*neg=*/0x2, /*omod=*/1,
|
|
/*clamp=*/1, w0, w1);
|
|
run_words("v_add_f32_e64 |v0|,-v1 *2 clamp", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_mul_f32_e64 v2, v0, v1 (CDNA4 VOP3 opcode 261) — no modifiers.
|
|
TEST(VAddSimdBenchmark, Cdna4_VMulF32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(261, /*vdst=*/2, /*src0=*/256, /*src1=*/257, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_mul_f32_e64 v2, v0, v1", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_and_b32_e64 v2, v0, v1 (CDNA4 VOP3 opcode 275) — integer/bitwise, plain.
|
|
TEST(VAddSimdBenchmark, Cdna4_VAndB32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(275, /*vdst=*/2, /*src0=*/256, /*src1=*/257, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_and_b32_e64 v2, v0, v1", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_add_u32_e64 v2, v0, v1 (CDNA4 VOP3 opcode 308) — integer, plain.
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddU32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(308, /*vdst=*/2, /*src0=*/256, /*src1=*/257, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_add_u32_e64 v2, v0, v1", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// === VOP3-encoded unary twins ===
|
|
|
|
// v_floor_f32_e64 v2, v0 (CDNA4 VOP3 opcode 351) — f32 unary, no modifiers.
|
|
TEST(VAddSimdBenchmark, Cdna4_VFloorF32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(351, /*vdst=*/2, /*src0=*/256, /*src1=*/0, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_floor_f32_e64 v2, v0", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_floor_f32_e64 v2, -|v0| *2 clamp (opcode 351) — full modifier set, shows
|
|
// the in-vector abs/neg/omod/clamp cost on the unary fp path.
|
|
TEST(VAddSimdBenchmark, Cdna4_VFloorF32_Vop3_Modifiers) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(351, /*vdst=*/2, /*src0=*/256, /*src1=*/0, /*abs=*/0x1, /*neg=*/0x1, /*omod=*/1,
|
|
/*clamp=*/1, w0, w1);
|
|
run_words("v_floor_f32_e64 -|v0| *2 clamp", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_cvt_f32_i32_e64 v2, v0 (CDNA4 VOP3 opcode 325) — plain int->float cvt
|
|
// (reuses the VOP1 unary path, no modifiers).
|
|
TEST(VAddSimdBenchmark, Cdna4_VCvtF32I32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(325, /*vdst=*/2, /*src0=*/256, /*src1=*/0, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_cvt_f32_i32_e64 v2, v0", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// === VOP3 form of the VOPC integer compares ===
|
|
// dst is the SGPR-pair (we point at VCC=106) instead of the fixed VCC. v2 stays
|
|
// untouched, so the correctness pre-check is a no-op (the compare result lands in
|
|
// VCC); timing is the point.
|
|
|
|
// v_cmp_lt_i32_e64 vcc, v0, v1 (CDNA4 VOP3 opcode 193) — 32-bit-lane integer.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpLtI32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(193, /*vdst=*/106, /*src0=*/256, /*src1=*/257, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_cmp_lt_i32_e64 vcc, v0, v1", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cmp_lt_i64_e64 vcc, v0:v1, v2:v3 (CDNA4 VOP3 opcode 225) — 64-bit-lane
|
|
// integer; exercises the split lo/hi VGPR-pair gather/scatter path (~half the
|
|
// 32-bit speedup because 8-wide chunks + scalar lo/hi reads).
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpLtI64_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(225, /*vdst=*/106, /*src0=*/256, /*src1=*/258, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_cmp_lt_i64_e64 vcc, v0:v1, v2:v3", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cmp_eq_f32_e64 vcc, v0, v1 (CDNA4 VOP3 opcode 66) — f32 with no modifiers.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpEqF32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(66, /*vdst=*/106, /*src0=*/256, /*src1=*/257, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_cmp_eq_f32_e64 vcc, v0, v1", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_cmp_eq_f32_e64 vcc, |v0|, -v1 (opcode 66) — both src abs/neg modifiers
|
|
// set; shows the in-vector apply_vop3_src_mod_f32 cost vs the no-modifier form.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpEqF32_Vop3_Modifiers) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(66, /*vdst=*/106, /*src0=*/256, /*src1=*/257, /*abs=*/0x1, /*neg=*/0x2, 0, 0, w0,
|
|
w1);
|
|
run_words("v_cmp_eq_f32_e64 vcc, |v0|, -v1", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_cmp_eq_f16_e64 vcc, v0, v1 (CDNA4 VOP3 opcode 34) — f16 widen-then-compare.
|
|
// Pays the f16_to_f32_simd widen tax on both operands; no modifiers.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpEqF16_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(34, /*vdst=*/106, /*src0=*/256, /*src1=*/257, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_cmp_eq_f16_e64 vcc, v0, v1", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cmp_eq_f64_e64 vcc, v0:v1, v2:v3 (CDNA4 VOP3 opcode 98) — 64-bit-lane
|
|
// f64, split lo/hi gather/scatter, no modifiers.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCmpEqF64_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(98, /*vdst=*/106, /*src0=*/256, /*src1=*/258, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_cmp_eq_f64_e64 vcc, v0:v1, v2:v3", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// === VOP3 integer ternary (src0/src1/src2, no modifiers) ===
|
|
//
|
|
// v_add3_u32 v2, v0, v1, v3 (CDNA4 VOP3 opcode 511) — plain element-wise
|
|
// a + b + c on 32-bit lanes. src2 is wired through the ternary VOP3 glue.
|
|
TEST(VAddSimdBenchmark, Cdna4_VAdd3U32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
// VOP3: word0 = vdst|op<<16|0x34<<26; word1 = src0|src1<<9|src2<<18.
|
|
uint32_t w0 = (2u) | ((511u & 0x3FF) << 16) | (0x34u << 26);
|
|
uint32_t w1 = 256u | (257u << 9) | (259u << 18); // src0=v0 src1=v1 src2=v3
|
|
run_words("v_add3_u32_e64 v2, v0, v1, v3", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_cvt_i32_f64_e64 v2, v0:v1 (CDNA4 VOP3 opcode 323) — VOP3 form of the f64
|
|
// cvt. Routed through the existing SIMD_CVT_F64_TO_B32 glue via a _vop3->_vop1
|
|
// fallback (the scalar body drops the abs/neg/omod/clamp modifier reads).
|
|
TEST(VAddSimdBenchmark, Cdna4_VCvtI32F64_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(323, /*vdst=*/2, /*src0=*/256, /*src1=*/0, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_cvt_i32_f64_e64 v2, v0:v1", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_add_f64_e64 v2:v3, v0:v1, v4:v5 (CDNA4 VOP3 opcode 640) — 64-bit-lane f64
|
|
// binary via the new try_execute_binary_vop3_fp64_simd glue.
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddF64_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(640, /*vdst=*/2, /*src0=*/256, /*src1=*/260, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_add_f64_e64 v2:v3, v0:v1, v4:v5", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_ceil_f64_e64 v2:v3, v0:v1 (CDNA4 VOP3 opcode 344) — f64 unary; uses the
|
|
// new try_execute_unary_vop3_fp64_simd glue (stdx::ceil on native<double>).
|
|
TEST(VAddSimdBenchmark, Cdna4_VCeilF64_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(344, /*vdst=*/2, /*src0=*/256, /*src1=*/0, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_ceil_f64_e64 v2:v3, v0:v1", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_ceil_f16_e64 v2, v0 (CDNA4 VOP3 opcode 389) — f16 unary; widen-then-narrow
|
|
// VOP3 glue. Pays the f16<->f32 round-trip on top of the rounding op.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCeilF16_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(389, /*vdst=*/2, /*src0=*/256, /*src1=*/0, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_ceil_f16_e64 v2, v0", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_div_fmas_f32_e64 v6, v0, v1, v2 (CDNA4 VOP3 opcode 482) — fma(s0,s1,s2)
|
|
// followed by a VCC-bit-gated ldexp(result, 32); no omod/clamp. Tests the
|
|
// new try_execute_div_fmas_f32_simd glue and its VCC input-mask path.
|
|
TEST(VAddSimdBenchmark, Cdna4_VDivFmasF32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0 = (6u) | ((482u & 0x3FF) << 16) | (0x34u << 26);
|
|
uint32_t w1 = 256u | (257u << 9) | (258u << 18); // src0=v0 src1=v1 src2=v2
|
|
run_words("v_div_fmas_f32_e64 v6, v0, v1, v2", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_cndmask_b32_e64 v2, v0, v1, vcc (CDNA4 VOP3 opcode 256) — VOP3 form of
|
|
// the VCC-driven select; selector is an SGPR-pair operand (src2=VCC=106), so
|
|
// the path is bit-identical to the VOP2 form. No modifiers applied by scalar.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCndmaskB32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
// VOP3 ternary encoding: word0 = vdst|op<<16|0x34<<26; word1 = src0|src1<<9|
|
|
// src2<<18. src2=VCC=106. Selector pattern is set by seed_inputs (vcc init).
|
|
uint32_t w0 = (2u) | ((256u & 0x3FF) << 16) | (0x34u << 26);
|
|
uint32_t w1 = 256u | (257u << 9) | (106u << 18);
|
|
run_words("v_cndmask_b32_e64 v2, v0, v1, vcc", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_rcp_f16_e64 v2, v0 (CDNA4 VOP3 opcode 381) — f16 transcendental; widen
|
|
// f16->f32, FTZ-flush input, IEEE 1/x, FTZ-flush output, narrow f32->f16. Pays
|
|
// the f16<->f32 round-trip on top of the rcp helper. Same NaN-passthrough
|
|
// (input NaN bits preserved) in both scalar and SIMD paths, so the bit-exact
|
|
// pre-check holds on raw random inputs (no `sanitize_finite` needed).
|
|
TEST(VAddSimdBenchmark, Cdna4_VRcpF16_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(381, /*vdst=*/2, /*src0=*/256, /*src1=*/0, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_rcp_f16_e64 v2, v0", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_fma_f32_e64 v6, v0, v1, v2 (CDNA4 VOP3 opcode 459) — f32 ternary FMA via
|
|
// the new try_execute_ternary_vop3_fp_simd glue. Encoded with src2=v2 (NOT
|
|
// dst-accumulate; the v_fmac form is deferred to a separate slice).
|
|
TEST(VAddSimdBenchmark, Cdna4_VFmaF32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
// VOP3 ternary: word0 vdst|op<<16|0x34<<26; word1 src0|src1<<9|src2<<18.
|
|
uint32_t w0 = (6u) | ((459u & 0x3FF) << 16) | (0x34u << 26);
|
|
uint32_t w1 = 256u | (257u << 9) | (258u << 18); // src0=v0 src1=v1 src2=v2
|
|
run_words("v_fma_f32_e64 v6, v0, v1, v2", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// === SIMD gap-family representatives (this session) ===
|
|
//
|
|
// One TEST per representative op from the newly-SIMD'd families, timed in
|
|
// whichever mode the process runs (RJ_FORCE_SCALAR). All CDNA4-decodable;
|
|
// opcodes verified against cdna4/decoder.cpp's sub_decode_vop3 / sub_decode_vop3p
|
|
// tables (and cdna4/test_encodings.h). NOTE: v_cvt_pk_i16_f32 was DROPPED — it
|
|
// does not exist on CDNA4 (only v_cvt_pk_i16_i32 / v_cvt_pknorm_i16_f32 are
|
|
// present), so it is not decodable by this fixture.
|
|
|
|
// v_cvt_pknorm_u16_f32_e64 v2, v0, v1 (CDNA4 VOP3 opcode 661) — binary f32 ->
|
|
// packed normalized u16 (two clamped/scaled lanes into one 32-bit dst). Inputs
|
|
// sanitized to finite normals for the SIMD fast path.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCvtPknormU16F32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(661, /*vdst=*/2, /*src0=*/256, /*src1=*/257, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_cvt_pknorm_u16_f32_e64 v2, v0, v1", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_cubema_f32_e64 v3, v0, v1, v2 (CDNA4 VOP3 opcode 455) — cubemap major-axis
|
|
// ternary: 2*max(|x|,|y|,|z|). src0/src1/src2 = v0/v1/v2; finite-sanitized.
|
|
TEST(VAddSimdBenchmark, Cdna4_VCubemaF32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_tern_encode(455, /*vdst=*/3, /*src0=*/256, /*src1=*/257, /*src2=*/258, w0, w1);
|
|
run_words("v_cubema_f32_e64 v3, v0, v1, v2", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_dot4c_i32_i8_e64 v2, v0, v1 (CDNA4 VOP3 opcode 313) — dst-accumulate 4xi8
|
|
// dot: vdst += sum(i8(v0)*i8(v1)). The accumulator is vdst (v2), so looping the
|
|
// SAME instruction creates a loop-carried RAW dependency on v2 in BOTH modes —
|
|
// the measured ratio reflects store->load latency, not the dot fast path (which
|
|
// does engage; bit-equivalence is checked by Vop3pDotSimdCorrectness's "c"
|
|
// forms). Reported for family coverage; read the speedup as a lower bound.
|
|
TEST(VAddSimdBenchmark, Cdna4_VDot4cI32I8_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(313, /*vdst=*/2, /*src0=*/256, /*src1=*/257, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_dot4c_i32_i8_e64 v2, v0, v1 (acc)", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_add_f64_e64 v2:v3, v0:v1, v4:v5 (CDNA4 VOP3 opcode 640) — f64 binary on
|
|
// 64-bit VGPR pairs. (This duplicates Cdna4_VAddF64_Vop3 above; included here so
|
|
// the gap-family table has the f64 binary representative in one place.) src0 =
|
|
// v0:v1, src1 = v4:v5, dst = v2:v3.
|
|
TEST(VAddSimdBenchmark, Cdna4_VAddF64Pair_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(640, /*vdst=*/2, /*src0=*/256, /*src1=*/260, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_add_f64_e64 v2:v3, v0:v1, v4:v5", w0, w1, /*sanitize_finite=*/false);
|
|
}
|
|
|
|
// v_pk_add_f32 v2:v3, v0:v1, v4:v5 (CDNA4 VOP3P opcode 50) — packed f32 add on
|
|
// 64-bit VGPR pairs (two f32 lanes per pair). Default packing op_sel = 0,
|
|
// op_sel_hi = 3 (required for the f32 pk fast path; non-default bails to
|
|
// scalar). src0 = v0:v1, src1 = v4:v5, dst = v2:v3; finite-sanitized.
|
|
TEST(VAddSimdBenchmark, Cdna4_VPkAddF32_Vop3p) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3p_encode(/*op=*/50, /*vdst=*/2, /*src0=*/256, /*src1=*/260, /*src2=*/0, /*op_sel=*/0,
|
|
/*op_sel_hi=*/3, /*op_sel_hi_2=*/0, /*neg=*/0, /*neg_hi=*/0, /*clamp=*/0, w0, w1);
|
|
run_words("v_pk_add_f32 v2:v3, v0:v1, v4:v5", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// v_frexp_exp_i32_f32_e64 v2, v0 (CDNA4 VOP3 opcode 371) — f32 -> i32 frexp
|
|
// exponent (unary). Raw finite inputs (the exponent extraction is bit-exact).
|
|
TEST(VAddSimdBenchmark, Cdna4_VFrexpExpI32F32_Vop3) {
|
|
if constexpr (!util::has_stdx_simd) {
|
|
GTEST_SKIP() << "<experimental/simd> unavailable — scalar fallback in use";
|
|
return;
|
|
}
|
|
uint32_t w0, w1;
|
|
vop3_bin_encode(371, /*vdst=*/2, /*src0=*/256, /*src1=*/0, 0, 0, 0, 0, w0, w1);
|
|
run_words("v_frexp_exp_i32_f32_e64 v2, v0", w0, w1, /*sanitize_finite=*/true);
|
|
}
|
|
|
|
// Diagnostic: report whether the SIMD fast path is compiled in.
|
|
TEST(VAddSimdBenchmark, SimdCompileTimeReport) {
|
|
#if __has_include(<experimental/simd>)
|
|
using simd_u32 = std::experimental::native_simd<uint32_t>;
|
|
std::printf("\n util::has_stdx_simd=true, native_simd<uint32_t>::size() = %zu\n",
|
|
simd_u32::size());
|
|
SUCCEED();
|
|
#else
|
|
// Scalar fallback is documented as correct; missing <experimental/simd> is
|
|
// a host capability, not a defect.
|
|
GTEST_SKIP() << "util::has_stdx_simd=false — SIMD path disabled at compile time";
|
|
#endif
|
|
}
|