From 8553df97bbc6154dae9d0ad861857e71a0efa420 Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Sat, 1 Aug 2026 13:09:30 +0800 Subject: [CodeGen] Add transactional MachineIR builder Validate insertion cursors, operand ownership, descriptor constraints, ties, implicit registers, and memory operands before allocating or publishing an instruction. Share instruction legality checks with the verifier path and preserve deterministic insertion order. --- .../include/rcc/CodeGen/InstructionDescriptor.h | 26 + backend/include/rcc/CodeGen/MachineIRBuilder.h | 161 ++++++ backend/include/rcc/CodeGen/MachineInstr.h | 4 +- backend/lib/CodeGen/CMakeLists.txt | 4 + backend/lib/CodeGen/InstructionDescriptor.cpp | 223 ++++++--- backend/lib/CodeGen/MachineIRBuilder.cpp | 537 +++++++++++++++++++++ backend/lib/CodeGen/MachineInstr.cpp | 8 +- backend/unittests/CodeGen/CMakeLists.txt | 15 + backend/unittests/CodeGen/MachineIRBuilderTest.cpp | 462 ++++++++++++++++++ 9 files changed, 1368 insertions(+), 72 deletions(-) create mode 100644 backend/include/rcc/CodeGen/MachineIRBuilder.h create mode 100644 backend/lib/CodeGen/MachineIRBuilder.cpp create mode 100644 backend/unittests/CodeGen/MachineIRBuilderTest.cpp (limited to 'backend') diff --git a/backend/include/rcc/CodeGen/InstructionDescriptor.h b/backend/include/rcc/CodeGen/InstructionDescriptor.h index c6049b9..94ea374 100644 --- a/backend/include/rcc/CodeGen/InstructionDescriptor.h +++ b/backend/include/rcc/CodeGen/InstructionDescriptor.h @@ -23,6 +23,7 @@ namespace rcc { class MachineInstr; +class MachineMemOperand; class MachineRegisterInfo; class OperandKindSet final { @@ -217,6 +218,13 @@ struct ImmediatePredicate final { std::function Predicate; }; +struct MachineOperandTie final { + std::size_t DefOperand; + std::size_t UseOperand; + + friend bool operator==(MachineOperandTie Left, MachineOperandTie Right) noexcept = default; +}; + class InstructionSet final { public: [[nodiscard]] static Expected @@ -232,6 +240,15 @@ public: [[nodiscard]] Expected validateExplicitOperands(Opcode InstructionOpcode, std::span Operands, const MachineRegisterInfo &RegInfo) const; + [[nodiscard]] Expected validateOperandTies(Opcode InstructionOpcode, + std::span ExplicitOperands, + std::span Ties) const; + [[nodiscard]] Expected + validateImplicitOperands(Opcode InstructionOpcode, + std::span ImplicitOperands) const; + [[nodiscard]] Expected + validateMemOperands(Opcode InstructionOpcode, + std::span MemOperands) const; [[nodiscard]] Expected validateInstruction(const MachineInstr &Instruction, const MachineRegisterInfo &RegInfo) const; @@ -248,6 +265,15 @@ private: [[nodiscard]] Expected validateExplicitOperands(const InstructionDescriptor &Descriptor, std::span Operands, const MachineRegisterInfo &RegInfo) const; + [[nodiscard]] Expected validateOperandTies(const InstructionDescriptor &Descriptor, + std::span ExplicitOperands, + std::span Ties) const; + [[nodiscard]] Expected + validateImplicitOperands(const InstructionDescriptor &Descriptor, + std::span ImplicitOperands) const; + [[nodiscard]] Expected + validateMemOperands(const InstructionDescriptor &Descriptor, + std::span MemOperands) const; [[nodiscard]] Expected validateOperand(const OperandDescriptor &Descriptor, const MachineOperand &Operand, const MachineRegisterInfo &RegInfo) const; diff --git a/backend/include/rcc/CodeGen/MachineIRBuilder.h b/backend/include/rcc/CodeGen/MachineIRBuilder.h new file mode 100644 index 0000000..02e47ff --- /dev/null +++ b/backend/include/rcc/CodeGen/MachineIRBuilder.h @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_MACHINEIRBUILDER_H +#define RCC_CODEGEN_MACHINEIRBUILDER_H + +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineIRTypes.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/Register.h" +#include "rcc/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +class MachineBasicBlock; +class MachineFunction; +class MachineInstr; +class MachineMemOperand; +class RegisterMask; + +class MachineInstrBuilder final { +public: + MachineInstrBuilder(const MachineInstrBuilder &) = delete; + MachineInstrBuilder &operator=(const MachineInstrBuilder &) = delete; + MachineInstrBuilder(MachineInstrBuilder &&Other) noexcept; + MachineInstrBuilder &operator=(MachineInstrBuilder &&Other) noexcept; + ~MachineInstrBuilder() = default; + + [[nodiscard]] Expected addOperand(MachineOperand &&Operand); + [[nodiscard]] Expected addImplicitOperand(MachineOperand &&Operand); + + [[nodiscard]] Expected addDef(Register Reg, RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addUse(Register Reg, RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addDefUse(Register Reg, RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addImplicitDef(Register Reg, + RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addImplicitUse(Register Reg, + RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, + std::uint32_t TargetFlags = 0); + + [[nodiscard]] Expected addImmediate(std::int64_t Value, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addFloatingImmediate(FloatingPointBits Bits, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addBasicBlock(MachineBasicBlock &Block, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addFrameIndex(FrameIndex Index, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addConstantPool(ConstantPoolIndex Index, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addJumpTable(JumpTableIndex Index, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addGlobalSymbol(std::string Name, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addExternalSymbol(std::string Name, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addBlockAddress(std::string FunctionName, std::string BlockName, + std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addRegisterMask(std::shared_ptr Mask, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addMetadata(std::string Marker, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addTargetIndex(TargetIndex Index, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected addMCSymbol(std::string Name, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + + [[nodiscard]] Expected tieOperands(std::size_t DefOperand, std::size_t UseOperand); + [[nodiscard]] Expected addMemOperand(const MachineMemOperand &MemOperand); + + [[nodiscard]] Expected setFlags(MachineInstrFlags NewFlags); + + [[nodiscard]] Expected setSourceLocation(SourceLocation NewLocation); + [[nodiscard]] Expected clearSourceLocation(); + + [[nodiscard]] Expected setOrigin(OriginID NewOrigin); + [[nodiscard]] Expected clearOrigin(); + + [[nodiscard]] Expected build(); + +private: + MachineInstrBuilder(MachineFunction &Function, const InstructionSet &Instructions, + const InstructionDescriptor &Descriptor, MachineBasicBlock &InsertBlock, + MachineInstr *InsertBefore) noexcept; + + [[nodiscard]] Expected ensurePending() const; + [[nodiscard]] Expected validateInsertionPoint() const; + [[nodiscard]] Expected validateOperandOwnership(const MachineOperand &Operand) const; + [[nodiscard]] std::vector collectOperandTies() const; + [[nodiscard]] Expected appendRegister(Register Reg, RegisterOperandFlags Flags, + SubRegIndex SubReg, std::uint32_t TargetFlags, + bool IsImplicit); + + MachineFunction *Function; + const InstructionSet *Instructions; + const InstructionDescriptor *Descriptor; + MachineBasicBlock *InsertBlock; + MachineInstr *InsertBefore; + std::vector ExplicitOperands; + std::vector ExtraImplicitOperands; + std::vector RequestedTies; + std::vector MemOperands; + MachineInstrFlags Flags; + std::optional Location; + std::optional Origin; + bool IsBuilt = false; + + friend class MachineIRBuilder; +}; + +class MachineIRBuilder final { +public: + MachineIRBuilder(MachineFunction &Function, const InstructionSet &Instructions) noexcept + : Function(&Function), Instructions(&Instructions) {} + MachineIRBuilder(const MachineIRBuilder &) = delete; + MachineIRBuilder &operator=(const MachineIRBuilder &) = delete; + MachineIRBuilder(MachineIRBuilder &&) = delete; + MachineIRBuilder &operator=(MachineIRBuilder &&) = delete; + + [[nodiscard]] MachineFunction &function() noexcept { + return *Function; + } + + [[nodiscard]] const MachineFunction &function() const noexcept { + return *Function; + } + + [[nodiscard]] Expected setInsertPoint(MachineBasicBlock &Block); + [[nodiscard]] Expected setInsertPointBefore(MachineInstr &Instruction); + [[nodiscard]] Expected setInsertPointAfter(MachineInstr &Instruction); + void clearInsertPoint() noexcept { + InsertBlock = nullptr; + InsertBefore = nullptr; + } + + [[nodiscard]] Expected buildInstr(Opcode InstructionOpcode) const; + +private: + [[nodiscard]] Expected validateBlock(const MachineBasicBlock &Block) const; + [[nodiscard]] Expected validateInstruction(const MachineInstr &Instruction) const; + + MachineFunction *Function; + const InstructionSet *Instructions; + MachineBasicBlock *InsertBlock = nullptr; + MachineInstr *InsertBefore = nullptr; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_MACHINEIRBUILDER_H diff --git a/backend/include/rcc/CodeGen/MachineInstr.h b/backend/include/rcc/CodeGen/MachineInstr.h index 376273f..bcae306 100644 --- a/backend/include/rcc/CodeGen/MachineInstr.h +++ b/backend/include/rcc/CodeGen/MachineInstr.h @@ -87,8 +87,8 @@ public: } void reserveOperands(std::size_t Capacity); - [[nodiscard]] Expected addOperand(MachineOperand Operand); - [[nodiscard]] Expected insertOperand(std::size_t Index, MachineOperand Operand); + [[nodiscard]] Expected addOperand(MachineOperand &&Operand); + [[nodiscard]] Expected insertOperand(std::size_t Index, MachineOperand &&Operand); [[nodiscard]] Expected eraseOperand(std::size_t Index); [[nodiscard]] Expected tieOperands(std::size_t DefIndex, std::size_t UseIndex); diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt index d700d54..deaacb1 100644 --- a/backend/lib/CodeGen/CMakeLists.txt +++ b/backend/lib/CodeGen/CMakeLists.txt @@ -8,6 +8,7 @@ set( MachineCFGUpdater.cpp MachineFunction.cpp MachineIRTypes.cpp + MachineIRBuilder.cpp MachineInstr.cpp MachineMemOperand.cpp MachineOperand.cpp @@ -24,6 +25,7 @@ set( "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineCFGUpdater.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFunction.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineIRTypes.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineIRBuilder.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineInstr.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineMemOperand.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineOperand.h" @@ -69,6 +71,7 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunction.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRTypes.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilder.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineInstr.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineMemOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineOperand.cpp" @@ -85,6 +88,7 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunction.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRTypes.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilder.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineInstr.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineMemOperand.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineOperand.cpp" diff --git a/backend/lib/CodeGen/InstructionDescriptor.cpp b/backend/lib/CodeGen/InstructionDescriptor.cpp index e5efad7..9f4a178 100644 --- a/backend/lib/CodeGen/InstructionDescriptor.cpp +++ b/backend/lib/CodeGen/InstructionDescriptor.cpp @@ -35,6 +35,14 @@ namespace { (Left == RegisterOperandRole::Use && Right == RegisterOperandRole::Def); } +[[nodiscard]] bool isPureDef(const MachineOperand &Operand) noexcept { + return Operand.isRegister() && Operand.isDef() && !Operand.isUse(); +} + +[[nodiscard]] bool isPureUse(const MachineOperand &Operand) noexcept { + return Operand.isRegister() && Operand.isUse() && !Operand.isDef(); +} + [[nodiscard]] Expected validateDescriptorStructure(const InstructionDescriptor &Descriptor) { if (!Descriptor.InstructionOpcode.isValid()) return makeUnexpected(ErrorCode::InvalidArgument, "instruction descriptor opcode is invalid"); @@ -320,6 +328,133 @@ Expected InstructionSet::validateExplicitOperands(Opcode InstructionOpcode return validateExplicitOperands(*Descriptor, Operands, RegInfo); } +Expected InstructionSet::validateOperandTies(const InstructionDescriptor &Descriptor, + std::span ExplicitOperands, + std::span Ties) const { + std::vector> Partners(ExplicitOperands.size()); + for (const MachineOperandTie Tie : Ties) { + if (Tie.DefOperand >= ExplicitOperands.size() || Tie.UseOperand >= ExplicitOperands.size() || + Tie.DefOperand == Tie.UseOperand || !isPureDef(ExplicitOperands[Tie.DefOperand]) || + !isPureUse(ExplicitOperands[Tie.UseOperand])) { + return makeUnexpected(ErrorCode::VerificationError, + "instruction tie must connect an in-range pure register def and use"); + } + if (Partners[Tie.DefOperand] || Partners[Tie.UseOperand]) { + return makeUnexpected(ErrorCode::VerificationError, + "instruction operand participates in duplicate or conflicting ties"); + } + Partners[Tie.DefOperand] = Tie.UseOperand; + Partners[Tie.UseOperand] = Tie.DefOperand; + } + + for (std::size_t Index = 0; Index < Descriptor.Operands.size(); ++Index) { + const OperandDescriptor &Operand = Descriptor.Operands[Index]; + if (Operand.DefaultTiedOperand && + (Index >= Partners.size() || Partners[Index] != Operand.DefaultTiedOperand)) { + return makeUnexpected(ErrorCode::VerificationError, + "required descriptor tie is not materialized"); + } + } + return {}; +} + +Expected InstructionSet::validateOperandTies(Opcode InstructionOpcode, + std::span ExplicitOperands, + std::span Ties) const { + const InstructionDescriptor *Descriptor = descriptor(InstructionOpcode); + if (Descriptor == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, "instruction opcode is not registered"); + } + return validateOperandTies(*Descriptor, ExplicitOperands, Ties); +} + +Expected InstructionSet::validateImplicitOperands( + const InstructionDescriptor &Descriptor, + std::span ImplicitOperands) const { + const std::size_t DefaultCount = Descriptor.ImplicitDefs.size() + Descriptor.ImplicitUses.size(); + if (ImplicitOperands.size() < DefaultCount || + (!Descriptor.AllowExtraImplicitOperands && ImplicitOperands.size() != DefaultCount)) { + return makeUnexpected(ErrorCode::VerificationError, + "instruction implicit operand count does not match its descriptor"); + } + + std::size_t Index = 0; + auto ValidateDefault = [&](const ImplicitRegisterDescriptor &Implicit, + bool IsDef) -> Expected { + const MachineOperand *Operand = ImplicitOperands[Index++]; + const Register *Reg = Operand == nullptr ? nullptr : Operand->getIf(); + if (Reg == nullptr || Reg->physicalRegister() != Implicit.Reg || Operand->isDef() != IsDef || + Operand->isUse() == IsDef || !Operand->isImplicit() || + Operand->registerFlags().has(RegisterOperandFlag::EarlyClobber) != Implicit.EarlyClobber || + Operand->registerFlags().has(RegisterOperandFlag::Renamable) != Implicit.Renamable) { + return makeUnexpected(ErrorCode::VerificationError, + "default implicit register operand does not match its descriptor"); + } + return {}; + }; + + for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitDefs) { + auto Validation = ValidateDefault(Implicit, true); + if (!Validation) + return std::unexpected(Validation.error()); + } + for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitUses) { + auto Validation = ValidateDefault(Implicit, false); + if (!Validation) + return std::unexpected(Validation.error()); + } + for (; Index < ImplicitOperands.size(); ++Index) { + const MachineOperand *Operand = ImplicitOperands[Index]; + if (Operand == nullptr || !Operand->isRegister() || !Operand->isImplicit()) { + return makeUnexpected(ErrorCode::VerificationError, + "dynamic implicit operand must be an implicit register"); + } + } + return {}; +} + +Expected InstructionSet::validateImplicitOperands( + Opcode InstructionOpcode, std::span ImplicitOperands) const { + const InstructionDescriptor *Descriptor = descriptor(InstructionOpcode); + if (Descriptor == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, "instruction opcode is not registered"); + } + return validateImplicitOperands(*Descriptor, ImplicitOperands); +} + +Expected +InstructionSet::validateMemOperands(const InstructionDescriptor &Descriptor, + std::span MemOperands) const { + const bool MayLoad = Descriptor.Flags.has(InstructionFlag::MayLoad); + const bool MayStore = Descriptor.Flags.has(InstructionFlag::MayStore); + if (Descriptor.RequiresMemoryOperands && MemOperands.empty()) { + return makeUnexpected(ErrorCode::VerificationError, + "memory instruction is missing MachineMemOperand"); + } + if (!MayLoad && !MayStore && !MemOperands.empty()) { + return makeUnexpected(ErrorCode::VerificationError, + "non-memory instruction carries MachineMemOperand"); + } + for (const MachineMemOperand *MemOperand : MemOperands) { + if (MemOperand == nullptr || (MemOperand->isLoad() && !MayLoad) || + (MemOperand->isStore() && !MayStore)) { + return makeUnexpected(ErrorCode::VerificationError, + "MachineMemOperand access conflicts with instruction descriptor"); + } + } + return {}; +} + +Expected +InstructionSet::validateMemOperands(Opcode InstructionOpcode, + std::span MemOperands) const { + const InstructionDescriptor *Descriptor = descriptor(InstructionOpcode); + if (Descriptor == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, "instruction opcode is not registered"); + } + return validateMemOperands(*Descriptor, MemOperands); +} + Expected InstructionSet::validateOperand(const OperandDescriptor &Descriptor, const MachineOperand &Operand, const MachineRegisterInfo &RegInfo) const { @@ -402,86 +537,42 @@ Expected InstructionSet::validateInstruction(const MachineInstr &Instructi if (!ExplicitValidation) return std::unexpected(ExplicitValidation.error()); + std::vector Ties; for (std::size_t Index = 0; Index < ExplicitCount; ++Index) { const MachineOperand &Operand = AllOperands[Index]; - const OperandDescriptor &OperandDesc = descriptorForOperand(*Descriptor, Index); - if (OperandDesc.DefaultTiedOperand && - Operand.tiedOperandIndex() != OperandDesc.DefaultTiedOperand) { - return makeUnexpected(ErrorCode::VerificationError, - "required descriptor tie is not materialized"); - } if (!Operand.tiedOperandIndex()) continue; const std::size_t OtherIndex = *Operand.tiedOperandIndex(); - if (OtherIndex >= ExplicitCount || AllOperands[OtherIndex].tiedOperandIndex() != Index || - !Operand.isRegister() || !AllOperands[OtherIndex].isRegister() || - Operand.isDef() == AllOperands[OtherIndex].isDef() || - Operand.isUse() == AllOperands[OtherIndex].isUse()) { + if (OtherIndex >= ExplicitCount || AllOperands[OtherIndex].tiedOperandIndex() != Index) { return makeUnexpected(ErrorCode::VerificationError, - "instruction operand tie is invalid or not reciprocal"); + "instruction operand tie is out of range or not reciprocal"); } + if (Index > OtherIndex) + continue; + if (isPureUse(Operand) && isPureDef(AllOperands[OtherIndex])) + Ties.push_back(MachineOperandTie{OtherIndex, Index}); + else + Ties.push_back(MachineOperandTie{Index, OtherIndex}); } - const std::size_t DefaultImplicitCount = - Descriptor->ImplicitDefs.size() + Descriptor->ImplicitUses.size(); - const std::size_t ActualImplicitCount = AllOperands.size() - ExplicitCount; - if (ActualImplicitCount < DefaultImplicitCount || - (!Descriptor->AllowExtraImplicitOperands && ActualImplicitCount != DefaultImplicitCount)) { - return makeUnexpected(ErrorCode::VerificationError, - "instruction implicit operand count does not match its descriptor"); - } - - std::size_t ImplicitIndex = ExplicitCount; - auto ValidateImplicit = [&](const ImplicitRegisterDescriptor &Implicit, - bool IsDef) -> Expected { - const MachineOperand &Operand = AllOperands[ImplicitIndex++]; - const auto Reg = Operand.registerValue(); - if (!Reg || Reg->physicalRegister() != Implicit.Reg || Operand.isDef() != IsDef || - Operand.isUse() == IsDef || !Operand.isImplicit() || - Operand.registerFlags().has(RegisterOperandFlag::EarlyClobber) != Implicit.EarlyClobber || - Operand.registerFlags().has(RegisterOperandFlag::Renamable) != Implicit.Renamable) { - return makeUnexpected(ErrorCode::VerificationError, - "default implicit register operand does not match its descriptor"); - } - return {}; - }; - - for (const ImplicitRegisterDescriptor &Implicit : Descriptor->ImplicitDefs) { - auto Validation = ValidateImplicit(Implicit, true); - if (!Validation) - return std::unexpected(Validation.error()); - } - for (const ImplicitRegisterDescriptor &Implicit : Descriptor->ImplicitUses) { - auto Validation = ValidateImplicit(Implicit, false); - if (!Validation) - return std::unexpected(Validation.error()); - } - for (; ImplicitIndex < AllOperands.size(); ++ImplicitIndex) { - if (!AllOperands[ImplicitIndex].isRegister() || !AllOperands[ImplicitIndex].isImplicit()) { - return makeUnexpected(ErrorCode::VerificationError, - "dynamic implicit operand must be an implicit register"); - } - } + auto TieValidation = validateOperandTies(*Descriptor, AllOperands.first(ExplicitCount), Ties); + if (!TieValidation) + return std::unexpected(TieValidation.error()); - const bool MayLoad = Descriptor->Flags.has(InstructionFlag::MayLoad); - const bool MayStore = Descriptor->Flags.has(InstructionFlag::MayStore); - if (Descriptor->RequiresMemoryOperands && Instruction.memOperands().empty()) { - return makeUnexpected(ErrorCode::VerificationError, - "memory instruction is missing MachineMemOperand"); - } - for (const MachineMemOperand *MemOperand : Instruction.memOperands()) { - if (MemOperand == nullptr || (MemOperand->isLoad() && !MayLoad) || - (MemOperand->isStore() && !MayStore)) { + std::vector ImplicitOperands; + ImplicitOperands.reserve(AllOperands.size() - ExplicitCount); + for (std::size_t Index = ExplicitCount; Index < AllOperands.size(); ++Index) { + if (AllOperands[Index].tiedOperandIndex()) { return makeUnexpected(ErrorCode::VerificationError, - "MachineMemOperand access conflicts with instruction descriptor"); + "implicit register operand cannot participate in a tie"); } - } - if (!MayLoad && !MayStore && !Instruction.memOperands().empty()) { - return makeUnexpected(ErrorCode::VerificationError, - "non-memory instruction carries MachineMemOperand"); + ImplicitOperands.push_back(&AllOperands[Index]); } - return {}; + auto ImplicitValidation = validateImplicitOperands(*Descriptor, ImplicitOperands); + if (!ImplicitValidation) + return std::unexpected(ImplicitValidation.error()); + return validateMemOperands(*Descriptor, Instruction.memOperands()); } Expected> diff --git a/backend/lib/CodeGen/MachineIRBuilder.cpp b/backend/lib/CodeGen/MachineIRBuilder.cpp new file mode 100644 index 0000000..8f80ad6 --- /dev/null +++ b/backend/lib/CodeGen/MachineIRBuilder.cpp @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineIRBuilder.h" + +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { + +MachineInstrBuilder::MachineInstrBuilder(MachineFunction &Function, + const InstructionSet &Instructions, + const InstructionDescriptor &Descriptor, + MachineBasicBlock &InsertBlock, + MachineInstr *InsertBefore) noexcept + : Function(&Function), Instructions(&Instructions), Descriptor(&Descriptor), + InsertBlock(&InsertBlock), InsertBefore(InsertBefore) {} + +MachineInstrBuilder::MachineInstrBuilder(MachineInstrBuilder &&Other) noexcept + : Function(Other.Function), Instructions(Other.Instructions), Descriptor(Other.Descriptor), + InsertBlock(Other.InsertBlock), InsertBefore(Other.InsertBefore), + ExplicitOperands(std::move(Other.ExplicitOperands)), + ExtraImplicitOperands(std::move(Other.ExtraImplicitOperands)), + RequestedTies(std::move(Other.RequestedTies)), MemOperands(std::move(Other.MemOperands)), + Flags(Other.Flags), Location(std::move(Other.Location)), Origin(Other.Origin), + IsBuilt(Other.IsBuilt) { + Other.Function = nullptr; + Other.Instructions = nullptr; + Other.Descriptor = nullptr; + Other.InsertBlock = nullptr; + Other.InsertBefore = nullptr; + Other.IsBuilt = true; +} + +MachineInstrBuilder &MachineInstrBuilder::operator=(MachineInstrBuilder &&Other) noexcept { + if (this == &Other) + return *this; + + Function = Other.Function; + Instructions = Other.Instructions; + Descriptor = Other.Descriptor; + InsertBlock = Other.InsertBlock; + InsertBefore = Other.InsertBefore; + ExplicitOperands = std::move(Other.ExplicitOperands); + ExtraImplicitOperands = std::move(Other.ExtraImplicitOperands); + RequestedTies = std::move(Other.RequestedTies); + MemOperands = std::move(Other.MemOperands); + Flags = Other.Flags; + Location = std::move(Other.Location); + Origin = Other.Origin; + IsBuilt = Other.IsBuilt; + + Other.Function = nullptr; + Other.Instructions = nullptr; + Other.Descriptor = nullptr; + Other.InsertBlock = nullptr; + Other.InsertBefore = nullptr; + Other.IsBuilt = true; + return *this; +} + +Expected MachineInstrBuilder::ensurePending() const { + if (IsBuilt) { + return makeUnexpected(ErrorCode::InvalidArgument, + "machine instruction builder was already consumed"); + } + return {}; +} + +Expected MachineInstrBuilder::addOperand(MachineOperand &&Operand) { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + if (Operand.parent() != nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder operand already belongs to an instruction"); + } + if (Operand.isImplicit()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "implicit register requires addImplicitOperand"); + } + ExplicitOperands.push_back(std::move(Operand)); + return {}; +} + +Expected MachineInstrBuilder::addImplicitOperand(MachineOperand &&Operand) { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + if (Operand.parent() != nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder operand already belongs to an instruction"); + } + if (!Operand.isRegister() || !Operand.isImplicit()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "dynamic implicit operand must be an implicit register"); + } + ExtraImplicitOperands.push_back(std::move(Operand)); + return {}; +} + +Expected MachineInstrBuilder::appendRegister(Register Reg, RegisterOperandFlags RegisterFlags, + SubRegIndex SubReg, std::uint32_t TargetFlags, + bool IsImplicit) { + auto Operand = MachineOperand::registerOperand(Reg, RegisterFlags, SubReg, TargetFlags); + if (!Operand) + return std::unexpected(Operand.error()); + if (IsImplicit) + return addImplicitOperand(std::move(*Operand)); + return addOperand(std::move(*Operand)); +} + +Expected MachineInstrBuilder::addDef(Register Reg, RegisterOperandFlags AdditionalFlags, + SubRegIndex SubReg, std::uint32_t TargetFlags) { + AdditionalFlags.set(RegisterOperandFlag::Define); + return appendRegister(Reg, AdditionalFlags, SubReg, TargetFlags, false); +} + +Expected MachineInstrBuilder::addUse(Register Reg, RegisterOperandFlags AdditionalFlags, + SubRegIndex SubReg, std::uint32_t TargetFlags) { + AdditionalFlags.set(RegisterOperandFlag::Use); + return appendRegister(Reg, AdditionalFlags, SubReg, TargetFlags, false); +} + +Expected MachineInstrBuilder::addDefUse(Register Reg, RegisterOperandFlags AdditionalFlags, + SubRegIndex SubReg, std::uint32_t TargetFlags) { + AdditionalFlags.set(RegisterOperandFlag::Define); + AdditionalFlags.set(RegisterOperandFlag::Use); + AdditionalFlags.set(RegisterOperandFlag::InternalRead); + return appendRegister(Reg, AdditionalFlags, SubReg, TargetFlags, false); +} + +Expected MachineInstrBuilder::addImplicitDef(Register Reg, + RegisterOperandFlags AdditionalFlags, + SubRegIndex SubReg, std::uint32_t TargetFlags) { + AdditionalFlags.set(RegisterOperandFlag::Define); + AdditionalFlags.set(RegisterOperandFlag::Implicit); + return appendRegister(Reg, AdditionalFlags, SubReg, TargetFlags, true); +} + +Expected MachineInstrBuilder::addImplicitUse(Register Reg, + RegisterOperandFlags AdditionalFlags, + SubRegIndex SubReg, std::uint32_t TargetFlags) { + AdditionalFlags.set(RegisterOperandFlag::Use); + AdditionalFlags.set(RegisterOperandFlag::Implicit); + return appendRegister(Reg, AdditionalFlags, SubReg, TargetFlags, true); +} + +Expected MachineInstrBuilder::addImmediate(std::int64_t Value, std::uint32_t TargetFlags) { + return addOperand(MachineOperand::immediate(Value, TargetFlags)); +} + +Expected MachineInstrBuilder::addFloatingImmediate(FloatingPointBits Bits, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::floatingImmediate(Bits, TargetFlags)); +} + +Expected MachineInstrBuilder::addBasicBlock(MachineBasicBlock &Block, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::basicBlock(Block, TargetFlags)); +} + +Expected MachineInstrBuilder::addFrameIndex(FrameIndex Index, std::int64_t Offset, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::frameIndex(Index, Offset, TargetFlags)); +} + +Expected MachineInstrBuilder::addConstantPool(ConstantPoolIndex Index, std::int64_t Offset, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::constantPool(Index, Offset, TargetFlags)); +} + +Expected MachineInstrBuilder::addJumpTable(JumpTableIndex Index, std::uint32_t TargetFlags) { + return addOperand(MachineOperand::jumpTable(Index, TargetFlags)); +} + +Expected MachineInstrBuilder::addGlobalSymbol(std::string Name, std::int64_t Offset, + std::uint32_t TargetFlags) { + auto Operand = MachineOperand::globalSymbol(std::move(Name), Offset, TargetFlags); + if (!Operand) + return std::unexpected(Operand.error()); + return addOperand(std::move(*Operand)); +} + +Expected MachineInstrBuilder::addExternalSymbol(std::string Name, std::int64_t Offset, + std::uint32_t TargetFlags) { + auto Operand = MachineOperand::externalSymbol(std::move(Name), Offset, TargetFlags); + if (!Operand) + return std::unexpected(Operand.error()); + return addOperand(std::move(*Operand)); +} + +Expected MachineInstrBuilder::addBlockAddress(std::string FunctionName, std::string BlockName, + std::int64_t Offset, + std::uint32_t TargetFlags) { + auto Operand = MachineOperand::blockAddress(std::move(FunctionName), std::move(BlockName), Offset, + TargetFlags); + if (!Operand) + return std::unexpected(Operand.error()); + return addOperand(std::move(*Operand)); +} + +Expected MachineInstrBuilder::addRegisterMask(std::shared_ptr Mask, + std::uint32_t TargetFlags) { + auto Operand = MachineOperand::registerMask(std::move(Mask), TargetFlags); + if (!Operand) + return std::unexpected(Operand.error()); + return addOperand(std::move(*Operand)); +} + +Expected MachineInstrBuilder::addMetadata(std::string Marker, std::uint32_t TargetFlags) { + auto Operand = MachineOperand::metadata(std::move(Marker), TargetFlags); + if (!Operand) + return std::unexpected(Operand.error()); + return addOperand(std::move(*Operand)); +} + +Expected MachineInstrBuilder::addTargetIndex(TargetIndex Index, std::int64_t Offset, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::targetIndex(Index, Offset, TargetFlags)); +} + +Expected MachineInstrBuilder::addMCSymbol(std::string Name, std::int64_t Offset, + std::uint32_t TargetFlags) { + auto Operand = MachineOperand::mcSymbol(std::move(Name), Offset, TargetFlags); + if (!Operand) + return std::unexpected(Operand.error()); + return addOperand(std::move(*Operand)); +} + +Expected MachineInstrBuilder::tieOperands(std::size_t DefOperand, std::size_t UseOperand) { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + if (DefOperand >= ExplicitOperands.size() || UseOperand >= ExplicitOperands.size() || + DefOperand == UseOperand) { + return makeUnexpected(ErrorCode::InvalidArgument, "builder operand tie indices are invalid"); + } + + const MachineOperand &Def = ExplicitOperands[DefOperand]; + const MachineOperand &Use = ExplicitOperands[UseOperand]; + if (!Def.isRegister() || !Use.isRegister() || !Def.isDef() || Def.isUse() || !Use.isUse() || + Use.isDef()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder tie must connect a pure register def and use"); + } + + const MachineOperandTie Tie{DefOperand, UseOperand}; + if (std::ranges::find(RequestedTies, Tie) == RequestedTies.end()) + RequestedTies.push_back(Tie); + return {}; +} + +Expected MachineInstrBuilder::addMemOperand(const MachineMemOperand &MemOperand) { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + if (!Function->ownsMemOperand(MemOperand)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder memory operand belongs to another function"); + } + MemOperands.push_back(&MemOperand); + return {}; +} + +Expected MachineInstrBuilder::setFlags(MachineInstrFlags NewFlags) { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + Flags = NewFlags; + return {}; +} + +Expected MachineInstrBuilder::setSourceLocation(SourceLocation NewLocation) { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + if (!NewLocation.isValid()) { + return makeUnexpected(ErrorCode::InvalidArgument, "builder source location is incomplete"); + } + Location = std::move(NewLocation); + return {}; +} + +Expected MachineInstrBuilder::clearSourceLocation() { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + Location.reset(); + return {}; +} + +Expected MachineInstrBuilder::setOrigin(OriginID NewOrigin) { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + if (NewOrigin.value() == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, "builder origin ID must be nonzero"); + } + Origin = NewOrigin; + return {}; +} + +Expected MachineInstrBuilder::clearOrigin() { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + Origin.reset(); + return {}; +} + +Expected MachineInstrBuilder::validateInsertionPoint() const { + if (InsertBlock == nullptr || &InsertBlock->function() != Function || !InsertBlock->isLinked()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder insertion block is not linked to its function"); + } + if (InsertBefore != nullptr && InsertBefore->parent() != InsertBlock) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder insertion cursor is no longer in its block"); + } + return {}; +} + +Expected MachineInstrBuilder::validateOperandOwnership(const MachineOperand &Operand) const { + if (Operand.parent() != nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder operand already belongs to an instruction"); + } + if (const auto *Block = Operand.getIf(); + Block != nullptr && (Block->Block == nullptr || &Block->Block->function() != Function || + !Block->Block->isLinked())) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder basic block operand is not linked to its function"); + } + if (const Register *Reg = Operand.getIf(); + Reg != nullptr && Reg->isVirtual() && + !Function->registerInfo().isKnownVirtualRegister(*Reg)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder operand references an unknown virtual register"); + } + return {}; +} + +std::vector MachineInstrBuilder::collectOperandTies() const { + std::vector Result; + for (std::size_t Index = 0; Index < Descriptor->Operands.size(); ++Index) { + const OperandDescriptor &Operand = Descriptor->Operands[Index]; + if (!Operand.DefaultTiedOperand || Index > *Operand.DefaultTiedOperand) + continue; + const std::size_t Other = *Operand.DefaultTiedOperand; + const bool IndexIsDef = Operand.RegisterRole == RegisterOperandRole::Def; + Result.push_back(IndexIsDef ? MachineOperandTie{Index, Other} + : MachineOperandTie{Other, Index}); + } + for (const MachineOperandTie Tie : RequestedTies) { + if (std::ranges::find(Result, Tie) == Result.end()) + Result.push_back(Tie); + } + return Result; +} + +Expected MachineInstrBuilder::build() { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + + auto InsertionValidation = validateInsertionPoint(); + if (!InsertionValidation) + return std::unexpected(InsertionValidation.error()); + + for (const MachineOperand &Operand : ExplicitOperands) { + auto Ownership = validateOperandOwnership(Operand); + if (!Ownership) + return std::unexpected(Ownership.error()); + } + for (const MachineOperand &Operand : ExtraImplicitOperands) { + auto Ownership = validateOperandOwnership(Operand); + if (!Ownership) + return std::unexpected(Ownership.error()); + } + + auto ExplicitValidation = Instructions->validateExplicitOperands( + Descriptor->InstructionOpcode, ExplicitOperands, Function->registerInfo()); + if (!ExplicitValidation) + return std::unexpected(ExplicitValidation.error()); + + auto DefaultImplicit = Instructions->createDefaultImplicitOperands(*Descriptor); + if (!DefaultImplicit) + return std::unexpected(DefaultImplicit.error()); + + std::vector ImplicitOperands; + ImplicitOperands.reserve(DefaultImplicit->size() + ExtraImplicitOperands.size()); + for (const MachineOperand &Operand : *DefaultImplicit) + ImplicitOperands.push_back(&Operand); + for (const MachineOperand &Operand : ExtraImplicitOperands) + ImplicitOperands.push_back(&Operand); + auto ImplicitValidation = + Instructions->validateImplicitOperands(Descriptor->InstructionOpcode, ImplicitOperands); + if (!ImplicitValidation) + return std::unexpected(ImplicitValidation.error()); + + const std::vector Ties = collectOperandTies(); + auto TieValidation = + Instructions->validateOperandTies(Descriptor->InstructionOpcode, ExplicitOperands, Ties); + if (!TieValidation) + return std::unexpected(TieValidation.error()); + + auto MemoryValidation = + Instructions->validateMemOperands(Descriptor->InstructionOpcode, MemOperands); + if (!MemoryValidation) + return std::unexpected(MemoryValidation.error()); + + auto Instruction = Function->createInstruction(Descriptor->InstructionOpcode); + if (!Instruction) + return std::unexpected(Instruction.error()); + MachineInstr *Result = *Instruction; + Result->reserveOperands(ExplicitOperands.size() + DefaultImplicit->size() + + ExtraImplicitOperands.size()); + + for (MachineOperand &Operand : ExplicitOperands) { + auto AddResult = Result->addOperand(std::move(Operand)); + if (!AddResult) + return std::unexpected(AddResult.error()); + } + for (MachineOperand &Operand : *DefaultImplicit) { + auto AddResult = Result->addOperand(std::move(Operand)); + if (!AddResult) + return std::unexpected(AddResult.error()); + } + for (MachineOperand &Operand : ExtraImplicitOperands) { + auto AddResult = Result->addOperand(std::move(Operand)); + if (!AddResult) + return std::unexpected(AddResult.error()); + } + for (const MachineOperandTie Tie : Ties) { + auto TieResult = Result->tieOperands(Tie.DefOperand, Tie.UseOperand); + if (!TieResult) + return std::unexpected(TieResult.error()); + } + for (const MachineMemOperand *MemOperand : MemOperands) { + auto AddResult = Result->addMemOperand(*MemOperand); + if (!AddResult) + return std::unexpected(AddResult.error()); + } + + Result->setFlags(Flags); + if (Location) + Result->setSourceLocation(std::move(*Location)); + if (Origin) + Result->setOrigin(*Origin); + + Expected InsertResult; + if (InsertBefore == nullptr) + InsertResult = InsertBlock->appendInstruction(*Result); + else + InsertResult = InsertBlock->insertInstructionBefore(*InsertBefore, *Result); + if (!InsertResult) + return std::unexpected(InsertResult.error()); + + IsBuilt = true; + return Result; +} + +Expected MachineIRBuilder::validateBlock(const MachineBasicBlock &Block) const { + if (&Block.function() != Function || !Block.isLinked()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder insertion block is not linked to its function"); + } + return {}; +} + +Expected MachineIRBuilder::validateInstruction(const MachineInstr &Instruction) const { + if (&Instruction.function() != Function || Instruction.parent() == nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder insertion instruction is not linked to its function"); + } + return {}; +} + +Expected MachineIRBuilder::setInsertPoint(MachineBasicBlock &Block) { + auto Validation = validateBlock(Block); + if (!Validation) + return std::unexpected(Validation.error()); + InsertBlock = &Block; + InsertBefore = nullptr; + return {}; +} + +Expected MachineIRBuilder::setInsertPointBefore(MachineInstr &Instruction) { + auto Validation = validateInstruction(Instruction); + if (!Validation) + return std::unexpected(Validation.error()); + InsertBlock = Instruction.parent(); + InsertBefore = &Instruction; + return {}; +} + +Expected MachineIRBuilder::setInsertPointAfter(MachineInstr &Instruction) { + auto Validation = validateInstruction(Instruction); + if (!Validation) + return std::unexpected(Validation.error()); + InsertBlock = Instruction.parent(); + InsertBefore = Instruction.next(); + return {}; +} + +Expected MachineIRBuilder::buildInstr(Opcode InstructionOpcode) const { + if (InsertBlock == nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, "machine IR builder has no insertion point"); + } + const InstructionDescriptor *Descriptor = Instructions->descriptor(InstructionOpcode); + if (Descriptor == nullptr) { + return makeUnexpected(ErrorCode::InvalidArgument, + "machine IR builder opcode is not registered"); + } + auto BlockValidation = validateBlock(*InsertBlock); + if (!BlockValidation) + return std::unexpected(BlockValidation.error()); + if (InsertBefore != nullptr && InsertBefore->parent() != InsertBlock) { + return makeUnexpected(ErrorCode::InvalidArgument, + "machine IR builder insertion cursor is stale"); + } + return MachineInstrBuilder(*Function, *Instructions, *Descriptor, *InsertBlock, InsertBefore); +} + +} // namespace rcc diff --git a/backend/lib/CodeGen/MachineInstr.cpp b/backend/lib/CodeGen/MachineInstr.cpp index 3692258..b201533 100644 --- a/backend/lib/CodeGen/MachineInstr.cpp +++ b/backend/lib/CodeGen/MachineInstr.cpp @@ -46,11 +46,11 @@ void MachineInstr::reserveOperands(std::size_t Capacity) { Operands.reserve(Capacity); } -Expected MachineInstr::addOperand(MachineOperand Operand) { +Expected MachineInstr::addOperand(MachineOperand &&Operand) { return insertOperand(Operands.size(), std::move(Operand)); } -Expected MachineInstr::insertOperand(std::size_t Index, MachineOperand Operand) { +Expected MachineInstr::insertOperand(std::size_t Index, MachineOperand &&Operand) { if (Index > Operands.size()) return makeUnexpected(ErrorCode::InvalidArgument, "operand insertion index is out of range"); if (Operand.Parent != nullptr) @@ -93,9 +93,9 @@ Expected MachineInstr::tieOperands(std::size_t DefIndex, std::size_t UseIn MachineOperand &DefOperand = Operands[DefIndex]; MachineOperand &UseOperand = Operands[UseIndex]; if (!DefOperand.isRegister() || !UseOperand.isRegister() || !DefOperand.isDef() || - !UseOperand.isUse()) { + DefOperand.isUse() || !UseOperand.isUse() || UseOperand.isDef()) { return makeUnexpected(ErrorCode::InvalidArgument, - "tie must connect a register definition and use"); + "tie must connect a pure register definition and use"); } if (DefOperand.TiedOperandIndex || UseOperand.TiedOperandIndex) { return makeUnexpected(ErrorCode::InvalidArgument, diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index a3aefde..ddba86a 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -4,6 +4,7 @@ set( RCCBackendCodeGenTestSources CoreTypesTest.cpp InstructionDescriptorTest.cpp + MachineIRBuilderTest.cpp MachineIRCoreTest.cpp MachineValueTypesTest.cpp ) @@ -29,6 +30,18 @@ add_test( COMMAND RCCBackendCodeGenInstructionDescriptorTests ) +add_executable(RCCBackendCodeGenMachineIRBuilderTests MachineIRBuilderTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenMachineIRBuilderTests) +target_link_libraries( + RCCBackendCodeGenMachineIRBuilderTests + PRIVATE RCCBackend::CodeGen +) + +add_test( + NAME RCCBackend.CodeGen.MachineIRBuilder + COMMAND RCCBackendCodeGenMachineIRBuilderTests +) + add_executable(RCCBackendCodeGenMachineValueTypesTests MachineValueTypesTest.cpp) rcc_backend_configure_target(RCCBackendCodeGenMachineValueTypesTests) target_link_libraries(RCCBackendCodeGenMachineValueTypesTests PRIVATE RCCBackend::CodeGen) @@ -50,12 +63,14 @@ add_test( rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" ) rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineValueTypesTest.cpp" ) diff --git a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp new file mode 100644 index 0000000..bda30b6 --- /dev/null +++ b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp @@ -0,0 +1,462 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineIRBuilder.h" + +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +constexpr std::uint32_t CopyOpcode = 1; +constexpr std::uint32_t AddOpcode = 2; +constexpr std::uint32_t LoadOpcode = 3; +constexpr std::uint32_t NopOpcode = 4; +constexpr std::uint32_t BranchOpcode = 5; +constexpr std::uint32_t DynamicImplicitOpcode = 6; + +class TestContext final { +public: + void expect(bool Condition, std::string_view Message) { + if (Condition) + return; + std::cerr << Message << '\n'; + ++FailureCount; + } + + [[nodiscard]] int result() const noexcept { + return FailureCount == 0 ? 0 : 1; + } + +private: + int FailureCount = 0; +}; + +[[nodiscard]] rcc::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 createInstructionSet() { + 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 Add = instructionDescriptor(AddOpcode, "TEST_ADD"); + Add.Operands.push_back(registerDescriptor(rcc::RegisterOperandRole::Def)); + Add.Operands.push_back(registerDescriptor(rcc::RegisterOperandRole::Use)); + rcc::OperandDescriptor RegisterOrImmediate; + RegisterOrImmediate.Kinds = { + rcc::MachineOperand::Kind::Register, + rcc::MachineOperand::Kind::Immediate, + }; + RegisterOrImmediate.RegisterRole = rcc::RegisterOperandRole::Use; + Add.Operands.push_back(RegisterOrImmediate); + Add.NumExplicitDefs = 1; + Add.ImplicitDefs.push_back( + rcc::ImplicitRegisterDescriptor{*rcc::PhysicalRegister::create(1), false, true}); + Add.ImplicitUses.push_back(rcc::ImplicitRegisterDescriptor{*rcc::PhysicalRegister::create(2)}); + + auto Load = instructionDescriptor(LoadOpcode, "TEST_LOAD"); + Load.Flags.set(rcc::InstructionFlag::MayLoad); + Load.RequiresMemoryOperands = true; + + auto Nop = instructionDescriptor(NopOpcode, "TEST_NOP"); + + 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 Dynamic = instructionDescriptor(DynamicImplicitOpcode, "TEST_DYNAMIC_IMPLICIT"); + Dynamic.AllowExtraImplicitOperands = true; + + std::vector Descriptors; + Descriptors.push_back(std::move(Copy)); + Descriptors.push_back(std::move(Add)); + Descriptors.push_back(std::move(Load)); + Descriptors.push_back(std::move(Nop)); + Descriptors.push_back(std::move(Branch)); + Descriptors.push_back(std::move(Dynamic)); + return rcc::InstructionSet::create(std::move(Descriptors)); +} + +[[nodiscard]] std::unique_ptr +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 memoryDescription(rcc::MemoryAccessFlag Access) { + rcc::MachineMemOperandDesc Description; + Description.Accesses = {Access}; + Description.Size = *rcc::MemorySize::fixed(8); + Description.Align = *rcc::Alignment::create(8); + Description.Base = rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(0)); + return Description; +} + +[[nodiscard]] rcc::MachineInstr *buildNop(TestContext &Context, rcc::MachineIRBuilder &Builder) { + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(NopOpcode)); + Context.expect(Pending.has_value(), "NOP builder creation must succeed"); + if (!Pending) + return nullptr; + auto Instruction = Pending->build(); + Context.expect(Instruction.has_value(), "NOP construction must succeed"); + if (!Instruction) + return nullptr; + return *Instruction; +} + +void testTransactionalBuild(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto Function = createFunction(Context, "transactional", 1); + Context.expect(InstructionSet.has_value(), "builder instruction set must be valid"); + if (!InstructionSet || !Function) + return; + rcc::MachineBasicBlock *Block = createBlock(Context, *Function); + if (Block == nullptr) + return; + + auto Type = rcc::LLT::makeInteger(64); + auto Destination = Function->registerInfo().createVirtualRegister(*Type); + auto Source = Function->registerInfo().createVirtualRegister(*Type); + Context.expect(Destination && Source, "builder virtual registers must be allocated"); + if (!Destination || !Source) + return; + + rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + Context.expect(!Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)), + "an unset insertion point must reject instruction construction"); + Context.expect(Function->allInstructions().empty(), + "rejected builder setup must not allocate an instruction"); + Context.expect(Builder.setInsertPoint(*Block).has_value(), + "a linked block must be a valid insertion point"); + Context.expect(!Builder.buildInstr(rcc::Opcode::fromValue(99)), + "an unregistered opcode must be rejected"); + + auto Incomplete = Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)); + Context.expect(Incomplete.has_value() && Incomplete->addDef(*Destination).has_value(), + "incomplete instruction setup must accept its first operand"); + if (!Incomplete) + return; + Context.expect(!Incomplete->tieOperands(0, 1) && !Incomplete->setSourceLocation({"", 0, 0, 0}) && + !Incomplete->setOrigin(rcc::OriginID::fromValue(0)), + "invalid pending metadata and tie indices must be rejected"); + Context.expect(!Incomplete->build(), "descriptor validation must reject a missing operand"); + Context.expect(Function->allInstructions().empty() && Block->instructionCount() == 0, + "failed validation must not allocate or publish an instruction"); + + auto Complete = Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)); + Context.expect(Complete.has_value(), "complete instruction builder must be created"); + if (!Complete) + return; + Context.expect(Complete->addDef(*Destination).has_value() && + Complete->addUse(*Source).has_value(), + "complete instruction operands must be accepted"); + Context.expect(Complete->tieOperands(0, 1).has_value(), + "an explicit tie matching the descriptor default must be idempotent"); + Context.expect(Complete->setFlags({rcc::MachineInstrFlag::FrameSetup}).has_value(), + "valid instruction flags must be accepted"); + Context.expect( + Complete->setSourceLocation(rcc::SourceLocation{"builder.mir", 7, 3, 1}).has_value() && + Complete->setOrigin(rcc::OriginID::fromValue(19)).has_value(), + "valid source and origin metadata must be accepted"); + auto Built = Complete->build(); + Context.expect(Built.has_value(), "valid pending instruction must build"); + if (!Built) + return; + + rcc::MachineInstr *Instruction = *Built; + Context.expect(Instruction->stableID().value() == 1, + "failed builds must not consume instruction StableIDs"); + Context.expect(Instruction->parent() == Block && Block->firstInstruction() == Instruction, + "successful build must publish at the insertion point"); + Context.expect(Instruction->operandAt(0)->tiedOperandIndex() == 1 && + Instruction->operandAt(1)->tiedOperandIndex() == 0, + "descriptor-default tie must be materialized bidirectionally"); + Context.expect(Instruction->flags().has(rcc::MachineInstrFlag::FrameSetup) && + Instruction->sourceLocation() == rcc::SourceLocation{"builder.mir", 7, 3, 1} && + Instruction->origin() == rcc::OriginID::fromValue(19), + "builder metadata must be preserved on the published instruction"); + Context.expect( + InstructionSet->validateInstruction(*Instruction, Function->registerInfo()).has_value(), + "builder output must satisfy the shared instruction descriptor"); + Context.expect(!Complete->build(), "a successful pending builder must be single-use"); + Context.expect(!Complete->setFlags({}) && !Complete->clearSourceLocation() && + !Complete->clearOrigin(), + "all pending-builder mutators must reject use after publication"); + Context.expect(Function->registerInfo().verifyDefUseChains().has_value(), + "published builder operands must enter exact def-use chains"); + + auto MoveSource = Builder.buildInstr(rcc::Opcode::fromValue(NopOpcode)); + Context.expect(MoveSource.has_value(), "movable pending builder must be created"); + if (!MoveSource) + return; + rcc::MachineInstrBuilder MoveDestination(std::move(*MoveSource)); + Context.expect(!MoveSource->build(), "a moved-from pending builder must be consumed"); + auto MovedResult = MoveDestination.build(); + Context.expect(MovedResult && (*MovedResult)->stableID().value() == 2, + "a moved-to pending builder must retain its construction state"); +} + +void testInsertionOrdering(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto Function = createFunction(Context, "ordering", 2); + if (!InstructionSet || !Function) + return; + rcc::MachineBasicBlock *Block = createBlock(Context, *Function); + if (Block == nullptr) + return; + + rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + Context.expect(Builder.setInsertPoint(*Block).has_value(), + "ordering builder insertion point must be valid"); + rcc::MachineInstr *First = buildNop(Context, Builder); + rcc::MachineInstr *Tail = buildNop(Context, Builder); + if (First == nullptr || Tail == nullptr) + return; + + Context.expect(Builder.setInsertPointAfter(*First).has_value(), + "after-instruction insertion point must be accepted"); + rcc::MachineInstr *MiddleA = buildNop(Context, Builder); + rcc::MachineInstr *MiddleB = buildNop(Context, Builder); + Context.expect(MiddleA != nullptr && MiddleB != nullptr && First->next() == MiddleA && + MiddleA->next() == MiddleB && MiddleB->next() == Tail, + "repeated insertion after a cursor must preserve build order"); + + Context.expect(Builder.setInsertPointBefore(*First).has_value(), + "before-instruction insertion point must be accepted"); + rcc::MachineInstr *PrefixA = buildNop(Context, Builder); + rcc::MachineInstr *PrefixB = buildNop(Context, Builder); + Context.expect(PrefixA != nullptr && PrefixB != nullptr && Block->firstInstruction() == PrefixA && + PrefixA->next() == PrefixB && PrefixB->next() == First, + "repeated insertion before a cursor must preserve build order"); +} + +void testImplicitAndMemoryValidation(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto Function = createFunction(Context, "implicit_memory", 3); + auto OtherFunction = createFunction(Context, "foreign", 4); + if (!InstructionSet || !Function || !OtherFunction) + return; + rcc::MachineBasicBlock *Block = createBlock(Context, *Function); + rcc::MachineBasicBlock *OtherBlock = createBlock(Context, *OtherFunction); + if (Block == nullptr || OtherBlock == nullptr) + return; + + auto Type = rcc::LLT::makeInteger(64); + auto Destination = Function->registerInfo().createVirtualRegister(*Type); + auto Source = Function->registerInfo().createVirtualRegister(*Type); + Context.expect(Destination && Source, "implicit test virtual registers must be allocated"); + if (!Destination || !Source) + return; + + rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + Context.expect(Builder.setInsertPoint(*Block).has_value(), + "implicit test insertion point must be accepted"); + + auto Add = Builder.buildInstr(rcc::Opcode::fromValue(AddOpcode)); + Context.expect(Add && Add->addDef(*Destination).has_value() && Add->addUse(*Source).has_value() && + Add->addImmediate(7).has_value(), + "ADD operands must be accepted by the pending builder"); + if (!Add) + return; + auto BuiltAdd = Add->build(); + Context.expect(BuiltAdd.has_value(), "ADD with default implicits must build"); + if (!BuiltAdd) + return; + Context.expect( + (*BuiltAdd)->operandCount() == 5 && (*BuiltAdd)->operandAt(3)->isImplicit() && + (*BuiltAdd)->operandAt(3)->registerFlags().has(rcc::RegisterOperandFlag::Renamable) && + (*BuiltAdd)->operandAt(4)->isImplicit(), + "default implicit defs and uses must follow explicit operands"); + Context.expect( + InstructionSet->validateInstruction(**BuiltAdd, Function->registerInfo()).has_value(), + "default implicit builder output must pass shared validation"); + + const std::size_t BeforeRejectedImplicit = Function->allInstructions().size(); + auto ExtraImplicit = Builder.buildInstr(rcc::Opcode::fromValue(AddOpcode)); + Context.expect( + ExtraImplicit && ExtraImplicit->addDef(*Destination).has_value() && + ExtraImplicit->addUse(*Source).has_value() && + ExtraImplicit->addImmediate(1).has_value() && + ExtraImplicit + ->addImplicitUse(rcc::Register::fromPhysical(*rcc::PhysicalRegister::create(3))) + .has_value(), + "dynamic implicit negative test must be assembled"); + Context.expect(ExtraImplicit && !ExtraImplicit->build() && + Function->allInstructions().size() == BeforeRejectedImplicit, + "descriptor-rejected dynamic implicits must not allocate instructions"); + + auto Dynamic = Builder.buildInstr(rcc::Opcode::fromValue(DynamicImplicitOpcode)); + Context.expect( + Dynamic && + Dynamic->addImplicitUse(rcc::Register::fromPhysical(*rcc::PhysicalRegister::create(3))) + .has_value(), + "descriptor-permitted dynamic implicit operand must be accepted"); + if (!Dynamic) + return; + auto BuiltDynamic = Dynamic->build(); + Context.expect( + BuiltDynamic.has_value() && + InstructionSet->validateInstruction(**BuiltDynamic, Function->registerInfo()).has_value(), + "dynamic implicit builder output must validate"); + + const std::size_t BeforeMissingMemory = Function->allInstructions().size(); + auto MissingMemory = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); + Context.expect(MissingMemory && !MissingMemory->build() && + Function->allInstructions().size() == BeforeMissingMemory, + "missing required MMO must fail before instruction allocation"); + + auto LoadMemory = Function->createMemOperand(memoryDescription(rcc::MemoryAccessFlag::Load)); + auto StoreMemory = Function->createMemOperand(memoryDescription(rcc::MemoryAccessFlag::Store)); + auto ForeignMemory = + OtherFunction->createMemOperand(memoryDescription(rcc::MemoryAccessFlag::Load)); + Context.expect(LoadMemory && StoreMemory && ForeignMemory, + "memory validation operands must be created"); + if (!LoadMemory || !StoreMemory || !ForeignMemory) + return; + + auto Load = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); + Context.expect(Load && Load->addMemOperand(**LoadMemory).has_value(), + "function-owned load MMO must be accepted"); + if (!Load) + return; + auto BuiltLoad = Load->build(); + Context.expect( + BuiltLoad.has_value() && + InstructionSet->validateInstruction(**BuiltLoad, Function->registerInfo()).has_value(), + "matching load MMO must build and validate"); + + const std::size_t BeforeStoreMismatch = Function->allInstructions().size(); + auto StoreMismatch = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); + Context.expect(StoreMismatch && StoreMismatch->addMemOperand(**StoreMemory).has_value() && + !StoreMismatch->build() && + Function->allInstructions().size() == BeforeStoreMismatch, + "MMO access mismatch must fail before instruction allocation"); + auto ForeignMMO = Builder.buildInstr(rcc::Opcode::fromValue(LoadOpcode)); + Context.expect(ForeignMMO && !ForeignMMO->addMemOperand(**ForeignMemory), + "foreign-function MMO must be rejected immediately"); + + const std::size_t BeforeForeignBlock = Function->allInstructions().size(); + auto ForeignBranch = Builder.buildInstr(rcc::Opcode::fromValue(BranchOpcode)); + Context.expect(ForeignBranch && ForeignBranch->addBasicBlock(*OtherBlock).has_value() && + !ForeignBranch->build() && + Function->allInstructions().size() == BeforeForeignBlock, + "foreign basic block operand must fail before instruction allocation"); +} + +void testStaleCursorsAndOwnedOperands(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto Function = createFunction(Context, "ownership", 5); + if (!InstructionSet || !Function) + return; + rcc::MachineBasicBlock *Block = createBlock(Context, *Function); + if (Block == nullptr) + return; + + auto Type = rcc::LLT::makeInteger(64); + auto Destination = Function->registerInfo().createVirtualRegister(*Type); + auto Source = Function->registerInfo().createVirtualRegister(*Type); + if (!Destination || !Source) + return; + + rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + Context.expect(Builder.setInsertPoint(*Block).has_value(), + "ownership test insertion point must be valid"); + auto Copy = Builder.buildInstr(rcc::Opcode::fromValue(CopyOpcode)); + Context.expect(Copy && Copy->addDef(*Destination).has_value() && + Copy->addUse(*Source).has_value(), + "ownership source instruction must be assembled"); + if (!Copy) + return; + auto BuiltCopy = Copy->build(); + if (!BuiltCopy) + return; + + auto DetachedTarget = Function->createInstruction(rcc::Opcode::fromValue(NopOpcode)); + Context.expect(DetachedTarget.has_value(), "detached target instruction must be allocated"); + if (!DetachedTarget) + return; + rcc::MachineOperand *OwnedOperand = (*BuiltCopy)->operandAt(0); + Context.expect( + OwnedOperand != nullptr && !(*DetachedTarget)->addOperand(std::move(*OwnedOperand)) && + OwnedOperand->parent() == *BuiltCopy && OwnedOperand->registerValue() == *Destination, + "controlled instruction API must reject an owned operand without moving it"); + + auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(NopOpcode)); + Context.expect(Pending && OwnedOperand != nullptr && + !Pending->addOperand(std::move(*OwnedOperand)) && + OwnedOperand->parent() == *BuiltCopy, + "controlled builder API must reject an owned operand without moving it"); + + rcc::MachineInstr *Cursor = buildNop(Context, Builder); + if (Cursor == nullptr) + return; + Context.expect(Builder.setInsertPointBefore(*Cursor).has_value(), "cursor setup must succeed"); + auto Stale = Builder.buildInstr(rcc::Opcode::fromValue(NopOpcode)); + Context.expect(Stale.has_value(), "pending stale-cursor test must be created"); + const std::size_t BeforeStaleBuild = Function->allInstructions().size(); + Context.expect(Block->removeInstruction(*Cursor).has_value(), "cursor removal must succeed"); + Context.expect(Stale && !Stale->build() && Function->allInstructions().size() == BeforeStaleBuild, + "a stale cursor must fail before instruction allocation"); +} + +} // namespace + +int main() { + TestContext Context; + testTransactionalBuild(Context); + testInsertionOrdering(Context); + testImplicitAndMemoryValidation(Context); + testStaleCursorsAndOwnedOperands(Context); + return Context.result(); +} -- cgit v1.2.3