diff options
Diffstat (limited to 'backend/unittests/CodeGen')
| -rw-r--r-- | backend/unittests/CodeGen/CMakeLists.txt | 36 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineConstantPoolTest.cpp | 260 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineFunctionClonerTest.cpp | 197 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineIRBuilderTest.cpp | 63 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp | 171 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineVerifierTest.cpp | 64 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/TargetConstantTest.cpp | 278 |
7 files changed, 1043 insertions, 26 deletions
diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index 18cda7c..eb4dbfe 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -4,12 +4,15 @@ set( RCCBackendCodeGenTestSources CoreTypesTest.cpp InstructionDescriptorTest.cpp + MachineConstantPoolTest.cpp MachineFrameInfoTest.cpp MachineFunctionClonerTest.cpp MachineIRBuilderTest.cpp MachineIRCoreTest.cpp + MachineJumpTableInfoTest.cpp MachineVerifierTest.cpp MachineValueTypesTest.cpp + TargetConstantTest.cpp ) add_executable(RCCBackendCodeGenCoreTypesTests CoreTypesTest.cpp) @@ -33,6 +36,15 @@ add_test( COMMAND RCCBackendCodeGenInstructionDescriptorTests ) +add_executable(RCCBackendCodeGenMachineConstantPoolTests MachineConstantPoolTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenMachineConstantPoolTests) +target_link_libraries(RCCBackendCodeGenMachineConstantPoolTests PRIVATE RCCBackend::CodeGen) + +add_test( + NAME RCCBackend.CodeGen.MachineConstantPool + COMMAND RCCBackendCodeGenMachineConstantPoolTests +) + add_executable(RCCBackendCodeGenMachineFrameInfoTests MachineFrameInfoTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenMachineFrameInfoTests) target_link_libraries(RCCBackendCodeGenMachineFrameInfoTests PRIVATE RCCBackend::CodeGen) @@ -99,23 +111,47 @@ add_test( COMMAND RCCBackendCodeGenMachineIRCoreTests ) +add_executable(RCCBackendCodeGenMachineJumpTableInfoTests MachineJumpTableInfoTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenMachineJumpTableInfoTests) +target_link_libraries(RCCBackendCodeGenMachineJumpTableInfoTests PRIVATE RCCBackend::CodeGen) + +add_test( + NAME RCCBackend.CodeGen.MachineJumpTableInfo + COMMAND RCCBackendCodeGenMachineJumpTableInfoTests +) + +add_executable(RCCBackendCodeGenTargetConstantTests TargetConstantTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenTargetConstantTests) +target_link_libraries(RCCBackendCodeGenTargetConstantTests PRIVATE RCCBackend::CodeGen) + +add_test( + NAME RCCBackend.CodeGen.TargetConstant + COMMAND RCCBackendCodeGenTargetConstantTests +) + rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.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" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp" ) rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineConstantPoolTest.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" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineJumpTableInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineVerifierTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/TargetConstantTest.cpp" ) diff --git a/backend/unittests/CodeGen/MachineConstantPoolTest.cpp b/backend/unittests/CodeGen/MachineConstantPoolTest.cpp new file mode 100644 index 0000000..7343fdb --- /dev/null +++ b/backend/unittests/CodeGen/MachineConstantPoolTest.cpp @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineConstantPool.h" + +#include <bit> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +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; +}; + +struct PoolTargetConstant final { + std::uint64_t Value = 0; + std::string Symbol; + + [[nodiscard]] static constexpr rcc::TargetConstantTypeID stableTypeID() noexcept { + return rcc::TargetConstantTypeID::fromValue(0x5253434350430001ULL); + } + + [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept { + return 1; + } + + [[nodiscard]] rcc::Expected<void> verify() const { + if (Value == 0) { + return rcc::makeUnexpected(rcc::ErrorCode::VerificationError, + "pool target constant value must be nonzero"); + } + return {}; + } + + [[nodiscard]] rcc::Expected<PoolTargetConstant> clone() const { + return PoolTargetConstant{Value, Symbol}; + } + + [[nodiscard]] std::uint64_t stableHash() const noexcept { + std::uint64_t Hash = Value; + for (const char Character : Symbol) + Hash = (Hash * 16777619U) ^ static_cast<unsigned char>(Character); + return Hash; + } + + [[nodiscard]] std::string print() const { + return Symbol.empty() ? std::to_string(Value) : Symbol + "+" + std::to_string(Value); + } + + [[nodiscard]] std::vector<std::byte> bytes() const { + std::vector<std::byte> Bytes(8); + for (std::size_t Index = 0; Index < Bytes.size(); ++Index) + Bytes[Index] = static_cast<std::byte>((Value >> (Index * 8U)) & 0xffU); + return Bytes; + } + + [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const { + std::vector<std::byte> Payload = bytes(); + for (const char Character : Symbol) + Payload.push_back(static_cast<std::byte>(static_cast<unsigned char>(Character))); + return Payload; + } + + [[nodiscard]] rcc::Expected<rcc::TargetConstantEmission> emit() const { + rcc::TargetConstantEmission Emission; + Emission.Bytes = bytes(); + if (!Symbol.empty()) { + Emission.Fixups.push_back({0, rcc::TargetRelocationKindID::fromValue(3), Symbol, + std::bit_cast<std::int64_t>(Value)}); + } + return Emission; + } + + [[nodiscard]] rcc::Alignment requiredAlignment() const noexcept { + return *rcc::Alignment::create(8); + } + + [[nodiscard]] std::optional<rcc::TargetRelocationKindID> relocationKind() const noexcept { + if (Symbol.empty()) + return std::nullopt; + return rcc::TargetRelocationKindID::fromValue(3); + } + + [[nodiscard]] static rcc::Expected<PoolTargetConstant> parse(std::string_view Text) { + if (Text.empty()) + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "pool constant text is empty"); + return PoolTargetConstant{1, std::string(Text)}; + } + + [[nodiscard]] static rcc::Expected<PoolTargetConstant> + decode(std::uint32_t Version, std::span<const std::byte> Payload) { + if (Version != schemaVersion() || Payload.size() < 8) { + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, + "pool target constant encoding is invalid"); + } + std::uint64_t Value = 0; + for (std::size_t Index = 0; Index < 8; ++Index) + Value |= std::to_integer<std::uint64_t>(Payload[Index]) << (Index * 8U); + std::string Symbol; + for (const std::byte Character : Payload.subspan(8)) + Symbol.push_back(static_cast<char>(std::to_integer<unsigned char>(Character))); + return PoolTargetConstant{Value, std::move(Symbol)}; + } + + friend bool operator==(const PoolTargetConstant &Left, const PoolTargetConstant &Right) = default; +}; + +[[nodiscard]] rcc::MachineConstantPoolEntryDesc +genericDescription(rcc::ConstantPoolMergeKind Merge) { + rcc::MachineConstantPoolEntryDesc Description; + Description.Value = + rcc::GenericConstantData{"i32", {std::byte{1}, std::byte{2}, std::byte{3}, std::byte{4}}}; + Description.Size = 4; + Description.Align = *rcc::Alignment::create(4); + Description.Merge = Merge; + if (Merge == rcc::ConstantPoolMergeKind::Exact) + Description.Placement.Section = rcc::ConstantPoolSectionKind::MergeableReadOnly; + return Description; +} + +[[nodiscard]] rcc::MachineConstantPoolEntryDesc targetDescription(bool Expression) { + rcc::MachineConstantPoolEntryDesc Description; + auto Constant = rcc::TargetConstant::create( + PoolTargetConstant{Expression ? 17U : 9U, Expression ? "symbol" : ""}); + if (Expression) + Description.Value = rcc::TargetExpressionConstant{std::move(*Constant)}; + else + Description.Value = std::move(*Constant); + Description.Size = 8; + Description.Align = *rcc::Alignment::create(8); + Description.Relocation = + Expression ? rcc::ConstantPoolRelocationMode::Target : rcc::ConstantPoolRelocationMode::None; + return Description; +} + +[[nodiscard]] rcc::MachineConstantPoolEntryDesc symbolDifferenceDescription() { + rcc::MachineConstantPoolEntryDesc Description; + Description.Value = rcc::SymbolDifferenceConstant{"upper", "lower", 4}; + Description.Size = 4; + Description.Align = *rcc::Alignment::create(4); + Description.Relocation = rcc::ConstantPoolRelocationMode::PositionIndependent; + return Description; +} + +[[nodiscard]] rcc::MachineConstantPoolEntryDesc blockAddressDescription() { + rcc::MachineConstantPoolEntryDesc Description; + Description.Value = rcc::BlockAddressConstant{"function", "block", 0}; + Description.Size = 8; + Description.Align = *rcc::Alignment::create(8); + Description.Relocation = rcc::ConstantPoolRelocationMode::Static; + return Description; +} + +void testPoolLifecycle(TestContext &Context) { + rcc::MachineConstantPool Pool; + + auto MergedA = Pool.addEntry(genericDescription(rcc::ConstantPoolMergeKind::Exact)); + auto MergedB = Pool.addEntry(genericDescription(rcc::ConstantPoolMergeKind::Exact)); + auto Unmerged = Pool.addEntry(genericDescription(rcc::ConstantPoolMergeKind::Never)); + Context.expect(MergedA && MergedB && Unmerged && *MergedA == *MergedB && MergedA->value() == 0 && + Unmerged->value() == 1 && Pool.entries().size() == 2 && + Pool.liveEntryCount() == 2, + "exact constants must merge while nonmergeable constants retain identity"); + if (!MergedA || !Unmerged) + return; + + Context.expect( + !Pool.updateEntry(*Unmerged, genericDescription(rcc::ConstantPoolMergeKind::Exact)), + "pool update must reject duplicate mergeable entries"); + Context.expect(Pool.eraseEntry(*Unmerged).has_value() && Pool.entry(*Unmerged) == nullptr, + "constant pool erase must leave a tombstone"); + + auto Target = Pool.addEntry(targetDescription(false)); + auto Difference = Pool.addEntry(symbolDifferenceDescription()); + auto Address = Pool.addEntry(blockAddressDescription()); + auto Expression = Pool.addEntry(targetDescription(true)); + Context.expect(Target && Difference && Address && Expression && Target->value() == 2 && + Expression->value() == 5 && Pool.liveEntryCount() == 5, + "all constant pool value kinds must receive monotonic indices"); + if (!Target || !Expression) + return; + + const rcc::MachineConstantPoolEntry *TargetEntry = Pool.entry(*Target); + auto ClonedDescription = + TargetEntry == nullptr ? rcc::Expected<rcc::MachineConstantPoolEntryDesc>(rcc::makeUnexpected( + rcc::ErrorCode::InternalError, "missing target entry")) + : TargetEntry->cloneDescription(); + Context.expect(ClonedDescription && + TargetEntry->description()->semanticallyEquals(*ClonedDescription), + "target constant pool descriptions must deep-clone"); + + Context.expect(Pool.verify().has_value() && Pool.freeze().has_value() && Pool.isFrozen(), + "valid constant pool must freeze after verification"); + Context.expect(!Pool.addEntry(blockAddressDescription()) && !Pool.eraseEntry(*Target) && + !Pool.updateEntry(*Expression, targetDescription(true)) && !Pool.freeze(), + "frozen constant pool must reject every mutation"); +} + +void testInvalidEntries(TestContext &Context) { + rcc::MachineConstantPool Pool; + + auto SizeMismatch = genericDescription(rcc::ConstantPoolMergeKind::Never); + SizeMismatch.Size = 8; + Context.expect(!Pool.addEntry(std::move(SizeMismatch)), + "generic constant byte size must match its entry size"); + + auto RelocatingBytes = genericDescription(rcc::ConstantPoolMergeKind::Never); + RelocatingBytes.Relocation = rcc::ConstantPoolRelocationMode::Static; + Context.expect(!Pool.addEntry(std::move(RelocatingBytes)), + "raw generic bytes must reject implicit relocation semantics"); + + auto WeakTargetAlignment = targetDescription(false); + WeakTargetAlignment.Align = *rcc::Alignment::create(4); + Context.expect(!Pool.addEntry(std::move(WeakTargetAlignment)), + "target constant must enforce its required alignment"); + + auto MissingTargetRelocation = targetDescription(true); + MissingTargetRelocation.Relocation = rcc::ConstantPoolRelocationMode::None; + Context.expect(!Pool.addEntry(std::move(MissingTargetRelocation)), + "target expression must agree with its relocation contract"); + + auto InvalidPlacement = blockAddressDescription(); + InvalidPlacement.Placement.Section = rcc::ConstantPoolSectionKind::Target; + Context.expect(!Pool.addEntry(std::move(InvalidPlacement)), + "target placement must carry target flags"); + + auto ZeroDistance = blockAddressDescription(); + ZeroDistance.Placement.MaximumDistance = 0; + Context.expect(!Pool.addEntry(std::move(ZeroDistance)), + "constant pool maximum distance must be nonzero"); +} + +} // namespace + +int main() { + TestContext Context; + testPoolLifecycle(Context); + testInvalidEntries(Context); + return Context.result(); +} diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp index 3b2fce9..f634a57 100644 --- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp +++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp @@ -6,17 +6,20 @@ #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineIRBuilder.h" #include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineMemOperand.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" #include "rcc/CodeGen/MachineVerifier.h" #include <algorithm> +#include <cstddef> #include <cstdint> #include <iostream> #include <memory> @@ -35,6 +38,8 @@ constexpr std::uint32_t CopyOpcode = 4; constexpr std::uint32_t FrameOpcode = 5; constexpr std::uint32_t BlockAddressOpcode = 6; constexpr std::uint32_t LoadOpcode = 7; +constexpr std::uint32_t ConstantPoolOpcode = 8; +constexpr std::uint32_t JumpTableOpcode = 9; class TestContext final { public: @@ -106,6 +111,16 @@ private: Load.Flags.set(rcc::InstructionFlag::MayLoad); Load.RequiresMemoryOperands = true; + auto ConstantPool = instructionDescriptor(ConstantPoolOpcode, "TEST_CONSTANT_POOL"); + rcc::OperandDescriptor ConstantPoolIndex; + ConstantPoolIndex.Kinds = {rcc::MachineOperand::Kind::ConstantPoolIndex}; + ConstantPool.Operands.push_back(ConstantPoolIndex); + + auto JumpTable = instructionDescriptor(JumpTableOpcode, "TEST_JUMP_TABLE"); + rcc::OperandDescriptor JumpTableIndex; + JumpTableIndex.Kinds = {rcc::MachineOperand::Kind::JumpTableIndex}; + JumpTable.Operands.push_back(JumpTableIndex); + std::vector<rcc::InstructionDescriptor> Descriptors; Descriptors.push_back(std::move(Nop)); Descriptors.push_back(std::move(Def)); @@ -114,6 +129,8 @@ private: Descriptors.push_back(std::move(Frame)); Descriptors.push_back(std::move(BlockAddress)); Descriptors.push_back(std::move(Load)); + Descriptors.push_back(std::move(ConstantPool)); + Descriptors.push_back(std::move(JumpTable)); return rcc::InstructionSet::create(std::move(Descriptors)); } @@ -138,12 +155,12 @@ createFunction(TestContext &Context, std::string Name, std::uint64_t ID) { return *Block; } -[[nodiscard]] rcc::MachineMemOperandDesc loadDescription(rcc::FrameIndex Index) { +[[nodiscard]] rcc::MachineMemOperandDesc loadDescription(rcc::MemoryBase Base) { 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(Index); + Description.Base = std::move(Base); Description.Offset = -8; return Description; } @@ -164,7 +181,9 @@ struct SourceFixture final { rcc::Register Value; rcc::Register CopyValue; rcc::FrameIndex StackSlot; - const rcc::MachineMemOperand *LoadMMO = nullptr; + rcc::ConstantPoolIndex Constant; + rcc::JumpTableIndex JumpTable; + std::vector<const rcc::MachineMemOperand *> LoadMMOs; }; [[nodiscard]] bool populateSource(TestContext &Context, rcc::MachineFunction &Function, @@ -203,6 +222,50 @@ struct SourceFixture final { if (Fixture.Entry == nullptr || Fixture.Exit == nullptr) return false; + rcc::MachineConstantPoolEntryDesc HistoricalConstant; + HistoricalConstant.Value = + rcc::GenericConstantData{"i32", std::vector<std::byte>(4, std::byte{1})}; + HistoricalConstant.Size = 4; + HistoricalConstant.Align = *rcc::Alignment::create(4); + auto HistoricalConstantIndex = Function.constantPool().addEntry(std::move(HistoricalConstant)); + Context.expect(HistoricalConstantIndex && + Function.constantPool().eraseEntry(*HistoricalConstantIndex).has_value(), + "source constant pool history must contain a tombstone"); + + rcc::MachineConstantPoolEntryDesc Constant; + Constant.Value = rcc::BlockAddressConstant{std::string(Function.name()), "exit", 5}; + Constant.Size = 8; + Constant.Align = *rcc::Alignment::create(8); + Constant.Relocation = rcc::ConstantPoolRelocationMode::Static; + auto ConstantIndex = Function.constantPool().addEntry(std::move(Constant)); + Context.expect(ConstantIndex && Function.constantPool().freeze().has_value(), + "source live constant pool entry must be frozen"); + if (!ConstantIndex) + return false; + Fixture.Constant = *ConstantIndex; + + rcc::MachineJumpTableDesc HistoricalJumpTable; + HistoricalJumpTable.EntrySize = 4; + HistoricalJumpTable.Align = *rcc::Alignment::create(4); + HistoricalJumpTable.Targets = {Fixture.Entry}; + auto HistoricalJumpTableIndex = + Function.jumpTableInfo().createTable(std::move(HistoricalJumpTable)); + Context.expect(HistoricalJumpTableIndex && + Function.jumpTableInfo().eraseTable(*HistoricalJumpTableIndex).has_value(), + "source jump table history must contain a tombstone"); + + rcc::MachineJumpTableDesc JumpTable; + JumpTable.Encoding = rcc::JumpTableEntryEncoding::RelativeToTableBase; + JumpTable.EntrySize = 4; + JumpTable.Align = *rcc::Alignment::create(4); + JumpTable.Targets = {Fixture.Exit, Fixture.Entry, Fixture.Exit}; + auto JumpTableIndex = Function.jumpTableInfo().createTable(std::move(JumpTable)); + Context.expect(JumpTableIndex && Function.jumpTableInfo().freeze().has_value(), + "source live jump table must be frozen"); + if (!JumpTableIndex) + return false; + Fixture.JumpTable = *JumpTableIndex; + auto Value = Function.registerInfo().createVirtualRegister(*Type); auto CopyValue = Function.registerInfo().createVirtualRegister(*Type); Context.expect(Value && CopyValue, "source live vregs must be created"); @@ -260,20 +323,39 @@ struct SourceFixture final { Context.expect(Frame && Frame->addFrameIndex(Fixture.StackSlot, -8).has_value() && Frame->build(), "source frame-index instruction must build"); + auto ConstantPool = Builder.buildInstr(rcc::Opcode::fromValue(ConstantPoolOpcode)); + Context.expect(ConstantPool && ConstantPool->addConstantPool(Fixture.Constant, 8).has_value() && + ConstantPool->build(), + "source constant-pool instruction must build"); + + auto JumpTableInstruction = Builder.buildInstr(rcc::Opcode::fromValue(JumpTableOpcode)); + Context.expect(JumpTableInstruction && + JumpTableInstruction->addJumpTable(Fixture.JumpTable).has_value() && + JumpTableInstruction->build(), + "source jump-table instruction must build"); + auto Address = Builder.buildInstr(rcc::Opcode::fromValue(BlockAddressOpcode)); Context.expect( Address && Address->addBlockAddress(std::string(Function.name()), "exit", 3).has_value() && Address->build(), "source self block-address instruction must build"); - auto MMO = Function.createMemOperand(loadDescription(Fixture.StackSlot)); - Context.expect(MMO.has_value(), "source MMO must be created"); - if (!MMO) - return false; - Fixture.LoadMMO = *MMO; + for (rcc::MemoryBase Base : {rcc::MemoryBase::frameIndex(Fixture.StackSlot), + rcc::MemoryBase::constantPool(Fixture.Constant), + rcc::MemoryBase::jumpTable(Fixture.JumpTable)}) { + auto MMO = Function.createMemOperand(loadDescription(std::move(Base))); + Context.expect(MMO.has_value(), "source MMO must be created"); + if (!MMO) + return false; + Fixture.LoadMMOs.push_back(*MMO); + } auto Load = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); - Context.expect(Load && Load->addMemOperand(**MMO).has_value() && Load->build(), - "source load with MMO must build"); + if (!Load) + return false; + bool AddedAllMMOs = true; + for (const rcc::MachineMemOperand *MMO : Fixture.LoadMMOs) + AddedAllMMOs &= Load->addMemOperand(*MMO).has_value(); + Context.expect(AddedAllMMOs && Load->build(), "source load with MMOs must build"); rcc::MachineInstr *Definition = findInstruction(*Fixture.Entry, DefOpcode); rcc::MachineInstr *LoadInstruction = findInstruction(*Fixture.Exit, LoadOpcode); @@ -322,13 +404,18 @@ void testDeepClone(TestContext &Context) { rcc::MachineVerifier::verify(*Source, *InstructionSet).has_value(), "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 && + Source->allInstructions().size() == 9 && + Destination.allInstructions().size() == 8 && Source->frameInfo().objects().size() == 2 && - Destination.frameInfo().objects().size() == 1, + Destination.frameInfo().objects().size() == 1 && + Source->constantPool().entries().size() == 2 && + Destination.constantPool().entries().size() == 1 && + Source->jumpTableInfo().tables().size() == 2 && + Destination.jumpTableInfo().tables().size() == 1, "clone must omit detached arena history"); - Context.expect(Clone->BlockStableIDs.size() == 2 && Clone->InstructionStableIDs.size() == 6 && - Clone->VirtualRegisters.size() == 2, + Context.expect(Clone->BlockStableIDs.size() == 2 && Clone->InstructionStableIDs.size() == 8 && + Clone->VirtualRegisters.size() == 2 && Clone->FrameIndices.size() == 1 && + Clone->ConstantPoolIndices.size() == 1 && Clone->JumpTableIndices.size() == 1, "clone must publish complete live identity mappings"); rcc::MachineBasicBlock *DestinationEntry = Destination.firstBlock(); @@ -387,13 +474,18 @@ void testDeepClone(TestContext &Context) { rcc::MachineInstr *DestinationBranch = findInstruction(*DestinationEntry, BranchOpcode); rcc::MachineInstr *DestinationCopy = findInstruction(*DestinationExit, CopyOpcode); rcc::MachineInstr *DestinationFrame = findInstruction(*DestinationExit, FrameOpcode); + rcc::MachineInstr *DestinationConstantPool = + findInstruction(*DestinationExit, ConstantPoolOpcode); + rcc::MachineInstr *DestinationJumpTable = findInstruction(*DestinationExit, JumpTableOpcode); rcc::MachineInstr *DestinationAddress = findInstruction(*DestinationExit, BlockAddressOpcode); rcc::MachineInstr *DestinationLoad = findInstruction(*DestinationExit, LoadOpcode); Context.expect(DestinationDef && DestinationBranch && DestinationCopy && DestinationFrame && - DestinationAddress && DestinationLoad, + DestinationConstantPool && DestinationJumpTable && DestinationAddress && + DestinationLoad, "clone must preserve every live instruction"); if (!DestinationDef || !DestinationBranch || !DestinationCopy || !DestinationFrame || - !DestinationAddress || !DestinationLoad) { + !DestinationConstantPool || !DestinationJumpTable || !DestinationAddress || + !DestinationLoad) { return; } @@ -417,20 +509,73 @@ void testDeepClone(TestContext &Context) { DestinationFrame->operandAt(0)->getIf<rcc::FrameIndexOperand>()->Index == FramePosition->second, "clone must compact and remap frame indices"); + + const auto ConstantPosition = Clone->ConstantPoolIndices.find(Fixture.Constant); + const auto JumpTablePosition = Clone->JumpTableIndices.find(Fixture.JumpTable); + Context.expect( + ConstantPosition != Clone->ConstantPoolIndices.end() && + JumpTablePosition != Clone->JumpTableIndices.end() && + ConstantPosition->second == rcc::ConstantPoolIndex::fromValue(0) && + JumpTablePosition->second == rcc::JumpTableIndex::fromValue(0) && + DestinationConstantPool->operandAt(0)->getIf<rcc::ConstantPoolOperand>()->Index == + ConstantPosition->second && + DestinationJumpTable->operandAt(0)->getIf<rcc::JumpTableOperand>()->Index == + JumpTablePosition->second, + "clone must compact and remap constant-pool and jump-table indices"); + + const rcc::MachineConstantPoolEntry *DestinationConstantEntry = + ConstantPosition == Clone->ConstantPoolIndices.end() + ? nullptr + : Destination.constantPool().entry(ConstantPosition->second); + const auto *DestinationPoolAddress = + DestinationConstantEntry == nullptr + ? nullptr + : std::get_if<rcc::BlockAddressConstant>(&DestinationConstantEntry->description()->Value); + Context.expect(Destination.constantPool().isFrozen() && DestinationPoolAddress != nullptr && + DestinationPoolAddress->FunctionName == "cloned_function" && + DestinationPoolAddress->BlockName == "exit", + "clone must preserve frozen constant pool state and rewrite self addresses"); + + const rcc::MachineJumpTableEntry *DestinationJumpTableEntry = + JumpTablePosition == Clone->JumpTableIndices.end() + ? nullptr + : Destination.jumpTableInfo().table(JumpTablePosition->second); + const rcc::MachineJumpTableDesc *DestinationJumpDescription = + DestinationJumpTableEntry == nullptr ? nullptr : DestinationJumpTableEntry->description(); + Context.expect(Destination.jumpTableInfo().isFrozen() && DestinationJumpDescription != nullptr && + DestinationJumpDescription->Targets.size() == 3 && + DestinationJumpDescription->Targets[0] == DestinationExit && + DestinationJumpDescription->Targets[1] == DestinationEntry && + DestinationJumpDescription->Targets[2] == DestinationExit, + "clone must preserve jump-table order while remapping block targets"); + 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]->description() == ExpectedMMO && - Destination.ownsMemOperand(*DestinationLoad->memOperands()[0]) && - !Source->ownsMemOperand(*DestinationLoad->memOperands()[0]), - "clone must deep-copy MMO ownership while remapping frame semantics"); + bool MMOsMatch = DestinationLoad->memOperands().size() == Fixture.LoadMMOs.size(); + for (std::size_t Index = 0; MMOsMatch && Index < DestinationLoad->memOperands().size(); ++Index) { + rcc::MachineMemOperandDesc ExpectedMMO = Fixture.LoadMMOs[Index]->description(); + if (const auto Frame = ExpectedMMO.Base.frameIndex(); + Frame && FramePosition != Clone->FrameIndices.end()) { + ExpectedMMO.Base = rcc::MemoryBase::frameIndex(FramePosition->second); + } + if (const auto Constant = ExpectedMMO.Base.constantPoolIndex(); + Constant && ConstantPosition != Clone->ConstantPoolIndices.end()) { + ExpectedMMO.Base = rcc::MemoryBase::constantPool(ConstantPosition->second); + } + if (const auto JumpTable = ExpectedMMO.Base.jumpTableIndex(); + JumpTable && JumpTablePosition != Clone->JumpTableIndices.end()) { + ExpectedMMO.Base = rcc::MemoryBase::jumpTable(JumpTablePosition->second); + } + + const rcc::MachineMemOperand *DestinationMMO = DestinationLoad->memOperands()[Index]; + MMOsMatch = + DestinationMMO != Fixture.LoadMMOs[Index] && DestinationMMO->description() == ExpectedMMO && + Destination.ownsMemOperand(*DestinationMMO) && !Source->ownsMemOperand(*DestinationMMO); + } + Context.expect(MMOsMatch, "clone must deep-copy MMO ownership while remapping every table base"); const rcc::StackObject *DestinationStackSlot = FramePosition == Clone->FrameIndices.end() diff --git a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp index ba5e3db..2b5c97d 100644 --- a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp +++ b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp @@ -5,8 +5,10 @@ #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineMemOperand.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" @@ -28,6 +30,8 @@ constexpr std::uint32_t LoadOpcode = 3; constexpr std::uint32_t NopOpcode = 4; constexpr std::uint32_t BranchOpcode = 5; constexpr std::uint32_t DynamicImplicitOpcode = 6; +constexpr std::uint32_t ConstantPoolOpcode = 7; +constexpr std::uint32_t JumpTableOpcode = 8; class TestContext final { public: @@ -103,6 +107,16 @@ private: auto Dynamic = instructionDescriptor(DynamicImplicitOpcode, "TEST_DYNAMIC_IMPLICIT"); Dynamic.AllowExtraImplicitOperands = true; + auto ConstantPool = instructionDescriptor(ConstantPoolOpcode, "TEST_CONSTANT_POOL"); + rcc::OperandDescriptor ConstantPoolIndex; + ConstantPoolIndex.Kinds = {rcc::MachineOperand::Kind::ConstantPoolIndex}; + ConstantPool.Operands.push_back(ConstantPoolIndex); + + auto JumpTable = instructionDescriptor(JumpTableOpcode, "TEST_JUMP_TABLE"); + rcc::OperandDescriptor JumpTableIndex; + JumpTableIndex.Kinds = {rcc::MachineOperand::Kind::JumpTableIndex}; + JumpTable.Operands.push_back(JumpTableIndex); + std::vector<rcc::InstructionDescriptor> Descriptors; Descriptors.push_back(std::move(Copy)); Descriptors.push_back(std::move(Add)); @@ -110,6 +124,8 @@ private: Descriptors.push_back(std::move(Nop)); Descriptors.push_back(std::move(Branch)); Descriptors.push_back(std::move(Dynamic)); + Descriptors.push_back(std::move(ConstantPool)); + Descriptors.push_back(std::move(JumpTable)); return rcc::InstructionSet::create(std::move(Descriptors)); } @@ -375,6 +391,53 @@ void testImplicitAndMemoryValidation(TestContext &Context) { if (!LoadMemory || !StoreMemory || !ForeignMemory) return; + const std::size_t BeforeUnknownTableReferences = Function->allInstructions().size(); + auto UnknownConstant = Builder.buildInstr(rcc::Opcode::fromValue(ConstantPoolOpcode)); + auto UnknownJumpTable = Builder.buildInstr(rcc::Opcode::fromValue(JumpTableOpcode)); + Context.expect( + UnknownConstant && + UnknownConstant->addConstantPool(rcc::ConstantPoolIndex::fromValue(99)).has_value() && + !UnknownConstant->build() && UnknownJumpTable && + UnknownJumpTable->addJumpTable(rcc::JumpTableIndex::fromValue(99)).has_value() && + !UnknownJumpTable->build() && + Function->allInstructions().size() == BeforeUnknownTableReferences, + "builder must reject unknown constant-pool and jump-table indices before allocation"); + + rcc::MachineConstantPoolEntryDesc ConstantDescription; + ConstantDescription.Value = + rcc::GenericConstantData{"i64", std::vector<std::byte>(8, std::byte{0x2a})}; + ConstantDescription.Size = 8; + ConstantDescription.Align = *rcc::Alignment::create(8); + auto ConstantIndex = Function->constantPool().addEntry(std::move(ConstantDescription)); + rcc::MachineJumpTableDesc JumpDescription; + JumpDescription.Targets = {Block}; + auto JumpIndex = Function->jumpTableInfo().createTable(std::move(JumpDescription)); + Context.expect(ConstantIndex && JumpIndex, "builder table references must have owned entries"); + if (!ConstantIndex || !JumpIndex) + return; + + auto ConstantInstruction = Builder.buildInstr(rcc::Opcode::fromValue(ConstantPoolOpcode)); + auto JumpInstruction = Builder.buildInstr(rcc::Opcode::fromValue(JumpTableOpcode)); + Context.expect( + ConstantInstruction && ConstantInstruction->addConstantPool(*ConstantIndex, 4).has_value() && + ConstantInstruction->build() && JumpInstruction && + JumpInstruction->addJumpTable(*JumpIndex).has_value() && JumpInstruction->build(), + "builder must publish valid owned table references"); + + auto ConstantMemoryDescription = memoryDescription(rcc::MemoryAccessFlag::Load); + ConstantMemoryDescription.Base = rcc::MemoryBase::constantPool(*ConstantIndex); + auto JumpMemoryDescription = memoryDescription(rcc::MemoryAccessFlag::Load); + JumpMemoryDescription.Base = rcc::MemoryBase::jumpTable(*JumpIndex); + auto ConstantMemory = Function->createMemOperand(std::move(ConstantMemoryDescription)); + auto JumpMemory = Function->createMemOperand(std::move(JumpMemoryDescription)); + auto ConstantLoad = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); + auto JumpLoad = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); + Context.expect(ConstantMemory && JumpMemory && ConstantLoad && + ConstantLoad->addMemOperand(**ConstantMemory).has_value() && + ConstantLoad->build() && JumpLoad && + JumpLoad->addMemOperand(**JumpMemory).has_value() && JumpLoad->build(), + "builder must validate constant-pool and jump-table MMO bases"); + auto Load = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); Context.expect(Load && Load->addMemOperand(**LoadMemory).has_value(), "function-owned load MMO must be accepted"); diff --git a/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp b/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp new file mode 100644 index 0000000..0273801 --- /dev/null +++ b/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineJumpTableInfo.h" + +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" + +#include <cstdint> +#include <iostream> +#include <memory> +#include <string> +#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]] std::unique_ptr<rcc::MachineFunction> +createFunction(TestContext &Context, std::string Name, std::uint64_t ID) { + auto Function = + rcc::MachineFunction::create(std::move(Name), rcc::MachineFunctionStableID::fromValue(ID)); + Context.expect(Function.has_value(), "machine function creation must succeed"); + if (!Function) + return nullptr; + return std::move(*Function); +} + +[[nodiscard]] rcc::MachineBasicBlock *createBlock(TestContext &Context, + rcc::MachineFunction &Function) { + auto Block = Function.createBasicBlock(); + Context.expect(Block.has_value() && Function.appendBlock(**Block).has_value(), + "jump table test block creation must succeed"); + return Block ? *Block : nullptr; +} + +[[nodiscard]] rcc::MachineJumpTableDesc tableDescription(rcc::MachineBasicBlock &First, + rcc::MachineBasicBlock &Second) { + rcc::MachineJumpTableDesc Description; + Description.Encoding = rcc::JumpTableEntryEncoding::RelativeToTableBase; + Description.EntrySize = 4; + Description.Align = *rcc::Alignment::create(4); + Description.Targets = {&First, &Second, &Second}; + Description.Placement.MaximumDistance = 4096; + return Description; +} + +void testTableLifecycle(TestContext &Context) { + auto Function = createFunction(Context, "jump_tables", 1); + if (!Function) + return; + rcc::MachineBasicBlock *Entry = createBlock(Context, *Function); + rcc::MachineBasicBlock *Exit = createBlock(Context, *Function); + if (Entry == nullptr || Exit == nullptr) + return; + + auto First = Function->jumpTableInfo().createTable(tableDescription(*Entry, *Exit)); + auto TargetDescription = tableDescription(*Exit, *Entry); + TargetDescription.Encoding = rcc::JumpTableEntryEncoding::Target; + TargetDescription.TargetEncoding = 19; + TargetDescription.Placement.TargetFlags = 7; + auto Target = Function->jumpTableInfo().createTable(std::move(TargetDescription)); + Context.expect(First && Target && First->value() == 0 && Target->value() == 1 && + Function->jumpTableInfo().liveTableCount() == 2, + "valid jump tables must receive monotonic indices"); + if (!First || !Target) + return; + + const rcc::MachineJumpTableEntry *FirstEntry = Function->jumpTableInfo().table(*First); + Context.expect(FirstEntry != nullptr && FirstEntry->description()->Targets.size() == 3 && + FirstEntry->description()->Targets[1] == Exit && + FirstEntry->description()->Targets[2] == Exit, + "jump table target order and duplicate cases must be preserved"); + + Context.expect(Function->jumpTableInfo().eraseTable(*First).has_value() && + Function->jumpTableInfo().table(*First) == nullptr, + "erasing a jump table must leave a tombstone"); + auto CompressedDescription = tableDescription(*Entry, *Exit); + CompressedDescription.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset; + CompressedDescription.EntrySize = 2; + auto Compressed = Function->jumpTableInfo().createTable(std::move(CompressedDescription)); + Context.expect(Compressed && Compressed->value() == 2, + "jump table tombstone indices must never be reused"); + + auto Updated = tableDescription(*Entry, *Exit); + Updated.Encoding = rcc::JumpTableEntryEncoding::RelativeToEntry; + Context.expect(Function->jumpTableInfo().updateTable(*Target, std::move(Updated)).has_value() && + Function->jumpTableInfo().verify().has_value() && + Function->jumpTableInfo().freeze().has_value() && + Function->jumpTableInfo().isFrozen(), + "valid jump table updates must verify and freeze"); + Context.expect( + !Function->jumpTableInfo().eraseTable(*Target) && + !Function->jumpTableInfo().updateTable(*Target, tableDescription(*Entry, *Exit)) && + !Function->jumpTableInfo().createTable(tableDescription(*Entry, *Exit)) && + !Function->jumpTableInfo().freeze(), + "frozen jump table info must reject every mutation"); +} + +void testInvalidTables(TestContext &Context) { + auto Function = createFunction(Context, "invalid_tables", 2); + auto Foreign = createFunction(Context, "foreign_tables", 3); + if (!Function || !Foreign) + return; + rcc::MachineBasicBlock *Block = createBlock(Context, *Function); + rcc::MachineBasicBlock *ForeignBlock = createBlock(Context, *Foreign); + if (Block == nullptr || ForeignBlock == nullptr) + return; + + rcc::MachineJumpTableDesc Empty; + Context.expect(!Function->jumpTableInfo().createTable(std::move(Empty)), + "jump table must reject an empty target list"); + + auto ForeignTarget = tableDescription(*Block, *ForeignBlock); + Context.expect(!Function->jumpTableInfo().createTable(std::move(ForeignTarget)), + "jump table must reject a foreign target block"); + + auto InvalidSize = tableDescription(*Block, *Block); + InvalidSize.EntrySize = 3; + Context.expect(!Function->jumpTableInfo().createTable(std::move(InvalidSize)), + "jump table entry size must be a bounded power of two"); + + auto MissingTargetEncoding = tableDescription(*Block, *Block); + MissingTargetEncoding.Encoding = rcc::JumpTableEntryEncoding::Target; + Context.expect(!Function->jumpTableInfo().createTable(std::move(MissingTargetEncoding)), + "target jump table must carry a target encoding ID"); + + auto UnexpectedTargetEncoding = tableDescription(*Block, *Block); + UnexpectedTargetEncoding.TargetEncoding = 1; + Context.expect(!Function->jumpTableInfo().createTable(std::move(UnexpectedTargetEncoding)), + "generic jump table encoding must reject target encoding IDs"); + + auto OversizedCompressed = tableDescription(*Block, *Block); + OversizedCompressed.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset; + OversizedCompressed.EntrySize = 8; + Context.expect(!Function->jumpTableInfo().createTable(std::move(OversizedCompressed)), + "compressed jump table must reject oversized entries"); + + auto ZeroDistance = tableDescription(*Block, *Block); + ZeroDistance.Placement.MaximumDistance = 0; + Context.expect(!Function->jumpTableInfo().createTable(std::move(ZeroDistance)), + "jump table maximum distance must be nonzero"); + + auto LiveTable = Function->jumpTableInfo().createTable(tableDescription(*Block, *Block)); + Context.expect(LiveTable && Function->removeBlock(*Block).has_value() && + !Function->jumpTableInfo().verify(), + "jump table verification must detect a detached target"); +} + +} // namespace + +int main() { + TestContext Context; + testTableLifecycle(Context); + testInvalidTables(Context); + return Context.result(); +} diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp index 82ae68b..17c4eb4 100644 --- a/backend/unittests/CodeGen/MachineVerifierTest.cpp +++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp @@ -6,10 +6,12 @@ #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineIRBuilder.h" #include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" @@ -32,6 +34,8 @@ constexpr std::uint32_t PhiOpcode = 5; constexpr std::uint32_t FrameOpcode = 6; constexpr std::uint32_t PseudoOpcode = 7; constexpr std::uint32_t NopOpcode = 8; +constexpr std::uint32_t ConstantPoolOpcode = 9; +constexpr std::uint32_t JumpTableOpcode = 10; class TestContext final { public: @@ -114,6 +118,16 @@ private: auto Nop = instructionDescriptor(NopOpcode, "TEST_NOP"); + auto ConstantPool = instructionDescriptor(ConstantPoolOpcode, "TEST_CONSTANT_POOL"); + rcc::OperandDescriptor ConstantPoolIndex; + ConstantPoolIndex.Kinds = {rcc::MachineOperand::Kind::ConstantPoolIndex}; + ConstantPool.Operands.push_back(ConstantPoolIndex); + + auto JumpTable = instructionDescriptor(JumpTableOpcode, "TEST_JUMP_TABLE"); + rcc::OperandDescriptor JumpTableIndex; + JumpTableIndex.Kinds = {rcc::MachineOperand::Kind::JumpTableIndex}; + JumpTable.Operands.push_back(JumpTableIndex); + std::vector<rcc::InstructionDescriptor> Descriptors; Descriptors.push_back(std::move(GenericDef)); Descriptors.push_back(std::move(GenericUse)); @@ -123,6 +137,8 @@ private: Descriptors.push_back(std::move(Frame)); Descriptors.push_back(std::move(Pseudo)); Descriptors.push_back(std::move(Nop)); + Descriptors.push_back(std::move(ConstantPool)); + Descriptors.push_back(std::move(JumpTable)); return rcc::InstructionSet::create(std::move(Descriptors)); } @@ -493,6 +509,53 @@ void testFrameStateFailures(TestContext &Context) { "verifier must reject stack lifetimes that reference unknown instructions"); } +void testTableReferenceFailures(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto ConstantFunction = createFunction(Context, "constant_reference", 16); + rcc::MachineBasicBlock *ConstantBlock = + ConstantFunction ? createBlock(Context, *ConstantFunction) : nullptr; + if (!InstructionSet || !ConstantFunction || ConstantBlock == nullptr) + return; + + rcc::MachineConstantPoolEntryDesc ConstantDescription; + ConstantDescription.Value = + rcc::GenericConstantData{"i32", std::vector<std::byte>(4, std::byte{1})}; + ConstantDescription.Size = 4; + ConstantDescription.Align = *rcc::Alignment::create(4); + auto ConstantIndex = ConstantFunction->constantPool().addEntry(std::move(ConstantDescription)); + rcc::MachineIRBuilder ConstantBuilder(*ConstantFunction, *InstructionSet); + Context.expect(ConstantIndex && ConstantBuilder.setInsertPoint(*ConstantBlock).has_value(), + "constant-pool verifier setup must succeed"); + if (!ConstantIndex) + return; + auto ConstantInstruction = ConstantBuilder.buildInstr(rcc::Opcode::fromValue(ConstantPoolOpcode)); + Context.expect(ConstantInstruction && + ConstantInstruction->addConstantPool(*ConstantIndex).has_value() && + ConstantInstruction->build() && + ConstantFunction->constantPool().eraseEntry(*ConstantIndex).has_value() && + !rcc::MachineVerifier::verify(*ConstantFunction, *InstructionSet), + "verifier must reject a tombstoned constant-pool reference"); + + auto JumpFunction = createFunction(Context, "jump_reference", 17); + rcc::MachineBasicBlock *JumpBlock = JumpFunction ? createBlock(Context, *JumpFunction) : nullptr; + if (!JumpFunction || JumpBlock == nullptr) + return; + rcc::MachineJumpTableDesc JumpDescription; + JumpDescription.Targets = {JumpBlock}; + auto JumpIndex = JumpFunction->jumpTableInfo().createTable(std::move(JumpDescription)); + rcc::MachineIRBuilder JumpBuilder(*JumpFunction, *InstructionSet); + Context.expect(JumpIndex && JumpBuilder.setInsertPoint(*JumpBlock).has_value(), + "jump-table verifier setup must succeed"); + if (!JumpIndex) + return; + auto JumpInstruction = JumpBuilder.buildInstr(rcc::Opcode::fromValue(JumpTableOpcode)); + Context.expect(JumpInstruction && JumpInstruction->addJumpTable(*JumpIndex).has_value() && + JumpInstruction->build() && + JumpFunction->jumpTableInfo().eraseTable(*JumpIndex).has_value() && + !rcc::MachineVerifier::verify(*JumpFunction, *InstructionSet), + "verifier must reject a tombstoned jump-table reference"); +} + } // namespace int main() { @@ -502,5 +565,6 @@ int main() { testPropertyFailures(Context); testSelectedStateAndPseudoDeadline(Context); testFrameStateFailures(Context); + testTableReferenceFailures(Context); return Context.result(); } diff --git a/backend/unittests/CodeGen/TargetConstantTest.cpp b/backend/unittests/CodeGen/TargetConstantTest.cpp new file mode 100644 index 0000000..77338b7 --- /dev/null +++ b/backend/unittests/CodeGen/TargetConstantTest.cpp @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/TargetConstant.h" + +#include <bit> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +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; +}; + +struct TestRelocConstant final { + std::int64_t Addend = 0; + std::string Symbol; + + [[nodiscard]] static constexpr rcc::TargetConstantTypeID stableTypeID() noexcept { + return rcc::TargetConstantTypeID::fromValue(0x5253434354430001ULL); + } + + [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept { + return 2; + } + + [[nodiscard]] rcc::Expected<void> verify() const { + if (Symbol.empty()) { + return rcc::makeUnexpected(rcc::ErrorCode::VerificationError, + "test target constant symbol cannot be empty"); + } + return {}; + } + + [[nodiscard]] rcc::Expected<TestRelocConstant> clone() const { + return TestRelocConstant{Addend, Symbol}; + } + + [[nodiscard]] std::uint64_t stableHash() const noexcept { + std::uint64_t Hash = 1469598103934665603ULL; + for (const char RawCharacter : Symbol) { + const auto Character = static_cast<unsigned char>(RawCharacter); + Hash ^= Character; + Hash *= 1099511628211ULL; + } + Hash ^= static_cast<std::uint64_t>(Addend); + return Hash * 1099511628211ULL; + } + + [[nodiscard]] std::string print() const { + return Symbol + "+" + std::to_string(Addend); + } + + [[nodiscard]] std::vector<std::byte> addendBytes() const { + std::vector<std::byte> Bytes(8); + const std::uint64_t Bits = static_cast<std::uint64_t>(Addend); + for (std::size_t Index = 0; Index < Bytes.size(); ++Index) + Bytes[Index] = static_cast<std::byte>((Bits >> (Index * 8U)) & 0xffU); + return Bytes; + } + + [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const { + std::vector<std::byte> Payload = addendBytes(); + for (const char RawCharacter : Symbol) { + const auto Character = static_cast<unsigned char>(RawCharacter); + Payload.push_back(static_cast<std::byte>(Character)); + } + return Payload; + } + + [[nodiscard]] rcc::Expected<rcc::TargetConstantEmission> emit() const { + rcc::TargetConstantEmission Emission; + Emission.Bytes = addendBytes(); + Emission.Fixups.push_back({0, rcc::TargetRelocationKindID::fromValue(9), Symbol, Addend}); + return Emission; + } + + [[nodiscard]] rcc::Alignment requiredAlignment() const noexcept { + return *rcc::Alignment::create(8); + } + + [[nodiscard]] std::optional<rcc::TargetRelocationKindID> relocationKind() const noexcept { + return rcc::TargetRelocationKindID::fromValue(9); + } + + [[nodiscard]] static rcc::Expected<TestRelocConstant> parse(std::string_view Text) { + if (Text.empty()) + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "test constant text is empty"); + return TestRelocConstant{7, std::string(Text)}; + } + + [[nodiscard]] static rcc::Expected<TestRelocConstant> decode(std::uint32_t Version, + std::span<const std::byte> Payload) { + if (Version != schemaVersion()) + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, + "unsupported test constant schema version"); + if (Payload.size() <= 8) + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, "test constant payload is incomplete"); + + std::uint64_t Bits = 0; + for (std::size_t Index = 0; Index < 8; ++Index) + Bits |= std::to_integer<std::uint64_t>(Payload[Index]) << (Index * 8U); + std::string Symbol; + Symbol.reserve(Payload.size() - 8); + for (const std::byte Character : Payload.subspan(8)) + Symbol.push_back(static_cast<char>(std::to_integer<unsigned char>(Character))); + return TestRelocConstant{std::bit_cast<std::int64_t>(Bits), std::move(Symbol)}; + } + + friend bool operator==(const TestRelocConstant &Left, const TestRelocConstant &Right) = default; +}; + +struct CollidingConstant final { + TestRelocConstant Value; + + [[nodiscard]] static constexpr rcc::TargetConstantTypeID stableTypeID() noexcept { + return TestRelocConstant::stableTypeID(); + } + + [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept { + return TestRelocConstant::schemaVersion(); + } + + [[nodiscard]] rcc::Expected<void> verify() const { + return Value.verify(); + } + + [[nodiscard]] rcc::Expected<CollidingConstant> clone() const { + return CollidingConstant{Value}; + } + + [[nodiscard]] std::uint64_t stableHash() const noexcept { + return Value.stableHash(); + } + + [[nodiscard]] std::string print() const { + return Value.print(); + } + + [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const { + return Value.encode(); + } + + [[nodiscard]] rcc::Expected<rcc::TargetConstantEmission> emit() const { + return Value.emit(); + } + + [[nodiscard]] rcc::Alignment requiredAlignment() const noexcept { + return Value.requiredAlignment(); + } + + [[nodiscard]] std::optional<rcc::TargetRelocationKindID> relocationKind() const noexcept { + return Value.relocationKind(); + } + + [[nodiscard]] static rcc::Expected<CollidingConstant> parse(std::string_view Text) { + auto Parsed = TestRelocConstant::parse(Text); + if (!Parsed) + return std::unexpected(Parsed.error()); + return CollidingConstant{std::move(*Parsed)}; + } + + [[nodiscard]] static rcc::Expected<CollidingConstant> decode(std::uint32_t Version, + std::span<const std::byte> Payload) { + auto Decoded = TestRelocConstant::decode(Version, Payload); + if (!Decoded) + return std::unexpected(Decoded.error()); + return CollidingConstant{std::move(*Decoded)}; + } + + friend bool operator==(const CollidingConstant &Left, const CollidingConstant &Right) = default; +}; + +void testTargetConstantContract(TestContext &Context) { + auto Constant = rcc::TargetConstant::create(TestRelocConstant{11, "symbol"}); + Context.expect(Constant.has_value(), "valid target constant must be type-erased"); + if (!Constant) + return; + + Context.expect(Constant->typeID() == TestRelocConstant::stableTypeID() && + Constant->schemaVersion() == TestRelocConstant::schemaVersion() && + Constant->getIf<TestRelocConstant>() != nullptr && + Constant->getIf<CollidingConstant>() == nullptr, + "strong query must require both stable and local type identity"); + auto Printed = Constant->print(); + auto RequiredAlignment = Constant->requiredAlignment(); + auto RelocationKind = Constant->relocationKind(); + Context.expect(Constant->verify().has_value() && Printed && *Printed == "symbol+11" && + RequiredAlignment && *RequiredAlignment == *rcc::Alignment::create(8) && + RelocationKind && *RelocationKind == rcc::TargetRelocationKindID::fromValue(9), + "target constant semantic hooks must remain available through erasure"); + + auto Cloned = Constant->clone(); + Context.expect(Cloned && Constant->equals(*Cloned) && + Cloned->getIf<TestRelocConstant>() != Constant->getIf<TestRelocConstant>(), + "target constant clone must own an equal independent value"); + + auto Encoded = Constant->encode(); + if (!Encoded) + return; + auto Decoded = rcc::TargetConstant::decode<TestRelocConstant>(*Encoded); + Context.expect(Encoded && Decoded && Constant->equals(*Decoded), + "versioned target constant encoding must round-trip losslessly"); + + auto Parsed = rcc::TargetConstant::parse<TestRelocConstant>("parsed"); + const TestRelocConstant *ParsedValue = Parsed ? Parsed->getIf<TestRelocConstant>() : nullptr; + Context.expect(ParsedValue != nullptr && ParsedValue->Addend == 7 && + ParsedValue->Symbol == "parsed", + "target constant text parser must construct the same strong type"); + + auto Emission = Constant->emit(); + Context.expect(Emission && Emission->Bytes.size() == 8 && Emission->Fixups.size() == 1 && + Emission->Fixups[0].Symbol == "symbol" && + Emission->Fixups[0].Kind == rcc::TargetRelocationKindID::fromValue(9), + "target constant emission must preserve bytes and structured fixups"); +} + +void testTargetConstantFailures(TestContext &Context) { + rcc::TargetConstant Empty; + Context.expect(!Empty.verify() && !Empty.clone() && !Empty.encode() && !Empty.emit(), + "empty target constant must reject every semantic operation"); + Context.expect(!rcc::TargetConstant::create(TestRelocConstant{0, ""}), + "target constant creation must verify its value"); + + auto Constant = rcc::TargetConstant::create(TestRelocConstant{3, "symbol"}); + if (!Constant) + return; + auto Encoded = Constant->encode(); + if (!Encoded) + return; + Encoded->Version = 99; + Context.expect(!rcc::TargetConstant::decode<TestRelocConstant>(*Encoded), + "target constant decode must reject unsupported versions"); + Encoded->Version = TestRelocConstant::schemaVersion(); + Encoded->TypeID = rcc::TargetConstantTypeID::fromValue(1); + Context.expect(!rcc::TargetConstant::decode<TestRelocConstant>(*Encoded), + "target constant decode must reject a mismatched TypeID"); + + rcc::TargetConstantEmission Invalid; + Invalid.Bytes.resize(4); + Invalid.Fixups.push_back({4, rcc::TargetRelocationKindID::fromValue(1), "symbol", 0}); + Context.expect(!Invalid.verify(), "target emission must reject out-of-range fixups"); + Invalid.Fixups[0].Offset = 0; + Invalid.Fixups[0].Kind = {}; + Context.expect(!Invalid.verify(), "target emission must reject invalid relocation kinds"); + Invalid.Fixups[0].Kind = rcc::TargetRelocationKindID::fromValue(1); + Invalid.Fixups.push_back(Invalid.Fixups[0]); + Context.expect(!Invalid.verify(), "target emission must reject duplicate fixup offsets"); +} + +} // namespace + +int main() { + TestContext Context; + testTargetConstantContract(Context); + testTargetConstantFailures(Context); + return Context.result(); +} |
