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
412 lines
17 KiB
C++
412 lines
17 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 "PhiTestFixtures.hpp"
|
|
#include "stinkytofu/hardware/ArchHelper.hpp"
|
|
#include "stinkytofu/transforms/asm/PhiPlacement.hpp"
|
|
|
|
using namespace stinkytofu;
|
|
using namespace stinkytofu::test;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fixture
|
|
// ---------------------------------------------------------------------------
|
|
|
|
class InsertPhiTest : public ::testing::Test {
|
|
protected:
|
|
GfxArchID arch = GfxArchID::Gfx1250;
|
|
};
|
|
|
|
// =============================================================================
|
|
// CFG: Pass-through block with multiple predecessors
|
|
//
|
|
// PHI for v0 should appear in C (the join point) with operands from A, F, B.
|
|
// D and E have a single predecessor (C) so they need no PHIs.
|
|
//
|
|
// Entry
|
|
// / | \.
|
|
// A F B
|
|
// | | |
|
|
// v0=x | v0=y (F has no v0)
|
|
// \ | /
|
|
// C (pass-through, no v0 use/def)
|
|
// / \.
|
|
// D E (both use v0)
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, PassThroughJoinWithMultiplePredecessors) {
|
|
Function func("pass_through_join");
|
|
setFunctionArch(func, arch);
|
|
BasicBlock* entry = func.createBasicBlock("entry");
|
|
BasicBlock* A = func.createBasicBlock("A");
|
|
BasicBlock* F = func.createBasicBlock("F");
|
|
BasicBlock* B = func.createBasicBlock("B");
|
|
BasicBlock* C = func.createBasicBlock("C");
|
|
BasicBlock* D = func.createBasicBlock("D");
|
|
BasicBlock* E = func.createBasicBlock("E");
|
|
|
|
func.addEdge(entry, A);
|
|
func.addEdge(entry, F);
|
|
func.addEdge(entry, B);
|
|
func.addEdge(A, C);
|
|
func.addEdge(F, C);
|
|
func.addEdge(B, C);
|
|
func.addEdge(C, D);
|
|
func.addEdge(C, E);
|
|
|
|
// A and B each define v0
|
|
StinkyInstruction* aAdd = createVAddInBlock(A, arch, 0, 1, 2);
|
|
StinkyInstruction* bAdd = createVAddInBlock(B, arch, 0, 3, 4);
|
|
// F: no v0 definition (pass-through from Entry, which has no v0 either)
|
|
createVAddInBlock(F, arch, 5, 6, 7); // v5 = v6 + v7
|
|
// C: pass-through (no v0 use or def)
|
|
createVAddInBlock(C, arch, 10, 11, 12); // v10 = v11 + v12
|
|
// D and E both use v0
|
|
createVAddInBlock(D, arch, 20, 0, 21);
|
|
createVAddInBlock(E, arch, 30, 0, 31);
|
|
|
|
// --- Run the pass ---
|
|
insertPhiInstructions(func, false);
|
|
|
|
// --- Verify PHI placement ---
|
|
|
|
// C is the only join point where v0 has multiple reaching definitions (A, B).
|
|
// D and E have a single predecessor (C), so no PHIs there.
|
|
EXPECT_EQ(countPhis(*C), 1u) << "C should have exactly one PHI (for v0)";
|
|
EXPECT_EQ(countPhis(*D), 0u) << "D (single predecessor) needs no PHI";
|
|
EXPECT_EQ(countPhis(*E), 0u) << "E (single predecessor) needs no PHI";
|
|
EXPECT_EQ(countPhisInFunction(func), 1u) << "Only one PHI in the entire function";
|
|
|
|
// --- Verify the PHI's operands ---
|
|
|
|
StinkyInstruction* phi = &getStinkyInst(C->begin());
|
|
ASSERT_EQ(phi->getUnifiedOpcode(), GFX::PHI);
|
|
|
|
// PHI defines v0
|
|
ASSERT_EQ(phi->getNumDestRegs(), 1u);
|
|
const StinkyRegister& phiDest = phi->getDestReg(0);
|
|
EXPECT_EQ(phiDest.reg.type, RegType::V);
|
|
EXPECT_EQ(phiDest.reg.idx, 0u);
|
|
|
|
// One src operand per predecessor (A, F, B)
|
|
ASSERT_EQ(phi->getNumSrcRegs(), 3u);
|
|
|
|
const std::vector<BasicBlock*>& cPreds = C->getPredecessors();
|
|
ASSERT_EQ(cPreds.size(), 3u);
|
|
|
|
for (size_t i = 0; i < cPreds.size(); ++i) {
|
|
if (cPreds[i] == A) {
|
|
EXPECT_EQ(phi->getSrcReg(i), aAdd->getDestReg(0))
|
|
<< "PHI src for A should match A's v_add dest (v0)";
|
|
} else if (cPreds[i] == F) {
|
|
EXPECT_FALSE(phi->getSrcReg(i).isRegister())
|
|
<< "PHI src for F should be non-register (v0 never defined on this path)";
|
|
} else if (cPreds[i] == B) {
|
|
EXPECT_EQ(phi->getSrcReg(i), bAdd->getDestReg(0))
|
|
<< "PHI src for B should match B's v_add dest (v0)";
|
|
} else {
|
|
FAIL() << "Unexpected predecessor of C";
|
|
}
|
|
}
|
|
}
|
|
|
|
// =============================================================================
|
|
// 1. Iterated Dominance Frontier
|
|
//
|
|
// PHI at G comes from DF(C). Placing PHI at G makes G a def site, and
|
|
// DF(G) = {H}, so a second PHI is placed at H by the iterated DF worklist.
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, IteratedDominanceFrontier) {
|
|
Function func("iterated_df");
|
|
IteratedDFCfg cfg = buildIteratedDFCfg(func, arch);
|
|
|
|
insertPhiInstructions(func, false);
|
|
|
|
EXPECT_EQ(countPhis(*cfg.G), 1u) << "G needs PHI (DF of C)";
|
|
EXPECT_EQ(countPhis(*cfg.H), 1u) << "H needs PHI (iterated DF of G)";
|
|
EXPECT_EQ(countPhis(*cfg.entry), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.A), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.B), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.C), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.D), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.E), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.F), 0u);
|
|
EXPECT_EQ(countPhisInFunction(func), 2u);
|
|
|
|
// PHI_G: preds [E, F] → src regs: entryDef dest (via B→E), cDef dest (via C→F)
|
|
StinkyInstruction* phiG = &getStinkyInst(cfg.G->begin());
|
|
ASSERT_EQ(phiG->getUnifiedOpcode(), GFX::PHI);
|
|
ASSERT_EQ(phiG->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiG->getSrcReg(0), cfg.entryDef->getDestReg(0));
|
|
EXPECT_EQ(phiG->getSrcReg(1), cfg.cDef->getDestReg(0));
|
|
|
|
// PHI_H: preds [D, G] → src regs: entryDef dest (via B→D), PHI_G dest
|
|
StinkyInstruction* phiH = &getStinkyInst(cfg.H->begin());
|
|
ASSERT_EQ(phiH->getUnifiedOpcode(), GFX::PHI);
|
|
ASSERT_EQ(phiH->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiH->getSrcReg(0), cfg.entryDef->getDestReg(0));
|
|
EXPECT_EQ(phiH->getSrcReg(1), phiG->getDestReg(0));
|
|
}
|
|
|
|
// =============================================================================
|
|
// 2. Nested Loops — PHIs at both loop headers
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, NestedLoops_DualPhiHeaders) {
|
|
Function func("nested_loops");
|
|
NestedLoopCfg cfg = buildNestedLoopCfg(func, arch);
|
|
|
|
insertPhiInstructions(func, false);
|
|
|
|
EXPECT_EQ(countPhis(*cfg.A), 1u) << "outer header A needs PHI";
|
|
EXPECT_EQ(countPhis(*cfg.B), 1u) << "inner header B needs PHI";
|
|
EXPECT_EQ(countPhis(*cfg.C), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.D), 0u);
|
|
EXPECT_EQ(countPhisInFunction(func), 2u);
|
|
|
|
// PHI_A: preds [Entry, D] → entryDef dest, cDef dest (reaches A via C→D→A)
|
|
StinkyInstruction* phiA = &getStinkyInst(cfg.A->begin());
|
|
ASSERT_EQ(phiA->getUnifiedOpcode(), GFX::PHI);
|
|
ASSERT_EQ(phiA->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiA->getSrcReg(0), cfg.entryDef->getDestReg(0));
|
|
EXPECT_EQ(phiA->getSrcReg(1), cfg.cDef->getDestReg(0));
|
|
|
|
// PHI_B: preds [A, C] → PHI_A dest, cDef dest
|
|
StinkyInstruction* phiB = &getStinkyInst(cfg.B->begin());
|
|
ASSERT_EQ(phiB->getUnifiedOpcode(), GFX::PHI);
|
|
ASSERT_EQ(phiB->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiB->getSrcReg(0), phiA->getDestReg(0));
|
|
EXPECT_EQ(phiB->getSrcReg(1), cfg.cDef->getDestReg(0));
|
|
}
|
|
|
|
// =============================================================================
|
|
// 3. Self-loop at a join point — self-referential PHI operand
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, SelfLoopAtJoinPoint) {
|
|
Function func("self_loop_join");
|
|
SelfLoopJoinCfg cfg = buildSelfLoopJoinCfg(func, arch);
|
|
|
|
insertPhiInstructions(func, false);
|
|
|
|
EXPECT_EQ(countPhis(*cfg.C), 1u) << "C (join + self-loop) needs PHI";
|
|
EXPECT_EQ(countPhis(*cfg.D), 0u) << "D has single pred";
|
|
EXPECT_EQ(countPhisInFunction(func), 1u);
|
|
|
|
// PHI_C: preds [A, B, C] → aDef dest, bDef dest, PHI_C dest (self-referential)
|
|
StinkyInstruction* phiC = &getStinkyInst(cfg.C->begin());
|
|
ASSERT_EQ(phiC->getUnifiedOpcode(), GFX::PHI);
|
|
ASSERT_EQ(phiC->getNumSrcRegs(), 3u);
|
|
EXPECT_EQ(phiC->getSrcReg(0), cfg.aDef->getDestReg(0));
|
|
EXPECT_EQ(phiC->getSrcReg(1), cfg.bDef->getDestReg(0));
|
|
EXPECT_EQ(phiC->getSrcReg(2), phiC->getDestReg(0)) << "self-loop edge → self-referential";
|
|
}
|
|
|
|
// =============================================================================
|
|
// 4. Irreducible CFG — mutually recursive PHIs
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, IrreducibleCFG_MutuallyRecursivePhis) {
|
|
Function func("irreducible");
|
|
IrreducibleCfg cfg = buildIrreducibleCfg(func, arch);
|
|
|
|
insertPhiInstructions(func, false);
|
|
|
|
EXPECT_EQ(countPhis(*cfg.C), 1u) << "C needs PHI (preds A, D)";
|
|
EXPECT_EQ(countPhis(*cfg.D), 1u) << "D needs PHI (preds B, C)";
|
|
EXPECT_EQ(countPhis(*cfg.E), 1u) << "E needs PHI (preds C, D)";
|
|
EXPECT_EQ(countPhisInFunction(func), 3u);
|
|
|
|
StinkyInstruction* phiC = &getStinkyInst(cfg.C->begin());
|
|
StinkyInstruction* phiD = &getStinkyInst(cfg.D->begin());
|
|
StinkyInstruction* phiE = &getStinkyInst(cfg.E->begin());
|
|
|
|
// PHI_C: preds [A, D] → aDef dest, PHI_D dest
|
|
ASSERT_EQ(phiC->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiC->getSrcReg(0), cfg.aDef->getDestReg(0));
|
|
EXPECT_EQ(phiC->getSrcReg(1), phiD->getDestReg(0)) << "mutually recursive with D";
|
|
|
|
// PHI_D: preds [B, C] → bDef dest, PHI_C dest
|
|
ASSERT_EQ(phiD->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiD->getSrcReg(0), cfg.bDef->getDestReg(0));
|
|
EXPECT_EQ(phiD->getSrcReg(1), phiC->getDestReg(0)) << "mutually recursive with C";
|
|
|
|
// PHI_E: preds [C, D] → PHI_C dest, PHI_D dest
|
|
ASSERT_EQ(phiE->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiE->getSrcReg(0), phiC->getDestReg(0));
|
|
EXPECT_EQ(phiE->getSrcReg(1), phiD->getDestReg(0));
|
|
}
|
|
|
|
// =============================================================================
|
|
// 5. Multiple registers at the same join point
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, MultipleRegistersAtSameJoin) {
|
|
Function func("multi_reg_join");
|
|
MultiRegJoinCfg cfg = buildMultiRegJoinCfg(func, arch);
|
|
|
|
insertPhiInstructions(func, false);
|
|
|
|
EXPECT_EQ(countPhis(*cfg.C), 2u) << "C needs PHIs for v0 and v1";
|
|
EXPECT_EQ(countPhisInFunction(func), 2u);
|
|
|
|
StinkyInstruction* phiV0 = findPhi(*cfg.C, RegType::V, 0);
|
|
StinkyInstruction* phiV1 = findPhi(*cfg.C, RegType::V, 1);
|
|
ASSERT_NE(phiV0, nullptr) << "PHI for v0 must exist in C";
|
|
ASSERT_NE(phiV1, nullptr) << "PHI for v1 must exist in C";
|
|
|
|
// PHI(v0): preds [A, B] → aDefV0 dest, bDefV0 dest
|
|
ASSERT_EQ(phiV0->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiV0->getSrcReg(0), cfg.aDefV0->getDestReg(0));
|
|
EXPECT_EQ(phiV0->getSrcReg(1), cfg.bDefV0->getDestReg(0));
|
|
|
|
// PHI(v1): preds [A, B] → aDefV1 dest, bDefV1 dest
|
|
ASSERT_EQ(phiV1->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiV1->getSrcReg(0), cfg.aDefV1->getDestReg(0));
|
|
EXPECT_EQ(phiV1->getSrcReg(1), cfg.bDefV1->getDestReg(0));
|
|
}
|
|
|
|
// =============================================================================
|
|
// 6. Chain of diamonds — 3 sequential merge points
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, ChainOfDiamonds) {
|
|
Function func("chain_diamonds");
|
|
ChainOfDiamondsCfg cfg = buildChainOfDiamondsCfg(func, arch);
|
|
|
|
insertPhiInstructions(func, false);
|
|
|
|
EXPECT_EQ(countPhis(*cfg.C), 1u);
|
|
EXPECT_EQ(countPhis(*cfg.F), 1u);
|
|
EXPECT_EQ(countPhis(*cfg.I), 1u);
|
|
EXPECT_EQ(countPhisInFunction(func), 3u);
|
|
|
|
StinkyInstruction* phiC = &getStinkyInst(cfg.C->begin());
|
|
StinkyInstruction* phiF = &getStinkyInst(cfg.F->begin());
|
|
StinkyInstruction* phiI = &getStinkyInst(cfg.I->begin());
|
|
|
|
// PHI_C: preds [A, B] → entryDef dest (inherited via A), bDef dest
|
|
ASSERT_EQ(phiC->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiC->getSrcReg(0), cfg.entryDef->getDestReg(0));
|
|
EXPECT_EQ(phiC->getSrcReg(1), cfg.bDef->getDestReg(0));
|
|
|
|
// PHI_F: preds [D, E] → PHI_C dest (inherited via D), eDef dest
|
|
ASSERT_EQ(phiF->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiF->getSrcReg(0), phiC->getDestReg(0));
|
|
EXPECT_EQ(phiF->getSrcReg(1), cfg.eDef->getDestReg(0));
|
|
|
|
// PHI_I: preds [G, H] → PHI_F dest (inherited via G), hDef dest
|
|
ASSERT_EQ(phiI->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiI->getSrcReg(0), phiF->getDestReg(0));
|
|
EXPECT_EQ(phiI->getSrcReg(1), cfg.hDef->getDestReg(0));
|
|
}
|
|
|
|
// =============================================================================
|
|
// 7. Dead register — semi-pruned SSA should skip v0 (never read)
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, DeadRegister_SemiPrunedSSA) {
|
|
Function func("dead_reg");
|
|
(void)buildDeadRegCfg(func, arch);
|
|
|
|
insertPhiInstructions(func, false);
|
|
|
|
EXPECT_EQ(countPhisInFunction(func), 0u)
|
|
<< "v0 is never used as a source — no PHI should be placed";
|
|
}
|
|
|
|
// =============================================================================
|
|
// 8. Re-definition within the same block — lastDef shadows first
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, RedefinitionInSameBlock) {
|
|
Function func("redef_same_block");
|
|
RedefSameBlockCfg cfg = buildRedefSameBlockCfg(func, arch);
|
|
|
|
insertPhiInstructions(func, false);
|
|
|
|
EXPECT_EQ(countPhis(*cfg.C), 1u);
|
|
EXPECT_EQ(countPhisInFunction(func), 1u);
|
|
|
|
StinkyInstruction* phiC = &getStinkyInst(cfg.C->begin());
|
|
ASSERT_EQ(phiC->getUnifiedOpcode(), GFX::PHI);
|
|
ASSERT_EQ(phiC->getNumSrcRegs(), 2u);
|
|
|
|
// Src from A must match aDef2's dest (the SECOND def), not aDef1
|
|
EXPECT_EQ(phiC->getSrcReg(0), cfg.aDef2->getDestReg(0))
|
|
<< "PHI should use the last def in A, not the first";
|
|
EXPECT_EQ(phiC->getSrcReg(1), cfg.bDef->getDestReg(0));
|
|
}
|
|
|
|
// =============================================================================
|
|
// 9. Wide register with partial sub-register redefine
|
|
//
|
|
// Entry defines v[0:3] (4 DWORDs, like buffer_load). C redefines only v0.
|
|
// PHIs should appear only for v0 (at G and H); v1, v2, v3 are unchanged.
|
|
// =============================================================================
|
|
|
|
TEST_F(InsertPhiTest, WideRegPartialSubregRedefine) {
|
|
Function func("wide_partial_redef");
|
|
WideRegPartialRedefCfg cfg = buildWideRegPartialRedefCfg(func, arch);
|
|
|
|
insertPhiInstructions(func, false);
|
|
|
|
// Only v0 has conflicting defs (Entry wide load vs. C's partial redef).
|
|
EXPECT_EQ(countPhis(*cfg.G), 1u) << "G needs PHI for v0 (DF of C)";
|
|
EXPECT_EQ(countPhis(*cfg.H), 1u) << "H needs PHI for v0 (iterated DF of G)";
|
|
EXPECT_EQ(countPhis(*cfg.entry), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.A), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.B), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.C), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.D), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.E), 0u);
|
|
EXPECT_EQ(countPhis(*cfg.F), 0u);
|
|
EXPECT_EQ(countPhisInFunction(func), 2u);
|
|
|
|
StinkyInstruction* phiG = findPhi(*cfg.G, RegType::V, 0);
|
|
StinkyInstruction* phiH = findPhi(*cfg.H, RegType::V, 0);
|
|
ASSERT_NE(phiG, nullptr);
|
|
ASSERT_NE(phiH, nullptr);
|
|
|
|
// PHI_G: preds [E, F] → entryWideDef dest (via B→E), cPartialDef dest (via C→F)
|
|
ASSERT_EQ(phiG->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiG->getSrcReg(0), cfg.entryWideDef->getDestReg(0));
|
|
EXPECT_EQ(phiG->getSrcReg(1), cfg.cPartialDef->getDestReg(0));
|
|
|
|
// PHI_H: preds [D, G] → entryWideDef dest (via B→D), PHI_G dest
|
|
ASSERT_EQ(phiH->getNumSrcRegs(), 2u);
|
|
EXPECT_EQ(phiH->getSrcReg(0), cfg.entryWideDef->getDestReg(0));
|
|
EXPECT_EQ(phiH->getSrcReg(1), phiG->getDestReg(0));
|
|
|
|
// v1, v2, v3 have a single reaching def (Entry) — no PHIs anywhere
|
|
EXPECT_EQ(findPhi(*cfg.G, RegType::V, 1), nullptr);
|
|
EXPECT_EQ(findPhi(*cfg.G, RegType::V, 2), nullptr);
|
|
EXPECT_EQ(findPhi(*cfg.G, RegType::V, 3), nullptr);
|
|
EXPECT_EQ(findPhi(*cfg.H, RegType::V, 1), nullptr);
|
|
EXPECT_EQ(findPhi(*cfg.H, RegType::V, 2), nullptr);
|
|
EXPECT_EQ(findPhi(*cfg.H, RegType::V, 3), nullptr);
|
|
}
|