diff options
| -rw-r--r-- | backend/include/rcc/CodeGen/InstructionDescriptor.h | 26 | ||||
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineIRBuilder.h | 161 | ||||
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineInstr.h | 4 | ||||
| -rw-r--r-- | backend/lib/CodeGen/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | backend/lib/CodeGen/InstructionDescriptor.cpp | 223 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineIRBuilder.cpp | 537 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineInstr.cpp | 8 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/CMakeLists.txt | 15 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineIRBuilderTest.cpp | 462 |
9 files changed, 1368 insertions, 72 deletions
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<bool(const MachineOperand &)> 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<InstructionSet> @@ -232,6 +240,15 @@ public: [[nodiscard]] Expected<void> validateExplicitOperands(Opcode InstructionOpcode, std::span<const MachineOperand> Operands, const MachineRegisterInfo &RegInfo) const; + [[nodiscard]] Expected<void> validateOperandTies(Opcode InstructionOpcode, + std::span<const MachineOperand> ExplicitOperands, + std::span<const MachineOperandTie> Ties) const; + [[nodiscard]] Expected<void> + validateImplicitOperands(Opcode InstructionOpcode, + std::span<const MachineOperand *const> ImplicitOperands) const; + [[nodiscard]] Expected<void> + validateMemOperands(Opcode InstructionOpcode, + std::span<const MachineMemOperand *const> MemOperands) const; [[nodiscard]] Expected<void> validateInstruction(const MachineInstr &Instruction, const MachineRegisterInfo &RegInfo) const; @@ -248,6 +265,15 @@ private: [[nodiscard]] Expected<void> validateExplicitOperands(const InstructionDescriptor &Descriptor, std::span<const MachineOperand> Operands, const MachineRegisterInfo &RegInfo) const; + [[nodiscard]] Expected<void> validateOperandTies(const InstructionDescriptor &Descriptor, + std::span<const MachineOperand> ExplicitOperands, + std::span<const MachineOperandTie> Ties) const; + [[nodiscard]] Expected<void> + validateImplicitOperands(const InstructionDescriptor &Descriptor, + std::span<const MachineOperand *const> ImplicitOperands) const; + [[nodiscard]] Expected<void> + validateMemOperands(const InstructionDescriptor &Descriptor, + std::span<const MachineMemOperand *const> MemOperands) const; [[nodiscard]] Expected<void> 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 <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <utility> +#include <vector> + +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<void> addOperand(MachineOperand &&Operand); + [[nodiscard]] Expected<void> addImplicitOperand(MachineOperand &&Operand); + + [[nodiscard]] Expected<void> addDef(Register Reg, RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addUse(Register Reg, RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addDefUse(Register Reg, RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addImplicitDef(Register Reg, + RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addImplicitUse(Register Reg, + RegisterOperandFlags AdditionalFlags = {}, + SubRegIndex SubReg = {}, + std::uint32_t TargetFlags = 0); + + [[nodiscard]] Expected<void> addImmediate(std::int64_t Value, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addFloatingImmediate(FloatingPointBits Bits, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addBasicBlock(MachineBasicBlock &Block, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addFrameIndex(FrameIndex Index, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addConstantPool(ConstantPoolIndex Index, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addJumpTable(JumpTableIndex Index, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addGlobalSymbol(std::string Name, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addExternalSymbol(std::string Name, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addBlockAddress(std::string FunctionName, std::string BlockName, + std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addRegisterMask(std::shared_ptr<const RegisterMask> Mask, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addMetadata(std::string Marker, std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addTargetIndex(TargetIndex Index, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + [[nodiscard]] Expected<void> addMCSymbol(std::string Name, std::int64_t Offset = 0, + std::uint32_t TargetFlags = 0); + + [[nodiscard]] Expected<void> tieOperands(std::size_t DefOperand, std::size_t UseOperand); + [[nodiscard]] Expected<void> addMemOperand(const MachineMemOperand &MemOperand); + + [[nodiscard]] Expected<void> setFlags(MachineInstrFlags NewFlags); + + [[nodiscard]] Expected<void> setSourceLocation(SourceLocation NewLocation); + [[nodiscard]] Expected<void> clearSourceLocation(); + + [[nodiscard]] Expected<void> setOrigin(OriginID NewOrigin); + [[nodiscard]] Expected<void> clearOrigin(); + + [[nodiscard]] Expected<MachineInstr *> build(); + +private: + MachineInstrBuilder(MachineFunction &Function, const InstructionSet &Instructions, + const InstructionDescriptor &Descriptor, MachineBasicBlock &InsertBlock, + MachineInstr *InsertBefore) noexcept; + + [[nodiscard]] Expected<void> ensurePending() const; + [[nodiscard]] Expected<void> validateInsertionPoint() const; + [[nodiscard]] Expected<void> validateOperandOwnership(const MachineOperand &Operand) const; + [[nodiscard]] std::vector<MachineOperandTie> collectOperandTies() const; + [[nodiscard]] Expected<void> 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<MachineOperand> ExplicitOperands; + std::vector<MachineOperand> ExtraImplicitOperands; + std::vector<MachineOperandTie> RequestedTies; + std::vector<const MachineMemOperand *> MemOperands; + MachineInstrFlags Flags; + std::optional<SourceLocation> Location; + std::optional<OriginID> 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<void> setInsertPoint(MachineBasicBlock &Block); + [[nodiscard]] Expected<void> setInsertPointBefore(MachineInstr &Instruction); + [[nodiscard]] Expected<void> setInsertPointAfter(MachineInstr &Instruction); + void clearInsertPoint() noexcept { + InsertBlock = nullptr; + InsertBefore = nullptr; + } + + [[nodiscard]] Expected<MachineInstrBuilder> buildInstr(Opcode InstructionOpcode) const; + +private: + [[nodiscard]] Expected<void> validateBlock(const MachineBasicBlock &Block) const; + [[nodiscard]] Expected<void> 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<void> addOperand(MachineOperand Operand); - [[nodiscard]] Expected<void> insertOperand(std::size_t Index, MachineOperand Operand); + [[nodiscard]] Expected<void> addOperand(MachineOperand &&Operand); + [[nodiscard]] Expected<void> insertOperand(std::size_t Index, MachineOperand &&Operand); [[nodiscard]] Expected<void> eraseOperand(std::size_t Index); [[nodiscard]] Expected<void> 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<void> validateDescriptorStructure(const InstructionDescriptor &Descriptor) { if (!Descriptor.InstructionOpcode.isValid()) return makeUnexpected(ErrorCode::InvalidArgument, "instruction descriptor opcode is invalid"); @@ -320,6 +328,133 @@ Expected<void> InstructionSet::validateExplicitOperands(Opcode InstructionOpcode return validateExplicitOperands(*Descriptor, Operands, RegInfo); } +Expected<void> InstructionSet::validateOperandTies(const InstructionDescriptor &Descriptor, + std::span<const MachineOperand> ExplicitOperands, + std::span<const MachineOperandTie> Ties) const { + std::vector<std::optional<std::size_t>> 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<void> InstructionSet::validateOperandTies(Opcode InstructionOpcode, + std::span<const MachineOperand> ExplicitOperands, + std::span<const MachineOperandTie> 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<void> InstructionSet::validateImplicitOperands( + const InstructionDescriptor &Descriptor, + std::span<const MachineOperand *const> 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<void> { + const MachineOperand *Operand = ImplicitOperands[Index++]; + const Register *Reg = Operand == nullptr ? nullptr : Operand->getIf<Register>(); + 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<void> InstructionSet::validateImplicitOperands( + Opcode InstructionOpcode, std::span<const MachineOperand *const> ImplicitOperands) const { + const InstructionDescriptor *Descriptor = descriptor(InstructionOpcode); + if (Descriptor == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, "instruction opcode is not registered"); + } + return validateImplicitOperands(*Descriptor, ImplicitOperands); +} + +Expected<void> +InstructionSet::validateMemOperands(const InstructionDescriptor &Descriptor, + std::span<const MachineMemOperand *const> 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<void> +InstructionSet::validateMemOperands(Opcode InstructionOpcode, + std::span<const MachineMemOperand *const> MemOperands) const { + const InstructionDescriptor *Descriptor = descriptor(InstructionOpcode); + if (Descriptor == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, "instruction opcode is not registered"); + } + return validateMemOperands(*Descriptor, MemOperands); +} + Expected<void> InstructionSet::validateOperand(const OperandDescriptor &Descriptor, const MachineOperand &Operand, const MachineRegisterInfo &RegInfo) const { @@ -402,86 +537,42 @@ Expected<void> InstructionSet::validateInstruction(const MachineInstr &Instructi if (!ExplicitValidation) return std::unexpected(ExplicitValidation.error()); + std::vector<MachineOperandTie> 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<void> { - 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<const MachineOperand *> 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<std::vector<MachineOperand>> 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 <algorithm> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <string> +#include <utility> +#include <vector> + +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<void> MachineInstrBuilder::ensurePending() const { + if (IsBuilt) { + return makeUnexpected(ErrorCode::InvalidArgument, + "machine instruction builder was already consumed"); + } + return {}; +} + +Expected<void> 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<void> 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<void> 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<void> MachineInstrBuilder::addDef(Register Reg, RegisterOperandFlags AdditionalFlags, + SubRegIndex SubReg, std::uint32_t TargetFlags) { + AdditionalFlags.set(RegisterOperandFlag::Define); + return appendRegister(Reg, AdditionalFlags, SubReg, TargetFlags, false); +} + +Expected<void> MachineInstrBuilder::addUse(Register Reg, RegisterOperandFlags AdditionalFlags, + SubRegIndex SubReg, std::uint32_t TargetFlags) { + AdditionalFlags.set(RegisterOperandFlag::Use); + return appendRegister(Reg, AdditionalFlags, SubReg, TargetFlags, false); +} + +Expected<void> 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<void> 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<void> 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<void> MachineInstrBuilder::addImmediate(std::int64_t Value, std::uint32_t TargetFlags) { + return addOperand(MachineOperand::immediate(Value, TargetFlags)); +} + +Expected<void> MachineInstrBuilder::addFloatingImmediate(FloatingPointBits Bits, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::floatingImmediate(Bits, TargetFlags)); +} + +Expected<void> MachineInstrBuilder::addBasicBlock(MachineBasicBlock &Block, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::basicBlock(Block, TargetFlags)); +} + +Expected<void> MachineInstrBuilder::addFrameIndex(FrameIndex Index, std::int64_t Offset, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::frameIndex(Index, Offset, TargetFlags)); +} + +Expected<void> MachineInstrBuilder::addConstantPool(ConstantPoolIndex Index, std::int64_t Offset, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::constantPool(Index, Offset, TargetFlags)); +} + +Expected<void> MachineInstrBuilder::addJumpTable(JumpTableIndex Index, std::uint32_t TargetFlags) { + return addOperand(MachineOperand::jumpTable(Index, TargetFlags)); +} + +Expected<void> 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<void> 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<void> 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<void> MachineInstrBuilder::addRegisterMask(std::shared_ptr<const RegisterMask> 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<void> 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<void> MachineInstrBuilder::addTargetIndex(TargetIndex Index, std::int64_t Offset, + std::uint32_t TargetFlags) { + return addOperand(MachineOperand::targetIndex(Index, Offset, TargetFlags)); +} + +Expected<void> 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<void> 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<void> 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<void> MachineInstrBuilder::setFlags(MachineInstrFlags NewFlags) { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + Flags = NewFlags; + return {}; +} + +Expected<void> 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<void> MachineInstrBuilder::clearSourceLocation() { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + Location.reset(); + return {}; +} + +Expected<void> 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<void> MachineInstrBuilder::clearOrigin() { + auto Pending = ensurePending(); + if (!Pending) + return std::unexpected(Pending.error()); + Origin.reset(); + return {}; +} + +Expected<void> 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<void> 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<BasicBlockOperand>(); + 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<Register>(); + Reg != nullptr && Reg->isVirtual() && + !Function->registerInfo().isKnownVirtualRegister(*Reg)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder operand references an unknown virtual register"); + } + return {}; +} + +std::vector<MachineOperandTie> MachineInstrBuilder::collectOperandTies() const { + std::vector<MachineOperandTie> 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<MachineInstr *> 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<const MachineOperand *> 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<MachineOperandTie> 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<void> 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<void> 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<void> 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<void> MachineIRBuilder::setInsertPoint(MachineBasicBlock &Block) { + auto Validation = validateBlock(Block); + if (!Validation) + return std::unexpected(Validation.error()); + InsertBlock = &Block; + InsertBefore = nullptr; + return {}; +} + +Expected<void> MachineIRBuilder::setInsertPointBefore(MachineInstr &Instruction) { + auto Validation = validateInstruction(Instruction); + if (!Validation) + return std::unexpected(Validation.error()); + InsertBlock = Instruction.parent(); + InsertBefore = &Instruction; + return {}; +} + +Expected<void> MachineIRBuilder::setInsertPointAfter(MachineInstr &Instruction) { + auto Validation = validateInstruction(Instruction); + if (!Validation) + return std::unexpected(Validation.error()); + InsertBlock = Instruction.parent(); + InsertBefore = Instruction.next(); + return {}; +} + +Expected<MachineInstrBuilder> 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<void> MachineInstr::addOperand(MachineOperand Operand) { +Expected<void> MachineInstr::addOperand(MachineOperand &&Operand) { return insertOperand(Operands.size(), std::move(Operand)); } -Expected<void> MachineInstr::insertOperand(std::size_t Index, MachineOperand Operand) { +Expected<void> 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<void> 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 <cstddef> +#include <cstdint> +#include <iostream> +#include <memory> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +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<rcc::InstructionSet> 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<rcc::InstructionDescriptor> 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<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 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(); +} |
