aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/include/rcc/CodeGen/MachineFunction.h4
-rw-r--r--backend/include/rcc/CodeGen/MachineFunctionRecords.h28
-rw-r--r--backend/include/rcc/CodeGen/MachineInstr.h13
-rw-r--r--backend/lib/CodeGen/MachineFunction.cpp14
-rw-r--r--backend/lib/CodeGen/MachineFunctionCloner.cpp6
-rw-r--r--backend/lib/CodeGen/MachineFunctionRecords.cpp77
-rw-r--r--backend/lib/CodeGen/MachineInstr.cpp26
-rw-r--r--backend/lib/CodeGen/MachineVerifier.cpp20
-rw-r--r--backend/unittests/CodeGen/CMakeLists.txt15
-rw-r--r--backend/unittests/CodeGen/MachineDebugInfoTest.cpp268
10 files changed, 471 insertions, 0 deletions
diff --git a/backend/include/rcc/CodeGen/MachineFunction.h b/backend/include/rcc/CodeGen/MachineFunction.h
index c067ee1..8addd4e 100644
--- a/backend/include/rcc/CodeGen/MachineFunction.h
+++ b/backend/include/rcc/CodeGen/MachineFunction.h
@@ -16,6 +16,7 @@
#include "rcc/Support/Error.h"
#include <cstddef>
+#include <cstdint>
#include <memory>
#include <span>
#include <string>
@@ -176,6 +177,8 @@ public:
private:
MachineFunction(std::string Name, MachineFunctionStableID StableID);
+ [[nodiscard]] Expected<std::uint32_t> allocateDebugInstrNumber();
+ void observeDebugInstrNumber(std::uint32_t Number) noexcept;
std::string Name;
MachineFunctionStableID StableID;
@@ -195,6 +198,7 @@ private:
std::vector<const MachineMemOperand *> AllMemOperands;
StableIDGenerator<MachineBlockStableID> BlockIDGenerator;
StableIDGenerator<MachineInstrStableID> InstrIDGenerator;
+ std::uint32_t DebugInstrNumberingCount = 0;
std::unique_ptr<MachineArena> Arena;
friend class MachineBasicBlock;
diff --git a/backend/include/rcc/CodeGen/MachineFunctionRecords.h b/backend/include/rcc/CodeGen/MachineFunctionRecords.h
index 7050072..7929ed3 100644
--- a/backend/include/rcc/CodeGen/MachineFunctionRecords.h
+++ b/backend/include/rcc/CodeGen/MachineFunctionRecords.h
@@ -18,6 +18,25 @@ namespace rcc {
class MachineFunctionCloneContext;
+inline constexpr std::uint32_t MachineDebugMemoryOperandNumber = 1'000'000;
+
+struct MachineDebugValueReference final {
+ std::uint32_t InstructionNumber = 0;
+ std::uint32_t OperandNumber = 0;
+
+ friend constexpr bool operator==(MachineDebugValueReference Left,
+ MachineDebugValueReference Right) noexcept = default;
+};
+
+struct MachineDebugValueSubstitution final {
+ MachineDebugValueReference Source;
+ MachineDebugValueReference Destination;
+ SubRegIndex Subregister;
+
+ friend constexpr bool operator==(MachineDebugValueSubstitution Left,
+ MachineDebugValueSubstitution Right) noexcept = default;
+};
+
struct MachineCallSiteArgument final {
PhysicalRegister Reg;
std::uint16_t ArgumentNumber = 0;
@@ -95,6 +114,14 @@ public:
[[nodiscard]] Expected<void> addPrefetchTarget(MachinePrefetchTarget Target);
+ [[nodiscard]] std::span<const MachineDebugValueSubstitution>
+ debugValueSubstitutions() const noexcept {
+ return DebugValueSubstitutions;
+ }
+
+ [[nodiscard]] Expected<void>
+ addDebugValueSubstitution(MachineDebugValueSubstitution Substitution);
+
[[nodiscard]] Expected<void> verify() const;
[[nodiscard]] Expected<MachineFunctionRecords>
clone(const MachineFunctionCloneContext &CloneContext) const;
@@ -107,6 +134,7 @@ private:
std::vector<MachineCallSiteInfo> CallSites;
std::vector<MachineCalledGlobal> CalledGlobals;
std::vector<MachinePrefetchTarget> PrefetchTargets;
+ std::vector<MachineDebugValueSubstitution> DebugValueSubstitutions;
};
} // namespace rcc
diff --git a/backend/include/rcc/CodeGen/MachineInstr.h b/backend/include/rcc/CodeGen/MachineInstr.h
index bcae306..78d48cb 100644
--- a/backend/include/rcc/CodeGen/MachineInstr.h
+++ b/backend/include/rcc/CodeGen/MachineInstr.h
@@ -9,6 +9,7 @@
#include "rcc/Support/Error.h"
#include <cstddef>
+#include <cstdint>
#include <functional>
#include <optional>
#include <span>
@@ -132,6 +133,17 @@ public:
Origin.reset();
}
+ [[nodiscard]] std::uint32_t debugInstrNumber() const noexcept {
+ return DebugInstrNumber;
+ }
+
+ [[nodiscard]] Expected<void> setDebugInstrNumber(std::uint64_t Number);
+ [[nodiscard]] Expected<std::uint32_t> assignDebugInstrNumber();
+
+ void clearDebugInstrNumber() noexcept {
+ DebugInstrNumber = 0;
+ }
+
private:
MachineInstr(MachineFunction &OwnerFunction, Opcode InstructionOpcode,
MachineInstrStableID StableID);
@@ -153,6 +165,7 @@ private:
MachineInstrFlags Flags;
std::optional<SourceLocation> Location;
std::optional<OriginID> Origin;
+ std::uint32_t DebugInstrNumber = 0;
friend class MachineArena;
friend class MachineBasicBlock;
diff --git a/backend/lib/CodeGen/MachineFunction.cpp b/backend/lib/CodeGen/MachineFunction.cpp
index 294158b..df1ceb5 100644
--- a/backend/lib/CodeGen/MachineFunction.cpp
+++ b/backend/lib/CodeGen/MachineFunction.cpp
@@ -10,6 +10,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdint>
+#include <limits>
#include <memory>
#include <memory_resource>
#include <new>
@@ -86,6 +87,19 @@ Expected<void> MachineFunction::setTargetState(TargetFunctionState State) {
return {};
}
+Expected<std::uint32_t> MachineFunction::allocateDebugInstrNumber() {
+ if (DebugInstrNumberingCount == std::numeric_limits<std::uint32_t>::max()) {
+ return makeUnexpected(ErrorCode::InternalError,
+ "machine debug instruction number space is exhausted");
+ }
+ ++DebugInstrNumberingCount;
+ return DebugInstrNumberingCount;
+}
+
+void MachineFunction::observeDebugInstrNumber(std::uint32_t Number) noexcept {
+ DebugInstrNumberingCount = std::max(DebugInstrNumberingCount, Number);
+}
+
Expected<MachineBasicBlock *> MachineFunction::createBasicBlock() {
auto ID = BlockIDGenerator.allocate();
if (!ID)
diff --git a/backend/lib/CodeGen/MachineFunctionCloner.cpp b/backend/lib/CodeGen/MachineFunctionCloner.cpp
index aae74ff..2f99341 100644
--- a/backend/lib/CodeGen/MachineFunctionCloner.cpp
+++ b/backend/lib/CodeGen/MachineFunctionCloner.cpp
@@ -388,6 +388,12 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) {
}
if (SourceInstruction->origin())
NewInstruction->setOrigin(*SourceInstruction->origin());
+ if (SourceInstruction->debugInstrNumber() != 0) {
+ auto DebugNumberResult =
+ NewInstruction->setDebugInstrNumber(SourceInstruction->debugInstrNumber());
+ if (!DebugNumberResult)
+ return std::unexpected(DebugNumberResult.error());
+ }
auto AppendResult = DestinationBlock->appendInstruction(*NewInstruction);
if (!AppendResult)
diff --git a/backend/lib/CodeGen/MachineFunctionRecords.cpp b/backend/lib/CodeGen/MachineFunctionRecords.cpp
index be0d936..9fda0d7 100644
--- a/backend/lib/CodeGen/MachineFunctionRecords.cpp
+++ b/backend/lib/CodeGen/MachineFunctionRecords.cpp
@@ -7,6 +7,7 @@
#include <algorithm>
#include <cstddef>
#include <string>
+#include <unordered_map>
#include <unordered_set>
#include <utility>
@@ -55,6 +56,30 @@ verifyCalleeSavedRegisters(std::span<const PhysicalRegister> Registers) {
return {};
}
+[[nodiscard]] constexpr std::uint64_t
+debugReferenceKey(MachineDebugValueReference Reference) noexcept {
+ return (static_cast<std::uint64_t>(Reference.InstructionNumber) << 32U) |
+ static_cast<std::uint64_t>(Reference.OperandNumber);
+}
+
+[[nodiscard]] Expected<void>
+verifyDebugValueSubstitution(const MachineDebugValueSubstitution &Substitution) {
+ if (Substitution.Source.InstructionNumber == 0 ||
+ Substitution.Destination.InstructionNumber == 0) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "debug value substitution has a zero instruction number");
+ }
+ if (Substitution.Source.InstructionNumber == Substitution.Destination.InstructionNumber) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "debug value substitution cannot replace a value within one instruction");
+ }
+ if (Substitution.Source.OperandNumber == MachineDebugMemoryOperandNumber) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "debug value substitution cannot originate from the memory operand");
+ }
+ return {};
+}
+
} // namespace
Expected<void>
@@ -102,6 +127,22 @@ Expected<void> MachineFunctionRecords::addPrefetchTarget(MachinePrefetchTarget T
return {};
}
+Expected<void>
+MachineFunctionRecords::addDebugValueSubstitution(MachineDebugValueSubstitution Substitution) {
+ auto Verification = verifyDebugValueSubstitution(Substitution);
+ if (!Verification)
+ return std::unexpected(Verification.error());
+ if (std::ranges::any_of(DebugValueSubstitutions,
+ [&Substitution](const MachineDebugValueSubstitution &Existing) {
+ return Existing.Source == Substitution.Source;
+ })) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "debug value source already has a substitution");
+ }
+ DebugValueSubstitutions.push_back(Substitution);
+ return {};
+}
+
Expected<void> MachineFunctionRecords::verify() const {
if (CalleeSavedRegisters) {
auto CalleeSavedVerification = verifyCalleeSavedRegisters(*CalleeSavedRegisters);
@@ -138,6 +179,37 @@ Expected<void> MachineFunctionRecords::verify() const {
"prefetch target list contains a duplicate entry");
}
}
+
+ std::unordered_map<std::uint64_t, std::uint64_t> DebugSubstitutionEdges;
+ for (const MachineDebugValueSubstitution &Substitution : DebugValueSubstitutions) {
+ auto SubstitutionVerification = verifyDebugValueSubstitution(Substitution);
+ if (!SubstitutionVerification)
+ return SubstitutionVerification;
+ const std::uint64_t Source = debugReferenceKey(Substitution.Source);
+ const std::uint64_t Destination = debugReferenceKey(Substitution.Destination);
+ if (!DebugSubstitutionEdges.emplace(Source, Destination).second) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "multiple debug substitutions have the same source");
+ }
+ }
+
+ std::unordered_set<std::uint64_t> CompletedDebugReferences;
+ for (const auto &Edge : DebugSubstitutionEdges) {
+ const std::uint64_t Start = Edge.first;
+ std::unordered_set<std::uint64_t> Path;
+ std::uint64_t Current = Start;
+ while (!CompletedDebugReferences.contains(Current)) {
+ const auto Position = DebugSubstitutionEdges.find(Current);
+ if (Position == DebugSubstitutionEdges.end())
+ break;
+ if (!Path.insert(Current).second) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "debug value substitutions contain a cycle");
+ }
+ Current = Position->second;
+ }
+ CompletedDebugReferences.insert(Path.begin(), Path.end());
+ }
return {};
}
@@ -181,6 +253,11 @@ MachineFunctionRecords::clone(const MachineFunctionCloneContext &CloneContext) c
if (!AddResult)
return std::unexpected(AddResult.error());
}
+ for (const MachineDebugValueSubstitution Source : DebugValueSubstitutions) {
+ auto AddResult = Result.addDebugValueSubstitution(Source);
+ if (!AddResult)
+ return std::unexpected(AddResult.error());
+ }
return Result;
}
diff --git a/backend/lib/CodeGen/MachineInstr.cpp b/backend/lib/CodeGen/MachineInstr.cpp
index b201533..aaeeacd 100644
--- a/backend/lib/CodeGen/MachineInstr.cpp
+++ b/backend/lib/CodeGen/MachineInstr.cpp
@@ -8,7 +8,9 @@
#include "rcc/CodeGen/MachineRegisterInfo.h"
#include <cstddef>
+#include <cstdint>
#include <functional>
+#include <limits>
#include <optional>
#include <utility>
@@ -30,6 +32,30 @@ Expected<void> MachineInstr::setOpcode(Opcode NewOpcode) {
return {};
}
+Expected<void> MachineInstr::setDebugInstrNumber(std::uint64_t Number) {
+ if (Number == 0 || Number > std::numeric_limits<std::uint32_t>::max()) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine debug instruction number must fit in nonzero uint32");
+ }
+ if (DebugInstrNumber != 0 && DebugInstrNumber != Number) {
+ return makeUnexpected(ErrorCode::InvalidArgument,
+ "machine instruction already has a different debug number");
+ }
+ DebugInstrNumber = static_cast<std::uint32_t>(Number);
+ OwnerFunction->observeDebugInstrNumber(DebugInstrNumber);
+ return {};
+}
+
+Expected<std::uint32_t> MachineInstr::assignDebugInstrNumber() {
+ if (DebugInstrNumber != 0)
+ return DebugInstrNumber;
+ auto Number = OwnerFunction->allocateDebugInstrNumber();
+ if (!Number)
+ return std::unexpected(Number.error());
+ DebugInstrNumber = *Number;
+ return DebugInstrNumber;
+}
+
MachineOperand *MachineInstr::operandAt(std::size_t Index) noexcept {
if (Index >= Operands.size())
return nullptr;
diff --git a/backend/lib/CodeGen/MachineVerifier.cpp b/backend/lib/CodeGen/MachineVerifier.cpp
index d1c6989..2fbbd38 100644
--- a/backend/lib/CodeGen/MachineVerifier.cpp
+++ b/backend/lib/CodeGen/MachineVerifier.cpp
@@ -396,6 +396,17 @@ private:
if (!LinkedBlockProfileIDs.contains(Key))
return fail("prefetch target references an unknown bb_id");
}
+
+ if (!Records.debugValueSubstitutions().empty() &&
+ !Function.attributes().has(MachineFunctionAttribute::DebugInstrRef)) {
+ return fail("debug value substitutions require DebugInstrRef");
+ }
+ for (const MachineDebugValueSubstitution &Substitution : Records.debugValueSubstitutions()) {
+ if (Substitution.Subregister.value() != 0 &&
+ Target.registers().subRegIndex(Substitution.Subregister) == nullptr) {
+ return fail("debug value substitution references an unknown subregister index");
+ }
+ }
return {};
}
@@ -421,6 +432,14 @@ private:
const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction.opcode());
if (Descriptor == nullptr)
return fail("live instruction uses an unregistered opcode");
+ if (Instruction.debugInstrNumber() != 0) {
+ if (Descriptor->Flags.has(InstructionFlag::Meta)) {
+ return fail("metadata instruction has a debug value tracking number");
+ }
+ if (!DebugInstructionNumbers.insert(Instruction.debugInstrNumber()).second) {
+ return fail("multiple instructions have the same debug value tracking number");
+ }
+ }
auto DescriptorResult = Target.validateInstruction(Instruction, Function.registerInfo());
if (!DescriptorResult)
return failWith("instruction descriptor", DescriptorResult.error());
@@ -664,6 +683,7 @@ private:
std::unordered_set<MachineInstrStableID> LinkedInstructionIDs;
std::unordered_map<MachineInstrStableID, const MachineInstr *> LinkedInstructionsByID;
std::unordered_set<std::uint64_t> LinkedBlockProfileIDs;
+ std::unordered_set<std::uint32_t> DebugInstructionNumbers;
std::unordered_set<const MachineMemOperand *> RegisteredMemOperands;
std::unordered_map<Register, std::size_t> VirtualDefCounts;
bool HasPhi = false;
diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt
index 92bed00..ed534bd 100644
--- a/backend/unittests/CodeGen/CMakeLists.txt
+++ b/backend/unittests/CodeGen/CMakeLists.txt
@@ -7,6 +7,7 @@ set(
InstructionDescriptorTest.cpp
MachineCompilationStatusTest.cpp
MachineConstantPoolTest.cpp
+ MachineDebugInfoTest.cpp
MachineFrameInfoTest.cpp
MachineFunctionClonerTest.cpp
MachineFunctionRecordsTest.cpp
@@ -76,6 +77,18 @@ add_test(
COMMAND RCCBackendCodeGenMachineConstantPoolTests
)
+add_executable(RCCBackendCodeGenMachineDebugInfoTests MachineDebugInfoTest.cpp)
+rcc_backend_configure_target(RCCBackendCodeGenMachineDebugInfoTests)
+target_link_libraries(
+ RCCBackendCodeGenMachineDebugInfoTests
+ PRIVATE RCCBackend::CodeGen RCCBackend::TestTarget
+)
+
+add_test(
+ NAME RCCBackend.CodeGen.MachineDebugInfo
+ COMMAND RCCBackendCodeGenMachineDebugInfoTests
+)
+
add_executable(RCCBackendCodeGenMachineFrameInfoTests MachineFrameInfoTest.cpp)
rcc_backend_configure_target(RCCBackendCodeGenMachineFrameInfoTests)
target_link_libraries(RCCBackendCodeGenMachineFrameInfoTests PRIVATE RCCBackend::CodeGen)
@@ -232,6 +245,7 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatusTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineDebugInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionRecordsTest.cpp"
@@ -252,6 +266,7 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineCompilationStatusTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/MachineDebugInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionRecordsTest.cpp"
diff --git a/backend/unittests/CodeGen/MachineDebugInfoTest.cpp b/backend/unittests/CodeGen/MachineDebugInfoTest.cpp
new file mode 100644
index 0000000..6c5eea7
--- /dev/null
+++ b/backend/unittests/CodeGen/MachineDebugInfoTest.cpp
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "Target/TestTarget.h"
+#include "rcc/CodeGen/InstructionDescriptor.h"
+#include "rcc/CodeGen/MachineBasicBlock.h"
+#include "rcc/CodeGen/MachineFunction.h"
+#include "rcc/CodeGen/MachineFunctionAttributes.h"
+#include "rcc/CodeGen/MachineFunctionCloner.h"
+#include "rcc/CodeGen/MachineFunctionRecords.h"
+#include "rcc/CodeGen/MachineInstr.h"
+#include "rcc/CodeGen/MachineVerifier.h"
+#include "rcc/CodeGen/TargetDescription.h"
+
+#include <cstdint>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+namespace {
+
+constexpr std::uint32_t ValueOpcode = 1;
+constexpr std::uint32_t MetaOpcode = 2;
+
+class TestContext final {
+public:
+ void expect(bool Condition, std::string_view Message) {
+ if (Condition)
+ return;
+ std::cerr << Message << '\n';
+ ++FailureCount;
+ }
+
+ [[nodiscard]] int result() const noexcept {
+ return FailureCount == 0 ? 0 : 1;
+ }
+
+private:
+ int FailureCount = 0;
+};
+
+[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() {
+ rcc::InstructionDescriptor Value;
+ Value.InstructionOpcode = rcc::Opcode::fromValue(ValueOpcode);
+ Value.Name = "TEST_VALUE";
+ Value.Flags = {rcc::InstructionFlag::ProducesCode};
+
+ rcc::InstructionDescriptor Meta;
+ Meta.InstructionOpcode = rcc::Opcode::fromValue(MetaOpcode);
+ Meta.Name = "TEST_META";
+ Meta.Flags = {rcc::InstructionFlag::Meta};
+
+ std::vector<rcc::InstructionDescriptor> Descriptors;
+ Descriptors.push_back(std::move(Value));
+ Descriptors.push_back(std::move(Meta));
+ auto Instructions = rcc::InstructionSet::create(std::move(Descriptors));
+ if (!Instructions)
+ return std::unexpected(Instructions.error());
+ return rcc::test::createTestTargetDescription(std::move(*Instructions));
+}
+
+[[nodiscard]] std::unique_ptr<rcc::MachineFunction>
+createFunction(TestContext &Context, std::uint64_t StableID, std::string Name) {
+ auto Function = rcc::MachineFunction::create(std::move(Name),
+ rcc::MachineFunctionStableID::fromValue(StableID));
+ Context.expect(Function.has_value(), "machine function creation must succeed");
+ if (!Function)
+ return nullptr;
+ return std::move(*Function);
+}
+
+[[nodiscard]] rcc::MachineBasicBlock *appendBlock(TestContext &Context,
+ rcc::MachineFunction &Function) {
+ auto Block = Function.createBasicBlock();
+ Context.expect(Block.has_value() && Function.appendBlock(**Block).has_value(),
+ "machine basic block creation and linking must succeed");
+ return Block ? *Block : nullptr;
+}
+
+[[nodiscard]] rcc::MachineInstr *appendInstruction(TestContext &Context,
+ rcc::MachineFunction &Function,
+ rcc::MachineBasicBlock &Block,
+ std::uint32_t OpcodeValue) {
+ auto Instruction = Function.createInstruction(rcc::Opcode::fromValue(OpcodeValue));
+ Context.expect(Instruction.has_value() && Block.appendInstruction(**Instruction).has_value(),
+ "machine instruction creation and linking must succeed");
+ return Instruction ? *Instruction : nullptr;
+}
+
+void testInstructionNumbering(TestContext &Context) {
+ auto Target = createTarget();
+ auto Function = createFunction(Context, 1, "debug-numbering");
+ if (!Target || Function == nullptr)
+ return;
+ rcc::MachineBasicBlock *Block = appendBlock(Context, *Function);
+ if (Block == nullptr)
+ return;
+ rcc::MachineInstr *First = appendInstruction(Context, *Function, *Block, ValueOpcode);
+ rcc::MachineInstr *Second = appendInstruction(Context, *Function, *Block, ValueOpcode);
+ rcc::MachineInstr *Third = appendInstruction(Context, *Function, *Block, ValueOpcode);
+ if (First == nullptr || Second == nullptr || Third == nullptr)
+ return;
+
+ Context.expect(!First->setDebugInstrNumber(0) &&
+ !First->setDebugInstrNumber(
+ static_cast<std::uint64_t>(std::numeric_limits<std::uint32_t>::max()) + 1),
+ "debug instruction numbers must fit in nonzero uint32");
+ Context.expect(First->setDebugInstrNumber(7).has_value(),
+ "an explicit debug instruction number must be accepted");
+ auto SecondNumber = Second->assignDebugInstrNumber();
+ auto ExistingSecondNumber = Second->assignDebugInstrNumber();
+ Context.expect(SecondNumber.has_value() && *SecondNumber == 8 &&
+ ExistingSecondNumber.has_value() && *ExistingSecondNumber == *SecondNumber,
+ "generated debug numbers must continue after the largest observed number");
+ Second->clearDebugInstrNumber();
+ auto ReassignedSecondNumber = Second->assignDebugInstrNumber();
+ Context.expect(ReassignedSecondNumber.has_value() && *ReassignedSecondNumber == 9,
+ "cleared debug numbers must not rewind the monotonic allocator");
+ Context.expect(Third->setDebugInstrNumber(7).has_value() &&
+ !rcc::MachineVerifier::verify(*Function, *Target),
+ "the unified verifier must reject duplicate live debug numbers");
+ Context.expect(!Third->setDebugInstrNumber(10),
+ "an existing debug identity must be cleared before replacement");
+ Third->clearDebugInstrNumber();
+ Context.expect(Third->setDebugInstrNumber(10).has_value() &&
+ rcc::MachineVerifier::verify(*Function, *Target).has_value(),
+ "unique debug numbers must pass unified verification");
+}
+
+void testSubstitutionsAndClone(TestContext &Context) {
+ auto Target = createTarget();
+ auto Function = createFunction(Context, 2, "debug-substitutions");
+ if (!Target || Function == nullptr)
+ return;
+ rcc::MachineBasicBlock *Block = appendBlock(Context, *Function);
+ if (Block == nullptr)
+ return;
+ rcc::MachineInstr *Instruction = appendInstruction(Context, *Function, *Block, ValueOpcode);
+ if (Instruction == nullptr)
+ return;
+ Context.expect(Instruction->setDebugInstrNumber(7).has_value(),
+ "tracked instruction numbering must succeed");
+
+ const rcc::MachineDebugValueSubstitution Substitution{
+ .Source = {.InstructionNumber = 1, .OperandNumber = 0},
+ .Destination = {.InstructionNumber = 7, .OperandNumber = 1},
+ .Subregister = rcc::test::TestSubReg::Low32,
+ };
+ Context.expect(Function->records().addDebugValueSubstitution(Substitution).has_value(),
+ "a structured debug value substitution must be stored");
+ Context.expect(!Function->records().addDebugValueSubstitution(Substitution),
+ "a debug value source must have at most one replacement");
+ Context.expect(!Function->records().addDebugValueSubstitution({
+ .Source =
+ {
+ .InstructionNumber = 2,
+ .OperandNumber = rcc::MachineDebugMemoryOperandNumber,
+ },
+ .Destination = {.InstructionNumber = 7, .OperandNumber = 0},
+ .Subregister = {},
+ }),
+ "a substitution cannot originate from the reserved memory operand");
+ Context.expect(!Function->records().addDebugValueSubstitution({
+ .Source = {.InstructionNumber = 3, .OperandNumber = 0},
+ .Destination = {.InstructionNumber = 3, .OperandNumber = 1},
+ .Subregister = {},
+ }),
+ "a substitution cannot replace a value within one instruction");
+
+ Context.expect(!rcc::MachineVerifier::verify(*Function, *Target),
+ "substitutions must require the DebugInstrRef function policy");
+ Function->attributes().set(rcc::MachineFunctionAttribute::DebugInstrRef);
+ Context.expect(rcc::MachineVerifier::verify(*Function, *Target).has_value(),
+ "well-formed substitutions must pass unified verification");
+
+ auto Clone =
+ rcc::cloneMachineFunction(*Function, *Target,
+ {
+ .NewStableID = rcc::MachineFunctionStableID::fromValue(3),
+ .NewName = std::string("debug-substitutions.clone"),
+ });
+ Context.expect(Clone.has_value(), "tracked debug state must support explicit deep cloning");
+ if (!Clone)
+ return;
+ const rcc::MachineBasicBlock *ClonedBlock = Clone->ClonedFunction->firstBlock();
+ const rcc::MachineInstr *ClonedInstruction =
+ ClonedBlock == nullptr ? nullptr : ClonedBlock->firstInstruction();
+ Context.expect(ClonedInstruction != nullptr && ClonedInstruction->debugInstrNumber() == 7 &&
+ Clone->ClonedFunction->records().debugValueSubstitutions().size() == 1 &&
+ Clone->ClonedFunction->records().debugValueSubstitutions()[0] == Substitution,
+ "deep clone must preserve debug numbers and substitution identities");
+}
+
+void testVerifierFailures(TestContext &Context) {
+ auto Target = createTarget();
+ if (!Target)
+ return;
+
+ {
+ auto Function = createFunction(Context, 10, "debug-meta");
+ if (Function == nullptr)
+ return;
+ rcc::MachineBasicBlock *Block = appendBlock(Context, *Function);
+ if (Block == nullptr)
+ return;
+ rcc::MachineInstr *Meta = appendInstruction(Context, *Function, *Block, MetaOpcode);
+ if (Meta == nullptr)
+ return;
+ Context.expect(Meta->setDebugInstrNumber(1).has_value() &&
+ !rcc::MachineVerifier::verify(*Function, *Target),
+ "metadata instructions must not carry debug value tracking numbers");
+ }
+
+ {
+ auto Function = createFunction(Context, 11, "debug-subreg");
+ if (Function == nullptr)
+ return;
+ rcc::MachineBasicBlock *Block = appendBlock(Context, *Function);
+ if (Block == nullptr)
+ return;
+ rcc::MachineInstr *Instruction = appendInstruction(Context, *Function, *Block, ValueOpcode);
+ if (Instruction == nullptr)
+ return;
+ Function->attributes().set(rcc::MachineFunctionAttribute::DebugInstrRef);
+ Context.expect(Function->records()
+ .addDebugValueSubstitution({
+ .Source = {.InstructionNumber = 1, .OperandNumber = 0},
+ .Destination = {.InstructionNumber = 2, .OperandNumber = 0},
+ .Subregister = rcc::SubRegIndex::fromValue(999),
+ })
+ .has_value() &&
+ !rcc::MachineVerifier::verify(*Function, *Target),
+ "debug substitutions must reject target-unknown subregister indices");
+ }
+
+ {
+ rcc::MachineFunctionRecords Records;
+ Context.expect(Records
+ .addDebugValueSubstitution({
+ .Source = {.InstructionNumber = 1, .OperandNumber = 0},
+ .Destination = {.InstructionNumber = 2, .OperandNumber = 0},
+ .Subregister = {},
+ })
+ .has_value() &&
+ Records
+ .addDebugValueSubstitution({
+ .Source = {.InstructionNumber = 2, .OperandNumber = 0},
+ .Destination = {.InstructionNumber = 1, .OperandNumber = 0},
+ .Subregister = {},
+ })
+ .has_value() &&
+ !Records.verify(),
+ "debug substitution chains must be acyclic");
+ }
+}
+
+} // namespace
+
+int main() {
+ TestContext Context;
+ testInstructionNumbering(Context);
+ testSubstitutionsAndClone(Context);
+ testVerifierFailures(Context);
+ return Context.result();
+}