diff options
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineFunction.h | 4 | ||||
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineFunctionRecords.h | 28 | ||||
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineInstr.h | 13 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineFunction.cpp | 14 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineFunctionCloner.cpp | 6 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineFunctionRecords.cpp | 77 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineInstr.cpp | 26 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineVerifier.cpp | 20 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/CMakeLists.txt | 15 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineDebugInfoTest.cpp | 268 |
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(); +} |
