diff options
Diffstat (limited to 'backend/unittests/CodeGen/MachineFunctionClonerTest.cpp')
| -rw-r--r-- | backend/unittests/CodeGen/MachineFunctionClonerTest.cpp | 846 |
1 files changed, 846 insertions, 0 deletions
diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp new file mode 100644 index 0000000..82bcce1 --- /dev/null +++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineFunctionCloner.h" + +#include "Target/TestTarget.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineCompilationStatus.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 "rcc/CodeGen/TargetFunctionState.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <memory> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace { + +constexpr std::uint32_t NopOpcode = 1; +constexpr std::uint32_t DefOpcode = 2; +constexpr std::uint32_t BranchOpcode = 3; +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: + 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; +}; + +void appendLittleEndian(std::vector<std::byte> &Bytes, std::uint64_t Value, std::size_t Width) { + for (std::size_t Index = 0; Index < Width; ++Index) + Bytes.push_back(static_cast<std::byte>((Value >> (Index * 8U)) & 0xffU)); +} + +[[nodiscard]] std::uint64_t readLittleEndian(std::span<const std::byte> Bytes, std::size_t Offset, + std::size_t Width) { + std::uint64_t Value = 0; + for (std::size_t Index = 0; Index < Width; ++Index) { + Value |= std::to_integer<std::uint64_t>(Bytes[Offset + Index]) << (Index * 8U); + } + return Value; +} + +struct CloneTargetState final { + rcc::MachineBlockStableID Block; + rcc::MachineInstrStableID Instruction; + rcc::Register TrackedRegister; + rcc::FrameIndex Frame; + rcc::ConstantPoolIndex Constant; + rcc::JumpTableIndex JumpTable; + + [[nodiscard]] static constexpr rcc::TargetFunctionStateTypeID stableTypeID() noexcept { + return rcc::TargetFunctionStateTypeID::fromValue(0x524343434c4f4e45ULL); + } + + [[nodiscard]] static constexpr std::uint32_t schemaVersion() noexcept { + return 1; + } + + [[nodiscard]] rcc::Expected<void> verify() const { + if (!Block.isValid() || !Instruction.isValid() || TrackedRegister.isNoRegister()) { + return rcc::makeUnexpected(rcc::ErrorCode::VerificationError, + "clone target state has an invalid identity"); + } + return {}; + } + + [[nodiscard]] rcc::Expected<CloneTargetState> + clone(const rcc::MachineFunctionCloneContext &Context) const { + auto MappedBlock = Context.mapBlockStableID(Block); + if (!MappedBlock) + return std::unexpected(MappedBlock.error()); + auto MappedInstruction = Context.mapInstructionStableID(Instruction); + if (!MappedInstruction) + return std::unexpected(MappedInstruction.error()); + auto MappedRegister = Context.mapRegister(TrackedRegister); + if (!MappedRegister) + return std::unexpected(MappedRegister.error()); + auto MappedFrame = Context.mapFrameIndex(Frame); + if (!MappedFrame) + return std::unexpected(MappedFrame.error()); + auto MappedConstant = Context.mapConstantPoolIndex(Constant); + if (!MappedConstant) + return std::unexpected(MappedConstant.error()); + auto MappedJumpTable = Context.mapJumpTableIndex(JumpTable); + if (!MappedJumpTable) + return std::unexpected(MappedJumpTable.error()); + return CloneTargetState{*MappedBlock, *MappedInstruction, *MappedRegister, + *MappedFrame, *MappedConstant, *MappedJumpTable}; + } + + [[nodiscard]] std::string print() const { + return "clone-target-state"; + } + + [[nodiscard]] rcc::Expected<std::vector<std::byte>> encode() const { + std::vector<std::byte> Payload; + Payload.reserve(32); + appendLittleEndian(Payload, Block.value(), 8); + appendLittleEndian(Payload, Instruction.value(), 8); + appendLittleEndian(Payload, TrackedRegister.opaqueValue(), 4); + appendLittleEndian(Payload, Frame.value(), 4); + appendLittleEndian(Payload, Constant.value(), 4); + appendLittleEndian(Payload, JumpTable.value(), 4); + return Payload; + } + + [[nodiscard]] static rcc::Expected<CloneTargetState> decode(std::uint32_t Version, + std::span<const std::byte> Payload) { + if (Version != schemaVersion() || Payload.size() != 32) { + return rcc::makeUnexpected(rcc::ErrorCode::ParseError, + "clone target state encoding is invalid"); + } + return CloneTargetState{ + rcc::MachineBlockStableID::fromValue(readLittleEndian(Payload, 0, 8)), + rcc::MachineInstrStableID::fromValue(readLittleEndian(Payload, 8, 8)), + rcc::Register(static_cast<std::uint32_t>(readLittleEndian(Payload, 16, 4))), + rcc::FrameIndex::fromValue(static_cast<std::uint32_t>(readLittleEndian(Payload, 20, 4))), + rcc::ConstantPoolIndex::fromValue( + static_cast<std::uint32_t>(readLittleEndian(Payload, 24, 4))), + rcc::JumpTableIndex::fromValue( + static_cast<std::uint32_t>(readLittleEndian(Payload, 28, 4))), + }; + } + + friend bool operator==(const CloneTargetState &Left, const CloneTargetState &Right) = default; +}; + +[[nodiscard]] rcc::OperandDescriptor registerDescriptor(rcc::RegisterOperandRole Role) { + rcc::OperandDescriptor Descriptor; + Descriptor.Kinds = {rcc::MachineOperand::Kind::Register}; + Descriptor.RegisterRole = Role; + return Descriptor; +} + +[[nodiscard]] rcc::InstructionDescriptor instructionDescriptor(std::uint32_t OpcodeValue, + std::string Name) { + rcc::InstructionDescriptor Descriptor; + Descriptor.InstructionOpcode = rcc::Opcode::fromValue(OpcodeValue); + Descriptor.Name = std::move(Name); + Descriptor.Flags = {rcc::InstructionFlag::ProducesCode}; + return Descriptor; +} + +[[nodiscard]] rcc::Expected<rcc::TargetDescription> createTarget() { + auto Nop = instructionDescriptor(NopOpcode, "TEST_NOP"); + + auto Def = instructionDescriptor(DefOpcode, "TEST_DEF"); + Def.Operands.push_back(registerDescriptor(rcc::RegisterOperandRole::Def)); + Def.NumExplicitDefs = 1; + + auto Branch = instructionDescriptor(BranchOpcode, "TEST_BRANCH"); + rcc::OperandDescriptor Block; + Block.Kinds = {rcc::MachineOperand::Kind::BasicBlock}; + Branch.Operands.push_back(Block); + Branch.Flags.set(rcc::InstructionFlag::Branch); + Branch.Flags.set(rcc::InstructionFlag::Terminator); + + auto Copy = instructionDescriptor(CopyOpcode, "TEST_COPY"); + auto CopyDef = registerDescriptor(rcc::RegisterOperandRole::Def); + auto CopyUse = registerDescriptor(rcc::RegisterOperandRole::Use); + CopyDef.DefaultTiedOperand = 1; + CopyUse.DefaultTiedOperand = 0; + Copy.Operands.push_back(CopyDef); + Copy.Operands.push_back(CopyUse); + Copy.NumExplicitDefs = 1; + + auto Frame = instructionDescriptor(FrameOpcode, "TEST_FRAME"); + rcc::OperandDescriptor FrameIndex; + FrameIndex.Kinds = {rcc::MachineOperand::Kind::FrameIndex}; + Frame.Operands.push_back(FrameIndex); + + auto BlockAddress = instructionDescriptor(BlockAddressOpcode, "TEST_BLOCK_ADDRESS"); + rcc::OperandDescriptor Address; + Address.Kinds = {rcc::MachineOperand::Kind::BlockAddress}; + BlockAddress.Operands.push_back(Address); + + auto Load = instructionDescriptor(LoadOpcode, "TEST_LOAD"); + 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)); + Descriptors.push_back(std::move(Branch)); + Descriptors.push_back(std::move(Copy)); + 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)); + auto Instructions = rcc::InstructionSet::create(std::move(Descriptors)); + if (!Instructions) + return std::unexpected(Instructions.error()); + return rcc::test::createTestTargetDescription(std::move(*Instructions)); +} + +[[nodiscard]] std::unique_ptr<rcc::MachineFunction> +createFunction(TestContext &Context, std::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(), "machine basic block creation must succeed"); + if (!Block) + return nullptr; + Context.expect(Function.appendBlock(**Block).has_value(), + "machine basic block linking must succeed"); + return *Block; +} + +[[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.BaseAlign = *rcc::Alignment::create(16); + Description.Base = std::move(Base); + Description.Offset = -8; + Description.TBAA = rcc::MachineMetadataReference::embeddedIR(10); + Description.AliasScope = rcc::MachineMetadataReference::embeddedIR(11); + Description.NoAlias = rcc::MachineMetadataReference::embeddedIR(12); + Description.NoAliasAddressSpace = rcc::MachineMetadataReference::embeddedIR(13); + Description.RangeMetadata = rcc::MachineMetadataReference::embeddedIR(14); + Description.TargetFlags = 0x3; + return Description; +} + +[[nodiscard]] rcc::MachineInstr *findInstruction(rcc::MachineBasicBlock &Block, + std::uint32_t OpcodeValue) { + for (rcc::MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (Instruction->opcode() == rcc::Opcode::fromValue(OpcodeValue)) + return Instruction; + } + return nullptr; +} + +struct SourceFixture final { + rcc::MachineBasicBlock *Entry = nullptr; + rcc::MachineBasicBlock *Exit = nullptr; + rcc::Register Value; + rcc::Register CopyValue; + rcc::FrameIndex StackSlot; + rcc::ConstantPoolIndex Constant; + rcc::JumpTableIndex JumpTable; + std::vector<const rcc::MachineMemOperand *> LoadMMOs; +}; + +[[nodiscard]] bool populateSource(TestContext &Context, rcc::MachineFunction &Function, + const rcc::TargetDescription &Target, SourceFixture &Fixture) { + auto HistoricalBlock = Function.createBasicBlock(); + auto HistoricalInstruction = Function.createInstruction(rcc::Opcode::fromValue(NopOpcode)); + Context.expect(HistoricalBlock && HistoricalInstruction, "source arena history must be created"); + + auto Type = rcc::LowLevelType::makeCategoryNeutralScalar(64); + auto Tombstone = Function.registerInfo().createVirtualRegister(*Type); + 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) + return false; + + rcc::MachineConstantPoolEntryDesc HistoricalConstant; + auto HistoricalInteger = rcc::APInt::fromUnsigned(32, 0x01010101U); + HistoricalConstant.Value = + std::move(*rcc::GenericConstantData::integer(std::move(*HistoricalInteger))); + 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::GPRelative32BlockAddress; + 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"); + if (!Value || !CopyValue) + return false; + Fixture.Value = *Value; + Fixture.CopyValue = *CopyValue; + + const auto Bank = rcc::RegisterBankID::fromValue(1); + const auto RegClass = rcc::RegisterClassID::fromValue(2); + for (const rcc::Register Reg : {Fixture.Value, Fixture.CopyValue}) { + Context.expect(Function.registerInfo().setRegisterBank(Reg, Bank).has_value() && + Function.registerInfo().setRegisterClass(Reg, RegClass).has_value() && + Function.registerInfo().setTargetRegisterFlags(Reg, 0x3).has_value(), + "source vreg constraints must be recorded"); + } + + const auto LiveIn = *rcc::PhysicalRegister::create(1); + const auto Reserved = *rcc::PhysicalRegister::create(2); + const auto Hint = *rcc::PhysicalRegister::create(5); + Context.expect(Function.registerInfo().addAllocationHint(Fixture.Value, Hint).has_value() && + Function.registerInfo().addLiveIn(LiveIn, Fixture.Value).has_value() && + Function.registerInfo().reservePhysicalRegister(Reserved).has_value() && + Fixture.Entry->addLiveIn(LiveIn).has_value(), + "source register configuration must be recorded"); + + rcc::MachineIRBuilder Builder(Function, Target); + Context.expect(Builder.setInsertPoint(*Fixture.Entry).has_value(), + "source entry insertion point must be valid"); + auto Def = Builder.buildInstr(rcc::Opcode::fromValue(DefOpcode)); + Context.expect(Def && Def->addDef(Fixture.Value).has_value(), + "source definition must be assembled"); + if (!Def) + return false; + Context.expect(Def->setFlags({rcc::MachineInstrFlag::NoMerge}).has_value() && + Def->setSourceLocation({"source.mir", 9, 4, 2}).has_value() && + Def->setOrigin(rcc::OriginID::fromValue(77)).has_value() && Def->build(), + "source definition metadata must build"); + + auto Branch = Builder.buildInstr(rcc::Opcode::fromValue(BranchOpcode)); + Context.expect(Branch && Branch->addBasicBlock(*Fixture.Exit).has_value() && Branch->build(), + "source branch must build"); + Context.expect(Function.cfgUpdater() + .addSuccessor(*Fixture.Entry, *Fixture.Exit, rcc::BranchProbability::one()) + .has_value(), + "source CFG edge must build"); + + Context.expect(Builder.setInsertPoint(*Fixture.Exit).has_value(), + "source exit insertion point must be valid"); + auto Copy = Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)); + Context.expect(Copy && Copy->addDef(Fixture.CopyValue).has_value() && + Copy->addUse(Fixture.Value).has_value() && Copy->build(), + "source tied copy must build"); + + auto Frame = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); + 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"); + + 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)); + 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); + const rcc::StackObject *Object = Function.frameInfo().object(Fixture.StackSlot); + if (Definition == nullptr || LoadInstruction == nullptr || Object == nullptr) + return false; + Context.expect( + Definition->setPreInstrSymbol("before").has_value() && + Definition->setPostInstrSymbol("after").has_value() && + Definition->setHeapAllocMarker(rcc::MachineMetadataReference::embeddedIR(20)) + .has_value() && + Definition->setPCSections(rcc::MachineMetadataReference::embeddedIR(21)).has_value() && + Definition->setMMRA(rcc::MachineMetadataReference::embeddedIR(22)).has_value() && + Definition->setCFIType(23).has_value() && + Definition->setDeactivationSymbol("guard").has_value(), + "source instruction attachments must be recorded"); + 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"); + + auto TargetState = rcc::TargetFunctionState::create( + CloneTargetState{Fixture.Entry->stableID(), LoadInstruction->stableID(), Fixture.Value, + Fixture.StackSlot, Fixture.Constant, Fixture.JumpTable}); + Context.expect(TargetState.has_value(), "source target function state must be created"); + if (!TargetState) + return false; + Context.expect(Function.setTargetState(std::move(*TargetState)).has_value(), + "source target function state must be installed"); + + rcc::MachineCompilationFailure Failure; + Failure.Diagnostic.Code = rcc::ErrorCode::Unsupported; + Failure.Diagnostic.Message = "test instruction selection failed"; + Failure.Diagnostic.Location = rcc::SourceLocation{"source.mir", 12, 5, 0}; + Failure.FailurePass = "test-instruction-select"; + Failure.FailureInstruction = LoadInstruction->stableID(); + Failure.FailureStage = rcc::PipelineStage::Selected; + Failure.FailureRule = "test-load-pattern"; + Failure.Target = "test64"; + Failure.Subtarget = "test64+feature"; + Context.expect(Function.compilationStatus() + .fail(rcc::MachineCompilationState::SelectionFailed, std::move(Failure)) + .has_value(), + "source structured compilation failure must be recorded"); + + Function.properties().set(rcc::MachineFunctionProperty::IsSSA); + Function.properties().set(rcc::MachineFunctionProperty::TracksLiveness); + Function.properties().set(rcc::MachineFunctionProperty::NoPHIs); + return true; +} + +void testDeepClone(TestContext &Context) { + auto Target = createTarget(); + auto Source = createFunction(Context, "source_function", 1); + SourceFixture Fixture; + if (!Target || !Source || !populateSource(Context, *Source, *Target, Fixture)) { + return; + } + Context.expect(rcc::MachineVerifier::verify(*Source, *Target).has_value(), + "source function must verify before cloning"); + + auto Clone = rcc::cloneMachineFunction( + *Source, *Target, + rcc::MachineFunctionCloneOptions{rcc::MachineFunctionStableID::fromValue(99), + std::string("cloned_function")}); + Context.expect(Clone.has_value(), "valid source function must deep-clone"); + if (!Clone) + return; + rcc::MachineFunction &Destination = *Clone->ClonedFunction; + + Context.expect(Destination.name() == "cloned_function" && + Destination.stableID() == rcc::MachineFunctionStableID::fromValue(99), + "clone must use its explicit function identity"); + Context.expect(Destination.properties() == Source->properties() && + rcc::MachineVerifier::verify(Destination, *Target).has_value() && + rcc::MachineVerifier::verify(*Source, *Target).has_value(), + "source and destination must independently preserve valid properties"); + Context.expect(Source->allBlocks().size() == 3 && Destination.allBlocks().size() == 2 && + Source->allInstructions().size() == 9 && + Destination.allInstructions().size() == 8 && + Source->frameInfo().objects().size() == 2 && + 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() == 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(); + rcc::MachineBasicBlock *DestinationExit = + DestinationEntry == nullptr ? nullptr : DestinationEntry->next(); + Context.expect(DestinationEntry != nullptr && DestinationExit != nullptr && + DestinationExit->next() == nullptr && DestinationEntry != Fixture.Entry && + DestinationExit != Fixture.Exit, + "clone blocks must be distinct and preserve layout"); + if (DestinationEntry == nullptr || DestinationExit == nullptr) + return; + + const auto EntryID = Clone->BlockStableIDs.find(Fixture.Entry->stableID()); + const auto ExitID = Clone->BlockStableIDs.find(Fixture.Exit->stableID()); + Context.expect(EntryID != Clone->BlockStableIDs.end() && ExitID != Clone->BlockStableIDs.end() && + EntryID->second == DestinationEntry->stableID() && + ExitID->second == DestinationExit->stableID(), + "block StableID mapping must identify cloned layout objects"); + Context.expect(DestinationEntry->successors().size() == 1 && + DestinationEntry->successors()[0].Block == DestinationExit && + DestinationExit->predecessors().size() == 1 && + DestinationExit->predecessors()[0] == DestinationEntry, + "clone must rebuild reciprocal CFG edges"); + Context.expect(std::ranges::equal(DestinationEntry->liveIns(), Fixture.Entry->liveIns()), + "clone must preserve block live-ins"); + + const auto ValuePosition = Clone->VirtualRegisters.find(Fixture.Value); + const auto CopyValuePosition = Clone->VirtualRegisters.find(Fixture.CopyValue); + Context.expect(ValuePosition != Clone->VirtualRegisters.end() && + CopyValuePosition != Clone->VirtualRegisters.end(), + "clone must map every live source vreg"); + if (ValuePosition == Clone->VirtualRegisters.end() || + CopyValuePosition == Clone->VirtualRegisters.end()) { + return; + } + const rcc::Register NewValue = ValuePosition->second; + const rcc::Register NewCopyValue = CopyValuePosition->second; + Context.expect(NewValue.virtualIndex() == 0 && NewCopyValue.virtualIndex() == 1, + "clone must deterministically compact source vreg tombstones"); + + auto SourceInfo = Source->registerInfo().virtualRegisterInfo(Fixture.Value); + auto DestinationInfo = Destination.registerInfo().virtualRegisterInfo(NewValue); + Context.expect(SourceInfo && DestinationInfo && + SourceInfo->get().Type == DestinationInfo->get().Type && + SourceInfo->get().Bank == DestinationInfo->get().Bank && + SourceInfo->get().RegClass == DestinationInfo->get().RegClass && + SourceInfo->get().AllocationHints == DestinationInfo->get().AllocationHints && + SourceInfo->get().TargetFlags == DestinationInfo->get().TargetFlags, + "clone must preserve vreg type and allocation constraints"); + Context.expect(Destination.registerInfo().liveIns().size() == 1 && + Destination.registerInfo().liveIns()[0].InitialVirtualRegister == NewValue && + std::ranges::equal(Destination.registerInfo().reservedRegisters(), + Source->registerInfo().reservedRegisters()), + "clone must remap function live-ins and preserve reserved registers"); + + rcc::MachineInstr *DestinationDef = findInstruction(*DestinationEntry, DefOpcode); + 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 && + DestinationConstantPool && DestinationJumpTable && DestinationAddress && + DestinationLoad, + "clone must preserve every live instruction"); + if (!DestinationDef || !DestinationBranch || !DestinationCopy || !DestinationFrame || + !DestinationConstantPool || !DestinationJumpTable || !DestinationAddress || + !DestinationLoad) { + return; + } + + Context.expect( + DestinationDef->operandAt(0)->registerValue() == NewValue && + DestinationDef->flags().has(rcc::MachineInstrFlag::NoMerge) && + DestinationDef->sourceLocation() == rcc::SourceLocation{"source.mir", 9, 4, 2} && + DestinationDef->origin() == rcc::OriginID::fromValue(77) && + DestinationDef->preInstrSymbol() == "before" && + DestinationDef->postInstrSymbol() == "after" && + DestinationDef->heapAllocMarker() == rcc::MachineMetadataReference::embeddedIR(20) && + DestinationDef->pcSections() == rcc::MachineMetadataReference::embeddedIR(21) && + DestinationDef->mmra() == rcc::MachineMetadataReference::embeddedIR(22) && + DestinationDef->cfiType() == 23 && DestinationDef->deactivationSymbol() == "guard", + "clone must remap register operands and preserve instruction metadata"); + Context.expect(DestinationBranch->operandAt(0)->getIf<rcc::BasicBlockOperand>()->Block == + DestinationExit, + "clone must remap basic-block operands"); + Context.expect(DestinationCopy->operandAt(0)->registerValue() == NewCopyValue && + DestinationCopy->operandAt(1)->registerValue() == NewValue && + DestinationCopy->operandAt(0)->tiedOperandIndex() == 1 && + DestinationCopy->operandAt(1)->tiedOperandIndex() == 0, + "clone must remap tied registers and rebuild ties"); + 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"); + + 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"); + + 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() + ? 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 + ? Clone->InstructionStableIDs.end() + : Clone->InstructionStableIDs.find(SourceDef->stableID()); + Context.expect(SourceDef != nullptr && InstructionID != Clone->InstructionStableIDs.end() && + InstructionID->second == DestinationDef->stableID(), + "instruction StableID mapping must identify cloned instructions"); + + rcc::MachineInstr *SourceLoad = findInstruction(*Fixture.Exit, LoadOpcode); + const auto LoadID = SourceLoad == nullptr + ? Clone->InstructionStableIDs.end() + : Clone->InstructionStableIDs.find(SourceLoad->stableID()); + const CloneTargetState *SourceTarget = Source->getTargetInfo<CloneTargetState>(); + const CloneTargetState *DestinationTarget = Destination.getTargetInfo<CloneTargetState>(); + Context.expect(SourceLoad != nullptr && LoadID != Clone->InstructionStableIDs.end() && + SourceTarget != nullptr && DestinationTarget != nullptr && + SourceTarget != DestinationTarget && EntryID != Clone->BlockStableIDs.end() && + FramePosition != Clone->FrameIndices.end() && + ConstantPosition != Clone->ConstantPoolIndices.end() && + JumpTablePosition != Clone->JumpTableIndices.end() && + DestinationTarget->Block == EntryID->second && + DestinationTarget->Instruction == LoadID->second && + DestinationTarget->TrackedRegister == NewValue && + DestinationTarget->Frame == FramePosition->second && + DestinationTarget->Constant == ConstantPosition->second && + DestinationTarget->JumpTable == JumpTablePosition->second, + "clone must strongly clone target state through every identity mapping"); + + const rcc::MachineCompilationFailure *SourceFailure = Source->compilationStatus().failure(); + const rcc::MachineCompilationFailure *DestinationFailure = + Destination.compilationStatus().failure(); + Context.expect(SourceFailure != nullptr && DestinationFailure != nullptr && + Destination.compilationStatus().state() == + rcc::MachineCompilationState::SelectionFailed && + DestinationFailure->FailureInstruction == DestinationLoad->stableID() && + DestinationFailure->Diagnostic == SourceFailure->Diagnostic && + DestinationFailure->FailurePass == SourceFailure->FailurePass && + DestinationFailure->FailureStage == SourceFailure->FailureStage && + DestinationFailure->FailureRule == SourceFailure->FailureRule && + DestinationFailure->Target == SourceFailure->Target && + DestinationFailure->Subtarget == SourceFailure->Subtarget, + "clone must preserve compilation failure details and remap its instruction"); +} + +void testCloneFailures(TestContext &Context) { + auto Target = createTarget(); + auto Source = createFunction(Context, "failure_source", 2); + if (!Target || !Source) + return; + + Context.expect( + !rcc::cloneMachineFunction(*Source, *Target, + rcc::MachineFunctionCloneOptions{ + rcc::MachineFunctionStableID::fromValue(0), std::nullopt}), + "clone must reject an invalid destination StableID"); + auto EmptyName = rcc::cloneMachineFunction( + *Source, *Target, + rcc::MachineFunctionCloneOptions{rcc::MachineFunctionStableID::fromValue(3), std::string()}); + Context.expect(EmptyName && EmptyName->ClonedFunction->name().empty(), + "clone must preserve an explicitly empty destination name"); + + auto Invalid = createFunction(Context, "invalid_source", 4); + rcc::MachineBasicBlock *Block = Invalid ? createBlock(Context, *Invalid) : nullptr; + if (Invalid && Block != nullptr) { + auto Unknown = Invalid->createInstruction(rcc::Opcode::fromValue(999)); + Context.expect(Unknown && Block->appendInstruction(**Unknown).has_value(), + "invalid clone source must contain an unknown opcode"); + Context.expect( + !rcc::cloneMachineFunction(*Invalid, *Target, + rcc::MachineFunctionCloneOptions{ + rcc::MachineFunctionStableID::fromValue(5), std::nullopt}), + "clone must verify and reject an invalid source function"); + } + + auto UnmappedState = createFunction(Context, "unmapped_target_state", 6); + auto Physical = rcc::Register::makePhysical(1); + if (UnmappedState && Physical) { + auto State = rcc::TargetFunctionState::create(CloneTargetState{ + rcc::MachineBlockStableID::fromValue(99), rcc::MachineInstrStableID::fromValue(99), + *Physical, rcc::FrameIndex::fromValue(0), rcc::ConstantPoolIndex::fromValue(0), + rcc::JumpTableIndex::fromValue(0)}); + Context.expect(State && UnmappedState->setTargetState(std::move(*State)).has_value(), + "unmapped target state negative test must be installed"); + Context.expect( + !rcc::cloneMachineFunction(*UnmappedState, *Target, + rcc::MachineFunctionCloneOptions{ + rcc::MachineFunctionStableID::fromValue(7), std::nullopt}), + "clone must reject target state that does not map its identities"); + } +} + +} // namespace + +int main() { + TestContext Context; + testDeepClone(Context); + testCloneFailures(Context); + return Context.result(); +} |
