aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/include/rcc/CodeGen/InstructionDescriptor.h26
-rw-r--r--backend/include/rcc/CodeGen/MachineIRBuilder.h161
-rw-r--r--backend/include/rcc/CodeGen/MachineInstr.h4
-rw-r--r--backend/lib/CodeGen/CMakeLists.txt4
-rw-r--r--backend/lib/CodeGen/InstructionDescriptor.cpp223
-rw-r--r--backend/lib/CodeGen/MachineIRBuilder.cpp537
-rw-r--r--backend/lib/CodeGen/MachineInstr.cpp8
-rw-r--r--backend/unittests/CodeGen/CMakeLists.txt15
-rw-r--r--backend/unittests/CodeGen/MachineIRBuilderTest.cpp462
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();
+}