diff options
Diffstat (limited to 'backend/unittests/CodeGen')
| -rw-r--r-- | backend/unittests/CodeGen/CMakeLists.txt | 12 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineFrameInfoTest.cpp | 156 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineFunctionClonerTest.cpp | 78 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineIRBuilderTest.cpp | 10 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineVerifierTest.cpp | 53 |
5 files changed, 294 insertions, 15 deletions
diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index 650cfa9..18cda7c 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -4,6 +4,7 @@ set( RCCBackendCodeGenTestSources CoreTypesTest.cpp InstructionDescriptorTest.cpp + MachineFrameInfoTest.cpp MachineFunctionClonerTest.cpp MachineIRBuilderTest.cpp MachineIRCoreTest.cpp @@ -32,6 +33,15 @@ add_test( COMMAND RCCBackendCodeGenInstructionDescriptorTests ) +add_executable(RCCBackendCodeGenMachineFrameInfoTests MachineFrameInfoTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenMachineFrameInfoTests) +target_link_libraries(RCCBackendCodeGenMachineFrameInfoTests PRIVATE RCCBackend::CodeGen) + +add_test( + NAME RCCBackend.CodeGen.MachineFrameInfo + COMMAND RCCBackendCodeGenMachineFrameInfoTests +) + add_executable( RCCBackendCodeGenMachineFunctionClonerTests MachineFunctionClonerTest.cpp @@ -92,6 +102,7 @@ add_test( rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" @@ -101,6 +112,7 @@ rcc_backend_register_format_files( rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" diff --git a/backend/unittests/CodeGen/MachineFrameInfoTest.cpp b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp new file mode 100644 index 0000000..7368815 --- /dev/null +++ b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineFrameInfo.h" + +#include <cstdint> +#include <iostream> +#include <string_view> + +namespace { + +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::StackObjectDesc fixedObject(std::uint64_t Size, std::uint64_t Align) { + rcc::StackObjectDesc Description; + Description.Size = Size; + Description.Align = *rcc::Alignment::create(Align); + return Description; +} + +void testObjectLifecycle(TestContext &Context) { + rcc::MachineFrameInfo Frame; + + auto LocalDescription = fixedObject(8, 8); + LocalDescription.IsAliased = true; + LocalDescription.AddressTaken = true; + LocalDescription.Lifetime.push_back( + {rcc::MachineInstrStableID::fromValue(3), rcc::MachineInstrStableID::fromValue(7)}); + auto Local = Frame.createObject(LocalDescription); + + auto FixedDescription = fixedObject(16, 16); + FixedDescription.Kind = rcc::StackObjectKind::FixedABI; + FixedDescription.Offset = -32; + auto Fixed = Frame.createObject(FixedDescription); + + auto SpillDescription = fixedObject(8, 8); + SpillDescription.Kind = rcc::StackObjectKind::SpillSlot; + auto Spill = Frame.createObject(SpillDescription); + + rcc::StackObjectDesc VariableDescription; + VariableDescription.Kind = rcc::StackObjectKind::VariableSized; + VariableDescription.Align = *rcc::Alignment::create(32); + auto Variable = Frame.createObject(VariableDescription); + + Context.expect(Local && Fixed && Spill && Variable, "valid stack object kinds must be created"); + if (!Local || !Fixed || !Spill || !Variable) + return; + + Context.expect(Local->value() == 0 && Fixed->value() == 1 && Spill->value() == 2 && + Variable->value() == 3 && Frame.liveObjectCount() == 4, + "frame indices must be monotonic table identities"); + Context.expect(Frame.object(*Fixed)->isFixed() && Frame.object(*Spill)->isSpill() && + Frame.object(*Variable)->isVariableSized(), + "stack object semantic categories must be derived exactly"); + + Context.expect(Frame.setObjectOffset(*Local, -8).has_value() && + Frame.setObjectOffset(*Spill, -16).has_value() && + Frame.markObjectDead(*Spill).has_value(), + "live stack objects must support controlled pre-freeze updates"); + Context.expect(Frame.object(*Spill)->description()->IsDead, + "dead stack object state must remain explicit"); + + Context.expect(Frame.eraseObject(*Fixed).has_value() && Frame.object(*Fixed) == nullptr && + Frame.liveObjectCount() == 3, + "erasing a stack object must leave an inaccessible tombstone"); + auto Replacement = Frame.createObject(fixedObject(4, 4)); + Context.expect(Replacement && Replacement->value() == 4, + "erased frame indices must never be reused"); + if (!Replacement) + return; + Context.expect(Frame.setObjectOffset(*Replacement, -24).has_value(), + "replacement stack object must accept a final offset"); + + Context.expect(Frame.setMaxCallFrameSize(24).has_value() && + Frame.freeze(64, *rcc::Alignment::create(16)).has_value() && + Frame.isFrozen() && Frame.staticStackSize() == 64 && + Frame.stackAlignment().bytes() == 16 && Frame.maxCallFrameSize() == 24 && + Frame.verify().has_value(), + "complete frame layout must freeze atomically"); + + Context.expect(!Frame.createObject(fixedObject(8, 8)) && !Frame.setObjectOffset(*Local, -40) && + !Frame.eraseObject(*Local) && !Frame.setMaxCallFrameSize(32) && + !Frame.freeze(64, *rcc::Alignment::create(16)), + "frozen frames must reject every semantic mutation"); +} + +void testInvalidDescriptions(TestContext &Context) { + rcc::MachineFrameInfo Frame; + + rcc::StackObjectDesc MissingSize; + Context.expect(!Frame.createObject(MissingSize), + "fixed-size stack object must reject a missing size"); + + auto ZeroSize = fixedObject(0, 1); + Context.expect(!Frame.createObject(ZeroSize), "fixed-size stack object must reject zero size"); + + rcc::StackObjectDesc InvalidVariable; + InvalidVariable.Kind = rcc::StackObjectKind::DynamicAlloca; + InvalidVariable.Size = 8; + Context.expect(!Frame.createObject(InvalidVariable), + "variable-sized stack object must reject a fixed size"); + + auto MissingFixedOffset = fixedObject(8, 8); + MissingFixedOffset.Kind = rcc::StackObjectKind::FixedABI; + Context.expect(!Frame.createObject(MissingFixedOffset), + "fixed ABI stack object must require an offset"); + + auto InvalidTarget = fixedObject(8, 8); + InvalidTarget.Kind = rcc::StackObjectKind::Target; + Context.expect(!Frame.createObject(InvalidTarget), + "target stack object must carry target identity"); + + auto InvalidLifetime = fixedObject(8, 8); + InvalidLifetime.Lifetime.push_back( + {rcc::MachineInstrStableID::fromValue(4), rcc::MachineInstrStableID::fromValue(4)}); + Context.expect(!Frame.createObject(InvalidLifetime), + "stack object lifetime must reject equal boundaries"); + + Context.expect(!rcc::StackID::targetStack(4), "target stack IDs must not overlap built-in IDs"); + auto TargetStack = rcc::StackID::targetStack(256); + auto Target = fixedObject(8, 8); + Target.Kind = rcc::StackObjectKind::Target; + if (TargetStack) + Target.Stack = *TargetStack; + auto TargetIndex = Frame.createObject(Target); + Context.expect(TargetStack && TargetIndex, + "target stack identity must admit target stack objects"); + + auto Unresolved = Frame.createObject(fixedObject(8, 8)); + Context.expect(Unresolved && !Frame.freeze(16, *rcc::Alignment::create(8)) && !Frame.isFrozen() && + !Frame.staticStackSize() && Frame.verify().has_value(), + "failed frame freeze must not mutate frame state"); +} + +} // namespace + +int main() { + TestContext Context; + testObjectLifecycle(Context); + testInvalidDescriptions(Context); + return Context.result(); +} diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp index 90e847b..3b2fce9 100644 --- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp +++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp @@ -6,6 +6,7 @@ #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineIRBuilder.h" @@ -137,12 +138,12 @@ createFunction(TestContext &Context, std::string Name, std::uint64_t ID) { return *Block; } -[[nodiscard]] rcc::MachineMemOperandDesc loadDescription() { +[[nodiscard]] rcc::MachineMemOperandDesc loadDescription(rcc::FrameIndex Index) { rcc::MachineMemOperandDesc Description; Description.Accesses = {rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Dereferenceable}; Description.Size = *rcc::MemorySize::fixed(8); Description.Align = *rcc::Alignment::create(8); - Description.Base = rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(4)); + Description.Base = rcc::MemoryBase::frameIndex(Index); Description.Offset = -8; return Description; } @@ -162,6 +163,7 @@ struct SourceFixture final { rcc::MachineBasicBlock *Exit = nullptr; rcc::Register Value; rcc::Register CopyValue; + rcc::FrameIndex StackSlot; const rcc::MachineMemOperand *LoadMMO = nullptr; }; @@ -177,6 +179,25 @@ struct SourceFixture final { Context.expect(Tombstone && Function.registerInfo().eraseVirtualRegister(*Tombstone).has_value(), "source vreg history must contain a tombstone"); + rcc::StackObjectDesc HistoricalFrame; + HistoricalFrame.Size = 4; + HistoricalFrame.Align = *rcc::Alignment::create(4); + auto HistoricalFrameIndex = Function.frameInfo().createObject(HistoricalFrame); + Context.expect(HistoricalFrameIndex && + Function.frameInfo().eraseObject(*HistoricalFrameIndex).has_value(), + "source frame history must contain a tombstone"); + + rcc::StackObjectDesc StackSlot; + StackSlot.Kind = rcc::StackObjectKind::SpillSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + StackSlot.Offset = -16; + auto StackSlotIndex = Function.frameInfo().createObject(StackSlot); + Context.expect(StackSlotIndex.has_value(), "source live stack object must be created"); + if (!StackSlotIndex) + return false; + Fixture.StackSlot = *StackSlotIndex; + Fixture.Entry = createBlock(Context, Function); Fixture.Exit = createBlock(Context, Function); if (Fixture.Entry == nullptr || Fixture.Exit == nullptr) @@ -236,8 +257,7 @@ struct SourceFixture final { "source tied copy must build"); auto Frame = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); - Context.expect(Frame && Frame->addFrameIndex(rcc::FrameIndex::fromValue(4), -8).has_value() && - Frame->build(), + Context.expect(Frame && Frame->addFrameIndex(Fixture.StackSlot, -8).has_value() && Frame->build(), "source frame-index instruction must build"); auto Address = Builder.buildInstr(rcc::Opcode::fromValue(BlockAddressOpcode)); @@ -246,7 +266,7 @@ struct SourceFixture final { Address->build(), "source self block-address instruction must build"); - auto MMO = Function.createMemOperand(loadDescription()); + auto MMO = Function.createMemOperand(loadDescription(Fixture.StackSlot)); Context.expect(MMO.has_value(), "source MMO must be created"); if (!MMO) return false; @@ -255,6 +275,20 @@ struct SourceFixture final { Context.expect(Load && Load->addMemOperand(**MMO).has_value() && Load->build(), "source load with MMO must build"); + rcc::MachineInstr *Definition = findInstruction(*Fixture.Entry, DefOpcode); + rcc::MachineInstr *LoadInstruction = findInstruction(*Fixture.Exit, LoadOpcode); + const rcc::StackObject *Object = Function.frameInfo().object(Fixture.StackSlot); + if (Definition == nullptr || LoadInstruction == nullptr || Object == nullptr) + return false; + rcc::StackObjectDesc UpdatedStackSlot = *Object->description(); + UpdatedStackSlot.Lifetime.push_back({Definition->stableID(), LoadInstruction->stableID()}); + Context.expect(Function.frameInfo() + .updateObject(Fixture.StackSlot, std::move(UpdatedStackSlot)) + .has_value() && + Function.frameInfo().setMaxCallFrameSize(16).has_value() && + Function.frameInfo().freeze(32, *rcc::Alignment::create(16)).has_value(), + "source frame metadata must be finalized"); + Function.properties().set(rcc::MachineFunctionProperty::IsSSA); Function.properties().set(rcc::MachineFunctionProperty::TracksLiveness); Function.properties().set(rcc::MachineFunctionProperty::NoPHIs); @@ -289,7 +323,9 @@ void testDeepClone(TestContext &Context) { "source and destination must independently preserve valid properties"); Context.expect(Source->allBlocks().size() == 3 && Destination.allBlocks().size() == 2 && Source->allInstructions().size() == 7 && - Destination.allInstructions().size() == 6, + Destination.allInstructions().size() == 6 && + Source->frameInfo().objects().size() == 2 && + Destination.frameInfo().objects().size() == 1, "clone must omit detached arena history"); Context.expect(Clone->BlockStableIDs.size() == 2 && Clone->InstructionStableIDs.size() == 6 && Clone->VirtualRegisters.size() == 2, @@ -375,21 +411,39 @@ void testDeepClone(TestContext &Context) { DestinationCopy->operandAt(0)->tiedOperandIndex() == 1 && DestinationCopy->operandAt(1)->tiedOperandIndex() == 0, "clone must remap tied registers and rebuild ties"); - Context.expect(DestinationFrame->operandAt(0)->getIf<rcc::FrameIndexOperand>()->Index == - rcc::FrameIndex::fromValue(4) && - Clone->FrameIndices.contains(rcc::FrameIndex::fromValue(4)), - "clone must preserve and map frame indices"); + const auto FramePosition = Clone->FrameIndices.find(Fixture.StackSlot); + Context.expect(FramePosition != Clone->FrameIndices.end() && + FramePosition->second == rcc::FrameIndex::fromValue(0) && + DestinationFrame->operandAt(0)->getIf<rcc::FrameIndexOperand>()->Index == + FramePosition->second, + "clone must compact and remap frame indices"); Context.expect( DestinationAddress->operandAt(0)->getIf<rcc::BlockAddressOperand>()->FunctionName == "cloned_function", "clone must rewrite self block-address function names"); + rcc::MachineMemOperandDesc ExpectedMMO = Fixture.LoadMMO->description(); + if (FramePosition != Clone->FrameIndices.end()) + ExpectedMMO.Base = rcc::MemoryBase::frameIndex(FramePosition->second); Context.expect(DestinationLoad->memOperands().size() == 1 && DestinationLoad->memOperands()[0] != Fixture.LoadMMO && - *DestinationLoad->memOperands()[0] == *Fixture.LoadMMO && + DestinationLoad->memOperands()[0]->description() == ExpectedMMO && Destination.ownsMemOperand(*DestinationLoad->memOperands()[0]) && !Source->ownsMemOperand(*DestinationLoad->memOperands()[0]), - "clone must deep-copy MMO ownership while preserving semantics"); + "clone must deep-copy MMO ownership while remapping frame semantics"); + + const rcc::StackObject *DestinationStackSlot = + FramePosition == Clone->FrameIndices.end() + ? nullptr + : Destination.frameInfo().object(FramePosition->second); + Context.expect( + DestinationStackSlot != nullptr && Destination.frameInfo().isFrozen() && + Destination.frameInfo().staticStackSize() == 32 && + Destination.frameInfo().maxCallFrameSize() == 16 && + DestinationStackSlot->description()->Lifetime.size() == 1 && + DestinationStackSlot->description()->Lifetime[0].Begin == DestinationDef->stableID() && + DestinationStackSlot->description()->Lifetime[0].End == DestinationLoad->stableID(), + "clone must preserve frozen frame layout and remap instruction lifetimes"); rcc::MachineInstr *SourceDef = findInstruction(*Fixture.Entry, DefOpcode); const auto InstructionID = SourceDef == nullptr diff --git a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp index bda30b6..ba5e3db 100644 --- a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp +++ b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp @@ -356,6 +356,16 @@ void testImplicitAndMemoryValidation(TestContext &Context) { Function->allInstructions().size() == BeforeMissingMemory, "missing required MMO must fail before instruction allocation"); + rcc::StackObjectDesc StackSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + auto FrameIndex = Function->frameInfo().createObject(StackSlot); + auto ForeignFrameIndex = OtherFunction->frameInfo().createObject(StackSlot); + Context.expect(FrameIndex && ForeignFrameIndex, + "memory validation stack objects must be created"); + if (!FrameIndex || !ForeignFrameIndex) + return; + auto LoadMemory = Function->createMemOperand(memoryDescription(rcc::MemoryAccessFlag::Load)); auto StoreMemory = Function->createMemOperand(memoryDescription(rcc::MemoryAccessFlag::Store)); auto ForeignMemory = diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp index b8b985e..82ae68b 100644 --- a/backend/unittests/CodeGen/MachineVerifierTest.cpp +++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp @@ -346,12 +346,17 @@ void testPropertyFailures(TestContext &Context) { auto Frame = createFunction(Context, "frame_index", 9); rcc::MachineBasicBlock *FrameBlock = Frame ? createBlock(Context, *Frame) : nullptr; if (Frame && FrameBlock != nullptr) { + rcc::StackObjectDesc StackSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + auto FrameIndex = Frame->frameInfo().createObject(StackSlot); rcc::MachineIRBuilder Builder(*Frame, *InstructionSet); - Context.expect(Builder.setInsertPoint(*FrameBlock).has_value(), + Context.expect(FrameIndex && Builder.setInsertPoint(*FrameBlock).has_value(), "frame-index negative test setup must succeed"); + if (!FrameIndex) + return; auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); - Context.expect(Pending && Pending->addFrameIndex(rcc::FrameIndex::fromValue(0)).has_value() && - Pending->build(), + Context.expect(Pending && Pending->addFrameIndex(*FrameIndex).has_value() && Pending->build(), "frame-index negative test instruction must build"); Frame->properties().set(rcc::MachineFunctionProperty::NoFrameIndices); Context.expect(!rcc::MachineVerifier::verify(*Frame, *InstructionSet), @@ -447,6 +452,47 @@ void testSelectedStateAndPseudoDeadline(TestContext &Context) { } } +void testFrameStateFailures(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto Function = createFunction(Context, "frame_references", 14); + rcc::MachineBasicBlock *Block = Function ? createBlock(Context, *Function) : nullptr; + if (!InstructionSet || !Function || Block == nullptr) + return; + + rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + Context.expect(Builder.setInsertPoint(*Block).has_value(), + "frame reference insertion point must be valid"); + auto Unknown = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); + Context.expect(Unknown && Unknown->addFrameIndex(rcc::FrameIndex::fromValue(99)).has_value() && + !Unknown->build() && Function->allInstructions().empty(), + "builder must reject an unknown frame index before instruction allocation"); + + rcc::StackObjectDesc StackSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + auto FrameIndex = Function->frameInfo().createObject(StackSlot); + if (!FrameIndex) + return; + auto Live = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); + Context.expect(Live && Live->addFrameIndex(*FrameIndex).has_value() && Live->build(), + "known live frame index must build"); + Context.expect(Function->frameInfo().markObjectDead(*FrameIndex).has_value() && + !rcc::MachineVerifier::verify(*Function, *InstructionSet), + "verifier must reject an instruction that references a dead stack object"); + + auto Lifetime = createFunction(Context, "frame_lifetime", 15); + if (!Lifetime) + return; + rcc::StackObjectDesc LifetimeSlot; + LifetimeSlot.Size = 8; + LifetimeSlot.Align = *rcc::Alignment::create(8); + LifetimeSlot.Lifetime.push_back( + {rcc::MachineInstrStableID::fromValue(1), rcc::MachineInstrStableID::fromValue(2)}); + Context.expect(Lifetime->frameInfo().createObject(LifetimeSlot).has_value() && + !rcc::MachineVerifier::verify(*Lifetime, *InstructionSet), + "verifier must reject stack lifetimes that reference unknown instructions"); +} + } // namespace int main() { @@ -455,5 +501,6 @@ int main() { testReachableStructuralFailures(Context); testPropertyFailures(Context); testSelectedStateAndPseudoDeadline(Context); + testFrameStateFailures(Context); return Context.result(); } |
