diff options
Diffstat (limited to 'backend/unittests/CodeGen/MachineDebugInfoTest.cpp')
| -rw-r--r-- | backend/unittests/CodeGen/MachineDebugInfoTest.cpp | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/backend/unittests/CodeGen/MachineDebugInfoTest.cpp b/backend/unittests/CodeGen/MachineDebugInfoTest.cpp new file mode 100644 index 0000000..ae4d539 --- /dev/null +++ b/backend/unittests/CodeGen/MachineDebugInfoTest.cpp @@ -0,0 +1,518 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineDebugInfo.h" + +#include "Target/TestTarget.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFrameInfo.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 <optional> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace { + +constexpr std::uint32_t ValueOpcode = 1024; +constexpr std::uint32_t MetaOpcode = 1025; + +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::OperandDescriptor OptionalMetadata; + OptionalMetadata.Kinds = {rcc::MachineOperand::Kind::Metadata}; + OptionalMetadata.Optional = true; + Value.Operands.push_back(OptionalMetadata); + + 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)); + std::vector<rcc::ImmediatePredicate> Predicates; + auto Generic = rcc::appendGenericInstructionInfo(Descriptors, Predicates); + if (!Generic) + return std::unexpected(Generic.error()); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors), std::move(Predicates)); + 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 testMetadataValuesAndLocations(TestContext &Context) { + rcc::MachineMetadataInfo Metadata; + Context.expect(Metadata.addNode({.Slot = 0, .IsDistinct = false, .Elements = {}}).has_value(), + "function-local debug metadata setup must succeed"); + + const rcc::MachineDIExpression Expression( + std::vector<std::uint64_t>{0x1005, 0, 0x22, 0x1000, 0, 64}); + Context.expect(Expression.elements().size() == 6 && Expression.elements()[0] == 0x1005, + "inline DIExpression elements must preserve their exact unsigned encoding"); + auto ExpressionValue = rcc::MachineMetadataValue::expression(Expression); + Context.expect(ExpressionValue.verify(Metadata).has_value() && + ExpressionValue.expression() != nullptr && + *ExpressionValue.expression() == Expression, + "inline DIExpression metadata values must remain structured"); + Context.expect(!rcc::MachineMetadataValue{}.verify(Metadata), + "an unrepresented metadata value must be rejected"); + + Context.expect(!rcc::MachineDILocation::create({}), + "an inline DILocation must contain a root frame"); + Context.expect(!rcc::MachineDILocation::create({{}}), + "an inline DILocation frame must contain a valid scope"); + Context.expect(!rcc::MachineDILocation::create({ + { + .Line = 1, + .Column = 0, + .Scope = rcc::MachineMetadataReference::embeddedIR(1), + .IsImplicitCode = false, + .AtomGroup = 0, + .AtomRank = 8, + }, + }), + "an inline DILocation atom rank must fit the fixed three-bit field"); + Context.expect(!rcc::MachineDILocation::create({ + { + .Line = 1, + .Column = 0, + .Scope = rcc::MachineMetadataReference::embeddedIR(1), + .IsImplicitCode = false, + .AtomGroup = rcc::MachineDILocation::MaxAtomGroup + 1, + .AtomRank = 0, + }, + }), + "an inline DILocation atom group must fit the fixed 61-bit field"); + + auto InlineLocation = rcc::MachineDILocation::create( + { + { + .Line = 7, + .Column = 65'535, + .Scope = rcc::MachineMetadataReference::embeddedIR(3), + .IsImplicitCode = true, + .AtomGroup = rcc::MachineDILocation::MaxAtomGroup, + .AtomRank = 7, + }, + { + .Line = 9, + .Column = 4, + .Scope = rcc::MachineMetadataReference::embeddedIR(0), + .IsImplicitCode = false, + .AtomGroup = 0, + .AtomRank = 0, + }, + }, + rcc::MachineMetadataReference::embeddedIR(4)); + Context.expect(InlineLocation.has_value() && InlineLocation->verify(Metadata).has_value() && + InlineLocation->inlineChain().size() == 2 && + InlineLocation->terminalInlinedAt() == + rcc::MachineMetadataReference::embeddedIR(4), + "inline DILocation chains must preserve nested and terminal references"); + if (!InlineLocation) + return; + + auto DebugLocation = rcc::MachineDebugLocation::inlineLocation(*InlineLocation); + Context.expect(DebugLocation.verify(Metadata).has_value() && + DebugLocation.inlineLocation() != nullptr, + "a structured inline DILocation must form a debug-location attachment"); + rcc::MachineVariableDebugInfo VariableInfo{ + .Variable = rcc::MachineMetadataReference::embeddedIR(5), + .Expression = ExpressionValue, + .Location = DebugLocation, + }; + Context.expect(VariableInfo.verify(Metadata).has_value() && + !rcc::MachineVariableDebugInfo{}.verify(Metadata), + "variable debug records must carry at least one verified metadata component"); + + auto Undefined = + rcc::MachineDebugLocation::reference(rcc::MachineMetadataReference::machineFunction(99)); + Context.expect(!Undefined, "typed debug locations must not accept function-local tuple metadata"); +} + +void testInstructionMetadataAndClone(TestContext &Context) { + auto Target = createTarget(); + auto Function = createFunction(Context, 20, "debug-metadata"); + if (!Target || Function == nullptr) + return; + Context.expect( + Function->metadata().addNode({.Slot = 0, .IsDistinct = false, .Elements = {}}).has_value(), + "instruction metadata graph setup must succeed"); + rcc::MachineBasicBlock *Block = appendBlock(Context, *Function); + if (Block == nullptr) + return; + rcc::MachineInstr *Instruction = appendInstruction(Context, *Function, *Block, ValueOpcode); + if (Instruction == nullptr) + return; + + auto MetadataValue = + rcc::MachineMetadataValue::reference(rcc::MachineMetadataReference::machineFunction(0)); + Context.expect(MetadataValue.has_value(), "instruction metadata reference must be valid"); + if (!MetadataValue) + return; + auto Operand = rcc::MachineOperand::metadata(std::move(*MetadataValue)); + auto InlineLocation = rcc::MachineDILocation::create({ + { + .Line = 12, + .Column = 8, + .Scope = rcc::MachineMetadataReference::embeddedIR(7), + .IsImplicitCode = false, + .AtomGroup = 0, + .AtomRank = 0, + }, + }); + Context.expect(Operand.has_value() && InlineLocation.has_value(), + "instruction metadata values must be constructible"); + if (!Operand || !InlineLocation) + return; + const rcc::MachineDebugLocation DebugLocation = + rcc::MachineDebugLocation::inlineLocation(*InlineLocation); + Context.expect(Instruction->addOperand(std::move(*Operand)).has_value() && + Instruction->setDebugLocation(DebugLocation).has_value(), + "instruction metadata operand and debug location must attach independently"); + Instruction->setSourceLocation({"debug-input.mir", 3, 5, 0}); + const rcc::MachineVariableDebugInfo StackDebugInfo{ + .Variable = rcc::MachineMetadataReference::embeddedIR(8), + .Expression = rcc::MachineMetadataValue::expression( + rcc::MachineDIExpression(std::vector<std::uint64_t>{0x1000, 0, 64})), + .Location = DebugLocation, + }; + rcc::StackObjectDesc StackDescription; + StackDescription.Size = 8; + StackDescription.Align = *rcc::Alignment::create(8); + StackDescription.DebugInfo = StackDebugInfo; + auto StackIndex = Function->frameInfo().createObject(std::move(StackDescription)); + Context.expect(StackIndex.has_value(), + "structured variable debug information must attach to a stack object"); + if (!StackIndex) + return; + Context.expect(rcc::MachineVerifier::verify(*Function, *Target).has_value(), + "well-formed instruction metadata must pass unified verification"); + + auto Clone = + rcc::cloneMachineFunction(*Function, *Target, + { + .NewStableID = rcc::MachineFunctionStableID::fromValue(21), + .NewName = std::string("debug-metadata.clone"), + }); + Context.expect(Clone.has_value(), "instruction debug metadata must support deep cloning"); + if (!Clone) + return; + const rcc::MachineInstr *ClonedInstruction = + Clone->ClonedFunction->firstBlock()->firstInstruction(); + const auto ClonedStackIndex = Clone->FrameIndices.find(*StackIndex); + const rcc::StackObject *ClonedStack = + ClonedStackIndex == Clone->FrameIndices.end() + ? nullptr + : Clone->ClonedFunction->frameInfo().object(ClonedStackIndex->second); + Context.expect( + ClonedInstruction->debugLocation() == DebugLocation && + ClonedInstruction->sourceLocation() == rcc::SourceLocation{"debug-input.mir", 3, 5, 0} && + ClonedInstruction->operandAt(0)->getIf<rcc::MetadataOperand>()->Value.reference() != + nullptr && + ClonedStack != nullptr && ClonedStack->description() != nullptr && + ClonedStack->description()->DebugInfo == StackDebugInfo, + "clone must preserve distinct parser source and semantic debug information"); + + auto InvalidFunction = createFunction(Context, 22, "invalid-debug-metadata"); + if (InvalidFunction == nullptr) + return; + rcc::MachineBasicBlock *InvalidBlock = appendBlock(Context, *InvalidFunction); + if (InvalidBlock == nullptr) + return; + rcc::MachineInstr *InvalidInstruction = + appendInstruction(Context, *InvalidFunction, *InvalidBlock, ValueOpcode); + if (InvalidInstruction == nullptr) + return; + auto InvalidValue = + rcc::MachineMetadataValue::reference(rcc::MachineMetadataReference::machineFunction(9)); + Context.expect(InvalidValue.has_value(), "undefined scoped references remain representable"); + if (!InvalidValue) + return; + auto InvalidOperand = rcc::MachineOperand::metadata(std::move(*InvalidValue)); + Context.expect(InvalidOperand.has_value() && + InvalidInstruction->addOperand(std::move(*InvalidOperand)).has_value() && + !rcc::MachineVerifier::verify(*InvalidFunction, *Target), + "undefined instruction metadata references must fail unified verification"); + + auto InvalidFrameFunction = createFunction(Context, 23, "invalid-frame-debug-metadata"); + if (InvalidFrameFunction == nullptr) + return; + rcc::StackObjectDesc InvalidStackDescription; + InvalidStackDescription.Size = 8; + InvalidStackDescription.Align = *rcc::Alignment::create(8); + InvalidStackDescription.DebugInfo = rcc::MachineVariableDebugInfo{ + .Variable = rcc::MachineMetadataReference::machineFunction(9), + .Expression = std::nullopt, + .Location = std::nullopt, + }; + Context.expect( + !InvalidFrameFunction->frameInfo().createObject(std::move(InvalidStackDescription)), + "typed stack debug information must reject function-local tuple metadata"); +} + +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, 9, "missing-debug-location"); + if (Function == nullptr) + return; + rcc::MachineBasicBlock *Block = appendBlock(Context, *Function); + if (Block == nullptr) + return; + auto Instruction = Function->createInstruction(rcc::opcode(rcc::GenericOpcode::DBG_VALUE)); + if (!Instruction) + return; + for (const std::int64_t Value : {1, 2, 3, 4}) { + if (!(*Instruction)->addOperand(rcc::MachineOperand::immediate(Value))) + return; + } + Context.expect(Block->appendInstruction(**Instruction).has_value(), + "four-operand DBG_VALUE must be linkable for verifier coverage"); + auto Verification = rcc::MachineVerifier::verify(*Function, *Target); + Context.expect(!Verification && + Verification.error().message().find( + "Bad machine code: Missing DebugLoc for debug instruction\n" + "- instruction: DBG_VALUE 1, 2, 3, 4") != std::string_view::npos, + "complete DBG_VALUE must require a DebugLoc with LLVM-compatible diagnostics"); + } + + { + 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; + testMetadataValuesAndLocations(Context); + testInstructionMetadataAndClone(Context); + testInstructionNumbering(Context); + testSubstitutionsAndClone(Context); + testVerifierFailures(Context); + return Context.result(); +} |
