diff options
Diffstat (limited to 'backend')
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineFrameInfo.h | 186 | ||||
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineFunction.h | 10 | ||||
| -rw-r--r-- | backend/include/rcc/CodeGen/MachineIRBuilder.h | 1 | ||||
| -rw-r--r-- | backend/lib/CodeGen/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineFrameInfo.cpp | 234 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineFunctionCloner.cpp | 85 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineIRBuilder.cpp | 22 | ||||
| -rw-r--r-- | backend/lib/CodeGen/MachineVerifier.cpp | 45 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/CMakeLists.txt | 12 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineFrameInfoTest.cpp | 156 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineFunctionClonerTest.cpp | 78 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineIRBuilderTest.cpp | 10 | ||||
| -rw-r--r-- | backend/unittests/CodeGen/MachineVerifierTest.cpp | 53 |
13 files changed, 876 insertions, 20 deletions
diff --git a/backend/include/rcc/CodeGen/MachineFrameInfo.h b/backend/include/rcc/CodeGen/MachineFrameInfo.h new file mode 100644 index 0000000..ad1de53 --- /dev/null +++ b/backend/include/rcc/CodeGen/MachineFrameInfo.h @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_CODEGEN_MACHINEFRAMEINFO_H +#define RCC_CODEGEN_MACHINEFRAMEINFO_H + +#include "rcc/CodeGen/MachineIRTypes.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/StableID.h" +#include "rcc/Support/Error.h" + +#include <cstdint> +#include <optional> +#include <span> +#include <utility> +#include <variant> +#include <vector> + +namespace rcc { + +enum class StackObjectKind : std::uint8_t { + Default, + FixedABI, + SpillSlot, + VariableSized, + DynamicAlloca, + CalleeSaved, + EmergencySpill, + Target, + Count, +}; + +class StackID final { +public: + constexpr StackID() noexcept = default; + + [[nodiscard]] static constexpr StackID defaultStack() noexcept { + return StackID(DefaultStackValue); + } + + [[nodiscard]] static constexpr StackID safeStack() noexcept { + return StackID(SafeStackValue); + } + + [[nodiscard]] static constexpr StackID shadowCallStack() noexcept { + return StackID(ShadowCallStackValue); + } + + [[nodiscard]] static constexpr StackID scalableVectorStack() noexcept { + return StackID(ScalableVectorStackValue); + } + + [[nodiscard]] static Expected<StackID> targetStack(std::uint32_t Value); + + [[nodiscard]] constexpr std::uint32_t value() const noexcept { + return Value; + } + + [[nodiscard]] constexpr bool isTargetStack() const noexcept { + return Value >= FirstTargetStackValue; + } + + [[nodiscard]] constexpr bool isValid() const noexcept { + return Value <= ScalableVectorStackValue || isTargetStack(); + } + + friend constexpr bool operator==(StackID Left, StackID Right) noexcept = default; + +private: + static constexpr std::uint32_t DefaultStackValue = 0; + static constexpr std::uint32_t SafeStackValue = 1; + static constexpr std::uint32_t ShadowCallStackValue = 2; + static constexpr std::uint32_t ScalableVectorStackValue = 3; + static constexpr std::uint32_t FirstTargetStackValue = 256; + + explicit constexpr StackID(std::uint32_t Value) noexcept : Value(Value) {} + + std::uint32_t Value = DefaultStackValue; +}; + +struct StackObjectLifetimeRange final { + MachineInstrStableID Begin; + MachineInstrStableID End; + + friend constexpr bool operator==(StackObjectLifetimeRange Left, + StackObjectLifetimeRange Right) noexcept = default; +}; + +struct StackObjectDesc final { + StackObjectKind Kind = StackObjectKind::Default; + std::optional<std::uint64_t> Size; + Alignment Align; + std::optional<std::int64_t> Offset; + StackID Stack; + bool IsDead = false; + bool IsAliased = false; + bool AddressTaken = false; + std::vector<StackObjectLifetimeRange> Lifetime; + std::uint64_t TargetFlags = 0; + + friend bool operator==(const StackObjectDesc &Left, const StackObjectDesc &Right) = default; +}; + +class StackObject final { +public: + [[nodiscard]] FrameIndex index() const noexcept { + return Index; + } + + [[nodiscard]] bool isTombstone() const noexcept { + return std::holds_alternative<std::monostate>(Description); + } + + [[nodiscard]] const StackObjectDesc *description() const noexcept { + return std::get_if<StackObjectDesc>(&Description); + } + + [[nodiscard]] bool isSpill() const noexcept; + [[nodiscard]] bool isFixed() const noexcept; + [[nodiscard]] bool isVariableSized() const noexcept; + +private: + StackObject(FrameIndex Index, StackObjectDesc Description) + : Index(Index), Description(std::move(Description)) {} + + FrameIndex Index; + std::variant<std::monostate, StackObjectDesc> Description; + + friend class MachineFrameInfo; +}; + +class MachineFrameInfo final { +public: + MachineFrameInfo() = default; + MachineFrameInfo(const MachineFrameInfo &) = delete; + MachineFrameInfo &operator=(const MachineFrameInfo &) = delete; + MachineFrameInfo(MachineFrameInfo &&) noexcept = default; + MachineFrameInfo &operator=(MachineFrameInfo &&) noexcept = default; + ~MachineFrameInfo() = default; + + [[nodiscard]] Expected<FrameIndex> createObject(StackObjectDesc Description); + [[nodiscard]] Expected<void> updateObject(FrameIndex Index, StackObjectDesc Description); + [[nodiscard]] Expected<void> setObjectOffset(FrameIndex Index, std::int64_t Offset); + [[nodiscard]] Expected<void> markObjectDead(FrameIndex Index); + [[nodiscard]] Expected<void> eraseObject(FrameIndex Index); + + [[nodiscard]] const StackObject *object(FrameIndex Index) const noexcept; + [[nodiscard]] std::span<const StackObject> objects() const noexcept { + return Objects; + } + + [[nodiscard]] std::size_t liveObjectCount() const noexcept; + + [[nodiscard]] bool isFrozen() const noexcept { + return Frozen; + } + + [[nodiscard]] std::optional<std::uint64_t> staticStackSize() const noexcept { + return StaticStackSize; + } + + [[nodiscard]] Alignment stackAlignment() const noexcept { + return StackAlign; + } + + [[nodiscard]] std::uint64_t maxCallFrameSize() const noexcept { + return MaxCallFrameSize; + } + + [[nodiscard]] Expected<void> setMaxCallFrameSize(std::uint64_t Size); + [[nodiscard]] Expected<void> freeze(std::uint64_t Size, Alignment Align); + [[nodiscard]] Expected<void> verify() const; + +private: + [[nodiscard]] static Expected<void> verifyDescription(const StackObjectDesc &Description); + [[nodiscard]] Expected<StackObject *> mutableObject(FrameIndex Index); + + std::vector<StackObject> Objects; + bool Frozen = false; + std::optional<std::uint64_t> StaticStackSize; + Alignment StackAlign; + std::uint64_t MaxCallFrameSize = 0; +}; + +} // namespace rcc + +#endif // RCC_CODEGEN_MACHINEFRAMEINFO_H diff --git a/backend/include/rcc/CodeGen/MachineFunction.h b/backend/include/rcc/CodeGen/MachineFunction.h index 487fb5b..3e3fecb 100644 --- a/backend/include/rcc/CodeGen/MachineFunction.h +++ b/backend/include/rcc/CodeGen/MachineFunction.h @@ -3,6 +3,7 @@ #ifndef RCC_CODEGEN_MACHINEFUNCTION_H #define RCC_CODEGEN_MACHINEFUNCTION_H +#include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineRegisterInfo.h" #include "rcc/CodeGen/StableID.h" @@ -52,6 +53,14 @@ public: return RegInfo; } + [[nodiscard]] MachineFrameInfo &frameInfo() noexcept { + return FrameInfo; + } + + [[nodiscard]] const MachineFrameInfo &frameInfo() const noexcept { + return FrameInfo; + } + [[nodiscard]] MachineFunctionProperties &properties() noexcept { return Properties; } @@ -110,6 +119,7 @@ private: std::string Name; MachineFunctionStableID StableID; MachineRegisterInfo RegInfo; + MachineFrameInfo FrameInfo; MachineFunctionProperties Properties; MachineBasicBlock *FirstBlock = nullptr; MachineBasicBlock *LastBlock = nullptr; diff --git a/backend/include/rcc/CodeGen/MachineIRBuilder.h b/backend/include/rcc/CodeGen/MachineIRBuilder.h index 02e47ff..3bd8d67 100644 --- a/backend/include/rcc/CodeGen/MachineIRBuilder.h +++ b/backend/include/rcc/CodeGen/MachineIRBuilder.h @@ -96,6 +96,7 @@ private: [[nodiscard]] Expected<void> ensurePending() const; [[nodiscard]] Expected<void> validateInsertionPoint() const; + [[nodiscard]] Expected<void> validateFrameReference(FrameIndex Index) const; [[nodiscard]] Expected<void> validateOperandOwnership(const MachineOperand &Operand) const; [[nodiscard]] std::vector<MachineOperandTie> collectOperandTies() const; [[nodiscard]] Expected<void> appendRegister(Register Reg, RegisterOperandFlags Flags, diff --git a/backend/lib/CodeGen/CMakeLists.txt b/backend/lib/CodeGen/CMakeLists.txt index d19aaba..283727c 100644 --- a/backend/lib/CodeGen/CMakeLists.txt +++ b/backend/lib/CodeGen/CMakeLists.txt @@ -6,6 +6,7 @@ set( LowLevelType.cpp MachineBasicBlock.cpp MachineCFGUpdater.cpp + MachineFrameInfo.cpp MachineFunction.cpp MachineFunctionCloner.cpp MachineIRTypes.cpp @@ -25,6 +26,7 @@ set( "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/LowLevelType.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineBasicBlock.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineCFGUpdater.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFrameInfo.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFunction.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineFunctionCloner.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/CodeGen/MachineIRTypes.h" @@ -73,6 +75,7 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlock.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfo.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunction.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionCloner.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRTypes.cpp" @@ -92,6 +95,7 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/LowLevelType.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineBasicBlock.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineCFGUpdater.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfo.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunction.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionCloner.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRTypes.cpp" diff --git a/backend/lib/CodeGen/MachineFrameInfo.cpp b/backend/lib/CodeGen/MachineFrameInfo.cpp new file mode 100644 index 0000000..58eab00 --- /dev/null +++ b/backend/lib/CodeGen/MachineFrameInfo.cpp @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineFrameInfo.h" + +#include <cstddef> +#include <cstdint> +#include <limits> +#include <unordered_set> +#include <utility> + +namespace rcc { + +Expected<StackID> StackID::targetStack(std::uint32_t Value) { + if (Value < FirstTargetStackValue) { + return makeUnexpected(ErrorCode::InvalidArgument, + "target stack ID must not overlap a built-in stack ID"); + } + return StackID(Value); +} + +bool StackObject::isSpill() const noexcept { + const StackObjectDesc *Desc = description(); + if (Desc == nullptr) + return false; + return Desc->Kind == StackObjectKind::SpillSlot || Desc->Kind == StackObjectKind::CalleeSaved || + Desc->Kind == StackObjectKind::EmergencySpill; +} + +bool StackObject::isFixed() const noexcept { + const StackObjectDesc *Desc = description(); + return Desc != nullptr && Desc->Kind == StackObjectKind::FixedABI; +} + +bool StackObject::isVariableSized() const noexcept { + const StackObjectDesc *Desc = description(); + return Desc != nullptr && (Desc->Kind == StackObjectKind::VariableSized || + Desc->Kind == StackObjectKind::DynamicAlloca); +} + +Expected<void> MachineFrameInfo::verifyDescription(const StackObjectDesc &Description) { + if (Description.Kind >= StackObjectKind::Count) + return makeUnexpected(ErrorCode::InvalidArgument, "stack object kind is invalid"); + if (!Description.Stack.isValid()) + return makeUnexpected(ErrorCode::InvalidArgument, "stack object has an invalid stack ID"); + + const bool IsVariable = Description.Kind == StackObjectKind::VariableSized || + Description.Kind == StackObjectKind::DynamicAlloca; + if (IsVariable) { + if (Description.Size || Description.Offset) { + return makeUnexpected(ErrorCode::InvalidArgument, + "variable-sized stack objects cannot have a fixed size or offset"); + } + } else if (!Description.Size || *Description.Size == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "fixed-size stack objects require a nonzero size"); + } + + if (Description.Kind == StackObjectKind::FixedABI && !Description.Offset) { + return makeUnexpected(ErrorCode::InvalidArgument, + "fixed ABI stack objects require a fixed offset"); + } + if (Description.Kind == StackObjectKind::Target && !Description.Stack.isTargetStack() && + Description.TargetFlags == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "target stack objects require a target stack ID or nonzero target flags"); + } + + std::unordered_set<MachineInstrStableID> LifetimeBoundaries; + for (const StackObjectLifetimeRange &Range : Description.Lifetime) { + if (!Range.Begin.isValid() || !Range.End.isValid() || Range.Begin == Range.End) { + return makeUnexpected(ErrorCode::InvalidArgument, + "stack object lifetime boundaries must be valid and distinct"); + } + if (!LifetimeBoundaries.insert(Range.Begin).second || + !LifetimeBoundaries.insert(Range.End).second) { + return makeUnexpected(ErrorCode::InvalidArgument, + "stack object lifetime boundaries must not be reused"); + } + } + return {}; +} + +Expected<FrameIndex> MachineFrameInfo::createObject(StackObjectDesc Description) { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, "frozen frame cannot create stack objects"); + auto DescriptionResult = verifyDescription(Description); + if (!DescriptionResult) + return std::unexpected(DescriptionResult.error()); + if (Objects.size() > std::numeric_limits<std::uint32_t>::max()) { + return makeUnexpected(ErrorCode::Unsupported, "stack object index space is exhausted"); + } + + const FrameIndex Index = FrameIndex::fromValue(static_cast<std::uint32_t>(Objects.size())); + Objects.push_back(StackObject(Index, std::move(Description))); + return Index; +} + +Expected<StackObject *> MachineFrameInfo::mutableObject(FrameIndex Index) { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, "frozen frame cannot modify stack objects"); + if (Index.value() >= Objects.size()) + return makeUnexpected(ErrorCode::InvalidArgument, "stack object index is out of range"); + StackObject &Object = Objects[Index.value()]; + if (Object.isTombstone()) + return makeUnexpected(ErrorCode::InvalidArgument, "stack object index is a tombstone"); + return &Object; +} + +Expected<void> MachineFrameInfo::updateObject(FrameIndex Index, StackObjectDesc Description) { + auto DescriptionResult = verifyDescription(Description); + if (!DescriptionResult) + return std::unexpected(DescriptionResult.error()); + auto Object = mutableObject(Index); + if (!Object) + return std::unexpected(Object.error()); + (*Object)->Description.emplace<StackObjectDesc>(std::move(Description)); + return {}; +} + +Expected<void> MachineFrameInfo::setObjectOffset(FrameIndex Index, std::int64_t Offset) { + auto Object = mutableObject(Index); + if (!Object) + return std::unexpected(Object.error()); + StackObjectDesc *CurrentDescription = std::get_if<StackObjectDesc>(&(*Object)->Description); + if (CurrentDescription == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "live stack object unexpectedly lacks a description"); + } + StackObjectDesc Description = *CurrentDescription; + if ((*Object)->isVariableSized()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "variable-sized stack object cannot have a fixed offset"); + } + Description.Offset = Offset; + (*Object)->Description.emplace<StackObjectDesc>(std::move(Description)); + return {}; +} + +Expected<void> MachineFrameInfo::markObjectDead(FrameIndex Index) { + auto Object = mutableObject(Index); + if (!Object) + return std::unexpected(Object.error()); + StackObjectDesc *Description = std::get_if<StackObjectDesc>(&(*Object)->Description); + if (Description == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "live stack object unexpectedly lacks a description"); + } + Description->IsDead = true; + return {}; +} + +Expected<void> MachineFrameInfo::eraseObject(FrameIndex Index) { + auto Object = mutableObject(Index); + if (!Object) + return std::unexpected(Object.error()); + (*Object)->Description.emplace<std::monostate>(); + return {}; +} + +const StackObject *MachineFrameInfo::object(FrameIndex Index) const noexcept { + if (Index.value() >= Objects.size() || Objects[Index.value()].isTombstone()) + return nullptr; + return &Objects[Index.value()]; +} + +std::size_t MachineFrameInfo::liveObjectCount() const noexcept { + std::size_t Count = 0; + for (const StackObject &Object : Objects) { + if (!Object.isTombstone()) + ++Count; + } + return Count; +} + +Expected<void> MachineFrameInfo::setMaxCallFrameSize(std::uint64_t Size) { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, + "frozen frame cannot change its maximum call-frame size"); + MaxCallFrameSize = Size; + return {}; +} + +Expected<void> MachineFrameInfo::freeze(std::uint64_t Size, Alignment Align) { + if (Frozen) + return makeUnexpected(ErrorCode::InvalidArgument, "frame layout is already frozen"); + for (const StackObject &Object : Objects) { + if (Object.isTombstone() || Object.isVariableSized()) + continue; + if (!Object.description()->Offset) { + return makeUnexpected(ErrorCode::VerificationError, + "fixed-size stack object is missing its final offset"); + } + } + if (Size % Align.bytes() != 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "static stack size must be aligned to the stack alignment"); + } + + StaticStackSize = Size; + StackAlign = Align; + Frozen = true; + return {}; +} + +Expected<void> MachineFrameInfo::verify() const { + for (std::size_t Position = 0; Position < Objects.size(); ++Position) { + const StackObject &Object = Objects[Position]; + if (Object.index() != FrameIndex::fromValue(static_cast<std::uint32_t>(Position))) { + return makeUnexpected(ErrorCode::VerificationError, + "stack object index does not match its stable table position"); + } + if (Object.isTombstone()) + continue; + auto DescriptionResult = verifyDescription(*Object.description()); + if (!DescriptionResult) + return std::unexpected(DescriptionResult.error()); + if (Frozen && !Object.isVariableSized() && !Object.description()->Offset) { + return makeUnexpected(ErrorCode::VerificationError, + "frozen fixed-size stack object has no final offset"); + } + } + + if (Frozen != StaticStackSize.has_value()) { + return makeUnexpected(ErrorCode::VerificationError, + "frame frozen state and static stack size are inconsistent"); + } + if (Frozen && *StaticStackSize % StackAlign.bytes() != 0) { + return makeUnexpected(ErrorCode::VerificationError, + "frozen static stack size violates stack alignment"); + } + return {}; +} + +} // namespace rcc diff --git a/backend/lib/CodeGen/MachineFunctionCloner.cpp b/backend/lib/CodeGen/MachineFunctionCloner.cpp index d452546..46fed6c 100644 --- a/backend/lib/CodeGen/MachineFunctionCloner.cpp +++ b/backend/lib/CodeGen/MachineFunctionCloner.cpp @@ -5,6 +5,7 @@ #include "rcc/CodeGen/InstructionDescriptor.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineInstr.h" #include "rcc/CodeGen/MachineMemOperand.h" @@ -86,8 +87,12 @@ struct CloneState final { if (Frame == nullptr) { return makeUnexpected(ErrorCode::VerificationError, "source frame-index operand is invalid"); } - State.Result.FrameIndices.emplace(Frame->Index, Frame->Index); - return SourceOperand.cloneDetached(); + const auto Position = State.Result.FrameIndices.find(Frame->Index); + if (Position == State.Result.FrameIndices.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "source operand references an unmapped frame index"); + } + return MachineOperand::frameIndex(Position->second, Frame->Offset, SourceOperand.targetFlags()); } case MachineOperand::Kind::ConstantPoolIndex: { const auto *Constant = SourceOperand.getIf<ConstantPoolOperand>(); @@ -123,8 +128,6 @@ struct CloneState final { void recordMemoryIndexMappings(const MachineMemOperandDesc &Description, MachineFunctionCloneResult &Result) { - if (const auto Frame = Description.Base.frameIndex()) - Result.FrameIndices.emplace(*Frame, *Frame); if (const auto Constant = Description.Base.constantPoolIndex()) Result.ConstantPoolIndices.emplace(*Constant, *Constant); if (const auto JumpTable = Description.Base.jumpTableIndex()) @@ -139,6 +142,14 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { MachineMemOperandDesc Description = SourceOperand.description(); recordMemoryIndexMappings(Description, State.Result); + if (const auto Frame = Description.Base.frameIndex()) { + const auto Position = State.Result.FrameIndices.find(*Frame); + if (Position == State.Result.FrameIndices.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "source MMO references an unmapped frame index"); + } + Description.Base = MemoryBase::frameIndex(Position->second); + } auto DestinationOperand = State.Destination.createMemOperand(std::move(Description)); if (!DestinationOperand) return std::unexpected(DestinationOperand.error()); @@ -207,6 +218,23 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { return {}; } +[[nodiscard]] Expected<void> cloneFrameObjects(CloneState &State) { + for (const StackObject &SourceObject : State.Source.frameInfo().objects()) { + const StackObjectDesc *Description = SourceObject.description(); + if (Description == nullptr) + continue; + auto DestinationIndex = State.Destination.frameInfo().createObject(*Description); + if (!DestinationIndex) + return std::unexpected(DestinationIndex.error()); + State.Result.FrameIndices.emplace(SourceObject.index(), *DestinationIndex); + } + auto CallFrameResult = State.Destination.frameInfo().setMaxCallFrameSize( + State.Source.frameInfo().maxCallFrameSize()); + if (!CallFrameResult) + return std::unexpected(CallFrameResult.error()); + return {}; +} + [[nodiscard]] Expected<void> cloneBlocks(CloneState &State) { for (const MachineBasicBlock *SourceBlock = State.Source.firstBlock(); SourceBlock != nullptr; SourceBlock = SourceBlock->next()) { @@ -294,6 +322,49 @@ cloneMemOperand(const MachineMemOperand &SourceOperand, CloneState &State) { return {}; } +[[nodiscard]] Expected<void> finalizeFrameClone(CloneState &State) { + for (const StackObject &SourceObject : State.Source.frameInfo().objects()) { + const StackObjectDesc *SourceDescription = SourceObject.description(); + if (SourceDescription == nullptr) + continue; + const auto FramePosition = State.Result.FrameIndices.find(SourceObject.index()); + if (FramePosition == State.Result.FrameIndices.end()) { + return makeUnexpected(ErrorCode::InternalError, + "source stack object was not mapped before frame finalization"); + } + + StackObjectDesc DestinationDescription = *SourceDescription; + for (StackObjectLifetimeRange &Range : DestinationDescription.Lifetime) { + const auto Begin = State.Result.InstructionStableIDs.find(Range.Begin); + const auto End = State.Result.InstructionStableIDs.find(Range.End); + if (Begin == State.Result.InstructionStableIDs.end() || + End == State.Result.InstructionStableIDs.end()) { + return makeUnexpected(ErrorCode::VerificationError, + "source stack lifetime references an unmapped instruction"); + } + Range.Begin = Begin->second; + Range.End = End->second; + } + auto UpdateResult = State.Destination.frameInfo().updateObject( + FramePosition->second, std::move(DestinationDescription)); + if (!UpdateResult) + return std::unexpected(UpdateResult.error()); + } + + if (State.Source.frameInfo().isFrozen()) { + const auto StackSize = State.Source.frameInfo().staticStackSize(); + if (!StackSize) { + return makeUnexpected(ErrorCode::InternalError, + "verified frozen source frame has no static stack size"); + } + auto FreezeResult = + State.Destination.frameInfo().freeze(*StackSize, State.Source.frameInfo().stackAlignment()); + if (!FreezeResult) + return std::unexpected(FreezeResult.error()); + } + return {}; +} + [[nodiscard]] Expected<void> cloneCFG(CloneState &State) { MachineCFGUpdater Updater = State.Destination.cfgUpdater(); for (const MachineBasicBlock *SourceBlock = State.Source.firstBlock(); SourceBlock != nullptr; @@ -353,12 +424,18 @@ Expected<MachineFunctionCloneResult> cloneMachineFunction(const MachineFunction auto RegisterResult = cloneRegisterConfiguration(State); if (!RegisterResult) return std::unexpected(RegisterResult.error()); + auto FrameResult = cloneFrameObjects(State); + if (!FrameResult) + return std::unexpected(FrameResult.error()); auto BlockResult = cloneBlocks(State); if (!BlockResult) return std::unexpected(BlockResult.error()); auto InstructionResult = cloneInstructions(State); if (!InstructionResult) return std::unexpected(InstructionResult.error()); + auto FrameFinalizationResult = finalizeFrameClone(State); + if (!FrameFinalizationResult) + return std::unexpected(FrameFinalizationResult.error()); auto CFGResult = cloneCFG(State); if (!CFGResult) return std::unexpected(CFGResult.error()); diff --git a/backend/lib/CodeGen/MachineIRBuilder.cpp b/backend/lib/CodeGen/MachineIRBuilder.cpp index 8f80ad6..185b7df 100644 --- a/backend/lib/CodeGen/MachineIRBuilder.cpp +++ b/backend/lib/CodeGen/MachineIRBuilder.cpp @@ -3,6 +3,7 @@ #include "rcc/CodeGen/MachineIRBuilder.h" #include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineInstr.h" #include "rcc/CodeGen/MachineMemOperand.h" @@ -332,6 +333,15 @@ Expected<void> MachineInstrBuilder::validateInsertionPoint() const { return {}; } +Expected<void> MachineInstrBuilder::validateFrameReference(FrameIndex Index) const { + const StackObject *Object = Function->frameInfo().object(Index); + if (Object == nullptr || Object->description()->IsDead) { + return makeUnexpected(ErrorCode::InvalidArgument, + "builder references an unknown or dead stack object"); + } + return {}; +} + Expected<void> MachineInstrBuilder::validateOperandOwnership(const MachineOperand &Operand) const { if (Operand.parent() != nullptr) { return makeUnexpected(ErrorCode::InvalidArgument, @@ -349,6 +359,11 @@ Expected<void> MachineInstrBuilder::validateOperandOwnership(const MachineOperan return makeUnexpected(ErrorCode::InvalidArgument, "builder operand references an unknown virtual register"); } + if (const auto *Frame = Operand.getIf<FrameIndexOperand>(); Frame != nullptr) { + auto FrameResult = validateFrameReference(Frame->Index); + if (!FrameResult) + return FrameResult; + } return {}; } @@ -389,6 +404,13 @@ Expected<MachineInstr *> MachineInstrBuilder::build() { if (!Ownership) return std::unexpected(Ownership.error()); } + for (const MachineMemOperand *MemOperand : MemOperands) { + if (const auto Frame = MemOperand->description().Base.frameIndex()) { + auto FrameResult = validateFrameReference(*Frame); + if (!FrameResult) + return std::unexpected(FrameResult.error()); + } + } auto ExplicitValidation = Instructions->validateExplicitOperands( Descriptor->InstructionOpcode, ExplicitOperands, Function->registerInfo()); diff --git a/backend/lib/CodeGen/MachineVerifier.cpp b/backend/lib/CodeGen/MachineVerifier.cpp index 450f8e3..6331366 100644 --- a/backend/lib/CodeGen/MachineVerifier.cpp +++ b/backend/lib/CodeGen/MachineVerifier.cpp @@ -3,6 +3,7 @@ #include "rcc/CodeGen/MachineVerifier.h" #include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineInstr.h" @@ -36,9 +37,15 @@ public: auto CatalogResult = catalogArenaObjects(); if (!CatalogResult) return CatalogResult; + auto FrameResult = Function.frameInfo().verify(); + if (!FrameResult) + return failWith("frame info", FrameResult.error()); auto LayoutResult = verifyBlockLayout(); if (!LayoutResult) return LayoutResult; + auto FrameLifetimeResult = verifyFrameLifetimes(); + if (!FrameLifetimeResult) + return FrameLifetimeResult; auto CFGResult = verifyCFG(); if (!CFGResult) return CFGResult; @@ -209,6 +216,7 @@ private: return fail("block layout references an unregistered instruction"); if (!LinkedInstructions.insert(Instruction).second) return fail("instruction layout contains a cycle or duplicate instruction"); + LinkedInstructionIDs.insert(Instruction->stableID()); if (Instruction->parent() != &Block || Instruction->previous() != Previous) { return fail("instruction parent or previous link is inconsistent"); } @@ -270,6 +278,29 @@ private: return {}; } + [[nodiscard]] Expected<void> verifyFrameIndex(FrameIndex Index) const { + const StackObject *Object = Function.frameInfo().object(Index); + const StackObjectDesc *Description = Object == nullptr ? nullptr : Object->description(); + if (Description == nullptr || Description->IsDead) + return fail("live instruction references an unknown or dead stack object"); + return {}; + } + + [[nodiscard]] Expected<void> verifyFrameLifetimes() const { + for (const StackObject &Object : Function.frameInfo().objects()) { + const StackObjectDesc *Description = Object.description(); + if (Description == nullptr) + continue; + for (const StackObjectLifetimeRange &Range : Description->Lifetime) { + if (!LinkedInstructionIDs.contains(Range.Begin) || + !LinkedInstructionIDs.contains(Range.End)) { + return fail("stack object lifetime references a detached or unknown instruction"); + } + } + } + return {}; + } + [[nodiscard]] Expected<void> verifyLiveInstruction(const MachineInstr &Instruction) { auto LocalResult = verifyInstructionLocalState(Instruction); if (!LocalResult) @@ -297,8 +328,12 @@ private: const auto Operands = Instruction.operands(); for (std::size_t Index = 0; Index < Operands.size(); ++Index) { const MachineOperand &Operand = Operands[Index]; - if (Operand.kind() == MachineOperand::Kind::FrameIndex) + if (const auto *Frame = Operand.getIf<FrameIndexOperand>(); Frame != nullptr) { HasFrameIndex = true; + auto FrameResult = verifyFrameIndex(Frame->Index); + if (!FrameResult) + return FrameResult; + } if (const auto *Block = Operand.getIf<BasicBlockOperand>(); Block != nullptr && @@ -328,6 +363,13 @@ private: HasUnrewrittenTie = true; } } + for (const MachineMemOperand *MemOperand : Instruction.memOperands()) { + if (const auto Frame = MemOperand->description().Base.frameIndex()) { + auto FrameResult = verifyFrameIndex(*Frame); + if (!FrameResult) + return FrameResult; + } + } return {}; } @@ -480,6 +522,7 @@ private: std::unordered_set<const MachineBasicBlock *> LinkedBlocks; std::unordered_set<const MachineInstr *> RegisteredInstructions; std::unordered_set<const MachineInstr *> LinkedInstructions; + std::unordered_set<MachineInstrStableID> LinkedInstructionIDs; std::unordered_set<const MachineMemOperand *> RegisteredMemOperands; std::unordered_map<Register, std::size_t> VirtualDefCounts; bool HasPhi = false; diff --git a/backend/unittests/CodeGen/CMakeLists.txt b/backend/unittests/CodeGen/CMakeLists.txt index 650cfa9..18cda7c 100644 --- a/backend/unittests/CodeGen/CMakeLists.txt +++ b/backend/unittests/CodeGen/CMakeLists.txt @@ -4,6 +4,7 @@ set( RCCBackendCodeGenTestSources CoreTypesTest.cpp InstructionDescriptorTest.cpp + MachineFrameInfoTest.cpp MachineFunctionClonerTest.cpp MachineIRBuilderTest.cpp MachineIRCoreTest.cpp @@ -32,6 +33,15 @@ add_test( COMMAND RCCBackendCodeGenInstructionDescriptorTests ) +add_executable(RCCBackendCodeGenMachineFrameInfoTests MachineFrameInfoTest.cpp) +rcc_backend_configure_target(RCCBackendCodeGenMachineFrameInfoTests) +target_link_libraries(RCCBackendCodeGenMachineFrameInfoTests PRIVATE RCCBackend::CodeGen) + +add_test( + NAME RCCBackend.CodeGen.MachineFrameInfo + COMMAND RCCBackendCodeGenMachineFrameInfoTests +) + add_executable( RCCBackendCodeGenMachineFunctionClonerTests MachineFunctionClonerTest.cpp @@ -92,6 +102,7 @@ add_test( rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" @@ -101,6 +112,7 @@ rcc_backend_register_format_files( rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/CoreTypesTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/InstructionDescriptorTest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MachineFrameInfoTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineFunctionClonerTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRBuilderTest.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MachineIRCoreTest.cpp" diff --git a/backend/unittests/CodeGen/MachineFrameInfoTest.cpp b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp new file mode 100644 index 0000000..7368815 --- /dev/null +++ b/backend/unittests/CodeGen/MachineFrameInfoTest.cpp @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/MachineFrameInfo.h" + +#include <cstdint> +#include <iostream> +#include <string_view> + +namespace { + +class TestContext final { +public: + void expect(bool Condition, std::string_view Message) { + if (Condition) + return; + std::cerr << Message << '\n'; + ++FailureCount; + } + + [[nodiscard]] int result() const noexcept { + return FailureCount == 0 ? 0 : 1; + } + +private: + int FailureCount = 0; +}; + +[[nodiscard]] rcc::StackObjectDesc fixedObject(std::uint64_t Size, std::uint64_t Align) { + rcc::StackObjectDesc Description; + Description.Size = Size; + Description.Align = *rcc::Alignment::create(Align); + return Description; +} + +void testObjectLifecycle(TestContext &Context) { + rcc::MachineFrameInfo Frame; + + auto LocalDescription = fixedObject(8, 8); + LocalDescription.IsAliased = true; + LocalDescription.AddressTaken = true; + LocalDescription.Lifetime.push_back( + {rcc::MachineInstrStableID::fromValue(3), rcc::MachineInstrStableID::fromValue(7)}); + auto Local = Frame.createObject(LocalDescription); + + auto FixedDescription = fixedObject(16, 16); + FixedDescription.Kind = rcc::StackObjectKind::FixedABI; + FixedDescription.Offset = -32; + auto Fixed = Frame.createObject(FixedDescription); + + auto SpillDescription = fixedObject(8, 8); + SpillDescription.Kind = rcc::StackObjectKind::SpillSlot; + auto Spill = Frame.createObject(SpillDescription); + + rcc::StackObjectDesc VariableDescription; + VariableDescription.Kind = rcc::StackObjectKind::VariableSized; + VariableDescription.Align = *rcc::Alignment::create(32); + auto Variable = Frame.createObject(VariableDescription); + + Context.expect(Local && Fixed && Spill && Variable, "valid stack object kinds must be created"); + if (!Local || !Fixed || !Spill || !Variable) + return; + + Context.expect(Local->value() == 0 && Fixed->value() == 1 && Spill->value() == 2 && + Variable->value() == 3 && Frame.liveObjectCount() == 4, + "frame indices must be monotonic table identities"); + Context.expect(Frame.object(*Fixed)->isFixed() && Frame.object(*Spill)->isSpill() && + Frame.object(*Variable)->isVariableSized(), + "stack object semantic categories must be derived exactly"); + + Context.expect(Frame.setObjectOffset(*Local, -8).has_value() && + Frame.setObjectOffset(*Spill, -16).has_value() && + Frame.markObjectDead(*Spill).has_value(), + "live stack objects must support controlled pre-freeze updates"); + Context.expect(Frame.object(*Spill)->description()->IsDead, + "dead stack object state must remain explicit"); + + Context.expect(Frame.eraseObject(*Fixed).has_value() && Frame.object(*Fixed) == nullptr && + Frame.liveObjectCount() == 3, + "erasing a stack object must leave an inaccessible tombstone"); + auto Replacement = Frame.createObject(fixedObject(4, 4)); + Context.expect(Replacement && Replacement->value() == 4, + "erased frame indices must never be reused"); + if (!Replacement) + return; + Context.expect(Frame.setObjectOffset(*Replacement, -24).has_value(), + "replacement stack object must accept a final offset"); + + Context.expect(Frame.setMaxCallFrameSize(24).has_value() && + Frame.freeze(64, *rcc::Alignment::create(16)).has_value() && + Frame.isFrozen() && Frame.staticStackSize() == 64 && + Frame.stackAlignment().bytes() == 16 && Frame.maxCallFrameSize() == 24 && + Frame.verify().has_value(), + "complete frame layout must freeze atomically"); + + Context.expect(!Frame.createObject(fixedObject(8, 8)) && !Frame.setObjectOffset(*Local, -40) && + !Frame.eraseObject(*Local) && !Frame.setMaxCallFrameSize(32) && + !Frame.freeze(64, *rcc::Alignment::create(16)), + "frozen frames must reject every semantic mutation"); +} + +void testInvalidDescriptions(TestContext &Context) { + rcc::MachineFrameInfo Frame; + + rcc::StackObjectDesc MissingSize; + Context.expect(!Frame.createObject(MissingSize), + "fixed-size stack object must reject a missing size"); + + auto ZeroSize = fixedObject(0, 1); + Context.expect(!Frame.createObject(ZeroSize), "fixed-size stack object must reject zero size"); + + rcc::StackObjectDesc InvalidVariable; + InvalidVariable.Kind = rcc::StackObjectKind::DynamicAlloca; + InvalidVariable.Size = 8; + Context.expect(!Frame.createObject(InvalidVariable), + "variable-sized stack object must reject a fixed size"); + + auto MissingFixedOffset = fixedObject(8, 8); + MissingFixedOffset.Kind = rcc::StackObjectKind::FixedABI; + Context.expect(!Frame.createObject(MissingFixedOffset), + "fixed ABI stack object must require an offset"); + + auto InvalidTarget = fixedObject(8, 8); + InvalidTarget.Kind = rcc::StackObjectKind::Target; + Context.expect(!Frame.createObject(InvalidTarget), + "target stack object must carry target identity"); + + auto InvalidLifetime = fixedObject(8, 8); + InvalidLifetime.Lifetime.push_back( + {rcc::MachineInstrStableID::fromValue(4), rcc::MachineInstrStableID::fromValue(4)}); + Context.expect(!Frame.createObject(InvalidLifetime), + "stack object lifetime must reject equal boundaries"); + + Context.expect(!rcc::StackID::targetStack(4), "target stack IDs must not overlap built-in IDs"); + auto TargetStack = rcc::StackID::targetStack(256); + auto Target = fixedObject(8, 8); + Target.Kind = rcc::StackObjectKind::Target; + if (TargetStack) + Target.Stack = *TargetStack; + auto TargetIndex = Frame.createObject(Target); + Context.expect(TargetStack && TargetIndex, + "target stack identity must admit target stack objects"); + + auto Unresolved = Frame.createObject(fixedObject(8, 8)); + Context.expect(Unresolved && !Frame.freeze(16, *rcc::Alignment::create(8)) && !Frame.isFrozen() && + !Frame.staticStackSize() && Frame.verify().has_value(), + "failed frame freeze must not mutate frame state"); +} + +} // namespace + +int main() { + TestContext Context; + testObjectLifecycle(Context); + testInvalidDescriptions(Context); + return Context.result(); +} diff --git a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp index 90e847b..3b2fce9 100644 --- a/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp +++ b/backend/unittests/CodeGen/MachineFunctionClonerTest.cpp @@ -6,6 +6,7 @@ #include "rcc/CodeGen/LowLevelType.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineIRBuilder.h" @@ -137,12 +138,12 @@ createFunction(TestContext &Context, std::string Name, std::uint64_t ID) { return *Block; } -[[nodiscard]] rcc::MachineMemOperandDesc loadDescription() { +[[nodiscard]] rcc::MachineMemOperandDesc loadDescription(rcc::FrameIndex Index) { rcc::MachineMemOperandDesc Description; Description.Accesses = {rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Dereferenceable}; Description.Size = *rcc::MemorySize::fixed(8); Description.Align = *rcc::Alignment::create(8); - Description.Base = rcc::MemoryBase::frameIndex(rcc::FrameIndex::fromValue(4)); + Description.Base = rcc::MemoryBase::frameIndex(Index); Description.Offset = -8; return Description; } @@ -162,6 +163,7 @@ struct SourceFixture final { rcc::MachineBasicBlock *Exit = nullptr; rcc::Register Value; rcc::Register CopyValue; + rcc::FrameIndex StackSlot; const rcc::MachineMemOperand *LoadMMO = nullptr; }; @@ -177,6 +179,25 @@ struct SourceFixture final { Context.expect(Tombstone && Function.registerInfo().eraseVirtualRegister(*Tombstone).has_value(), "source vreg history must contain a tombstone"); + rcc::StackObjectDesc HistoricalFrame; + HistoricalFrame.Size = 4; + HistoricalFrame.Align = *rcc::Alignment::create(4); + auto HistoricalFrameIndex = Function.frameInfo().createObject(HistoricalFrame); + Context.expect(HistoricalFrameIndex && + Function.frameInfo().eraseObject(*HistoricalFrameIndex).has_value(), + "source frame history must contain a tombstone"); + + rcc::StackObjectDesc StackSlot; + StackSlot.Kind = rcc::StackObjectKind::SpillSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + StackSlot.Offset = -16; + auto StackSlotIndex = Function.frameInfo().createObject(StackSlot); + Context.expect(StackSlotIndex.has_value(), "source live stack object must be created"); + if (!StackSlotIndex) + return false; + Fixture.StackSlot = *StackSlotIndex; + Fixture.Entry = createBlock(Context, Function); Fixture.Exit = createBlock(Context, Function); if (Fixture.Entry == nullptr || Fixture.Exit == nullptr) @@ -236,8 +257,7 @@ struct SourceFixture final { "source tied copy must build"); auto Frame = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); - Context.expect(Frame && Frame->addFrameIndex(rcc::FrameIndex::fromValue(4), -8).has_value() && - Frame->build(), + Context.expect(Frame && Frame->addFrameIndex(Fixture.StackSlot, -8).has_value() && Frame->build(), "source frame-index instruction must build"); auto Address = Builder.buildInstr(rcc::Opcode::fromValue(BlockAddressOpcode)); @@ -246,7 +266,7 @@ struct SourceFixture final { Address->build(), "source self block-address instruction must build"); - auto MMO = Function.createMemOperand(loadDescription()); + auto MMO = Function.createMemOperand(loadDescription(Fixture.StackSlot)); Context.expect(MMO.has_value(), "source MMO must be created"); if (!MMO) return false; @@ -255,6 +275,20 @@ struct SourceFixture final { Context.expect(Load && Load->addMemOperand(**MMO).has_value() && Load->build(), "source load with MMO must build"); + rcc::MachineInstr *Definition = findInstruction(*Fixture.Entry, DefOpcode); + rcc::MachineInstr *LoadInstruction = findInstruction(*Fixture.Exit, LoadOpcode); + const rcc::StackObject *Object = Function.frameInfo().object(Fixture.StackSlot); + if (Definition == nullptr || LoadInstruction == nullptr || Object == nullptr) + return false; + rcc::StackObjectDesc UpdatedStackSlot = *Object->description(); + UpdatedStackSlot.Lifetime.push_back({Definition->stableID(), LoadInstruction->stableID()}); + Context.expect(Function.frameInfo() + .updateObject(Fixture.StackSlot, std::move(UpdatedStackSlot)) + .has_value() && + Function.frameInfo().setMaxCallFrameSize(16).has_value() && + Function.frameInfo().freeze(32, *rcc::Alignment::create(16)).has_value(), + "source frame metadata must be finalized"); + Function.properties().set(rcc::MachineFunctionProperty::IsSSA); Function.properties().set(rcc::MachineFunctionProperty::TracksLiveness); Function.properties().set(rcc::MachineFunctionProperty::NoPHIs); @@ -289,7 +323,9 @@ void testDeepClone(TestContext &Context) { "source and destination must independently preserve valid properties"); Context.expect(Source->allBlocks().size() == 3 && Destination.allBlocks().size() == 2 && Source->allInstructions().size() == 7 && - Destination.allInstructions().size() == 6, + Destination.allInstructions().size() == 6 && + Source->frameInfo().objects().size() == 2 && + Destination.frameInfo().objects().size() == 1, "clone must omit detached arena history"); Context.expect(Clone->BlockStableIDs.size() == 2 && Clone->InstructionStableIDs.size() == 6 && Clone->VirtualRegisters.size() == 2, @@ -375,21 +411,39 @@ void testDeepClone(TestContext &Context) { DestinationCopy->operandAt(0)->tiedOperandIndex() == 1 && DestinationCopy->operandAt(1)->tiedOperandIndex() == 0, "clone must remap tied registers and rebuild ties"); - Context.expect(DestinationFrame->operandAt(0)->getIf<rcc::FrameIndexOperand>()->Index == - rcc::FrameIndex::fromValue(4) && - Clone->FrameIndices.contains(rcc::FrameIndex::fromValue(4)), - "clone must preserve and map frame indices"); + const auto FramePosition = Clone->FrameIndices.find(Fixture.StackSlot); + Context.expect(FramePosition != Clone->FrameIndices.end() && + FramePosition->second == rcc::FrameIndex::fromValue(0) && + DestinationFrame->operandAt(0)->getIf<rcc::FrameIndexOperand>()->Index == + FramePosition->second, + "clone must compact and remap frame indices"); Context.expect( DestinationAddress->operandAt(0)->getIf<rcc::BlockAddressOperand>()->FunctionName == "cloned_function", "clone must rewrite self block-address function names"); + rcc::MachineMemOperandDesc ExpectedMMO = Fixture.LoadMMO->description(); + if (FramePosition != Clone->FrameIndices.end()) + ExpectedMMO.Base = rcc::MemoryBase::frameIndex(FramePosition->second); Context.expect(DestinationLoad->memOperands().size() == 1 && DestinationLoad->memOperands()[0] != Fixture.LoadMMO && - *DestinationLoad->memOperands()[0] == *Fixture.LoadMMO && + DestinationLoad->memOperands()[0]->description() == ExpectedMMO && Destination.ownsMemOperand(*DestinationLoad->memOperands()[0]) && !Source->ownsMemOperand(*DestinationLoad->memOperands()[0]), - "clone must deep-copy MMO ownership while preserving semantics"); + "clone must deep-copy MMO ownership while remapping frame semantics"); + + const rcc::StackObject *DestinationStackSlot = + FramePosition == Clone->FrameIndices.end() + ? nullptr + : Destination.frameInfo().object(FramePosition->second); + Context.expect( + DestinationStackSlot != nullptr && Destination.frameInfo().isFrozen() && + Destination.frameInfo().staticStackSize() == 32 && + Destination.frameInfo().maxCallFrameSize() == 16 && + DestinationStackSlot->description()->Lifetime.size() == 1 && + DestinationStackSlot->description()->Lifetime[0].Begin == DestinationDef->stableID() && + DestinationStackSlot->description()->Lifetime[0].End == DestinationLoad->stableID(), + "clone must preserve frozen frame layout and remap instruction lifetimes"); rcc::MachineInstr *SourceDef = findInstruction(*Fixture.Entry, DefOpcode); const auto InstructionID = SourceDef == nullptr diff --git a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp index bda30b6..ba5e3db 100644 --- a/backend/unittests/CodeGen/MachineIRBuilderTest.cpp +++ b/backend/unittests/CodeGen/MachineIRBuilderTest.cpp @@ -356,6 +356,16 @@ void testImplicitAndMemoryValidation(TestContext &Context) { Function->allInstructions().size() == BeforeMissingMemory, "missing required MMO must fail before instruction allocation"); + rcc::StackObjectDesc StackSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + auto FrameIndex = Function->frameInfo().createObject(StackSlot); + auto ForeignFrameIndex = OtherFunction->frameInfo().createObject(StackSlot); + Context.expect(FrameIndex && ForeignFrameIndex, + "memory validation stack objects must be created"); + if (!FrameIndex || !ForeignFrameIndex) + return; + auto LoadMemory = Function->createMemOperand(memoryDescription(rcc::MemoryAccessFlag::Load)); auto StoreMemory = Function->createMemOperand(memoryDescription(rcc::MemoryAccessFlag::Store)); auto ForeignMemory = diff --git a/backend/unittests/CodeGen/MachineVerifierTest.cpp b/backend/unittests/CodeGen/MachineVerifierTest.cpp index b8b985e..82ae68b 100644 --- a/backend/unittests/CodeGen/MachineVerifierTest.cpp +++ b/backend/unittests/CodeGen/MachineVerifierTest.cpp @@ -346,12 +346,17 @@ void testPropertyFailures(TestContext &Context) { auto Frame = createFunction(Context, "frame_index", 9); rcc::MachineBasicBlock *FrameBlock = Frame ? createBlock(Context, *Frame) : nullptr; if (Frame && FrameBlock != nullptr) { + rcc::StackObjectDesc StackSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + auto FrameIndex = Frame->frameInfo().createObject(StackSlot); rcc::MachineIRBuilder Builder(*Frame, *InstructionSet); - Context.expect(Builder.setInsertPoint(*FrameBlock).has_value(), + Context.expect(FrameIndex && Builder.setInsertPoint(*FrameBlock).has_value(), "frame-index negative test setup must succeed"); + if (!FrameIndex) + return; auto Pending = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); - Context.expect(Pending && Pending->addFrameIndex(rcc::FrameIndex::fromValue(0)).has_value() && - Pending->build(), + Context.expect(Pending && Pending->addFrameIndex(*FrameIndex).has_value() && Pending->build(), "frame-index negative test instruction must build"); Frame->properties().set(rcc::MachineFunctionProperty::NoFrameIndices); Context.expect(!rcc::MachineVerifier::verify(*Frame, *InstructionSet), @@ -447,6 +452,47 @@ void testSelectedStateAndPseudoDeadline(TestContext &Context) { } } +void testFrameStateFailures(TestContext &Context) { + auto InstructionSet = createInstructionSet(); + auto Function = createFunction(Context, "frame_references", 14); + rcc::MachineBasicBlock *Block = Function ? createBlock(Context, *Function) : nullptr; + if (!InstructionSet || !Function || Block == nullptr) + return; + + rcc::MachineIRBuilder Builder(*Function, *InstructionSet); + Context.expect(Builder.setInsertPoint(*Block).has_value(), + "frame reference insertion point must be valid"); + auto Unknown = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); + Context.expect(Unknown && Unknown->addFrameIndex(rcc::FrameIndex::fromValue(99)).has_value() && + !Unknown->build() && Function->allInstructions().empty(), + "builder must reject an unknown frame index before instruction allocation"); + + rcc::StackObjectDesc StackSlot; + StackSlot.Size = 8; + StackSlot.Align = *rcc::Alignment::create(8); + auto FrameIndex = Function->frameInfo().createObject(StackSlot); + if (!FrameIndex) + return; + auto Live = Builder.buildInstr(rcc::Opcode::fromValue(FrameOpcode)); + Context.expect(Live && Live->addFrameIndex(*FrameIndex).has_value() && Live->build(), + "known live frame index must build"); + Context.expect(Function->frameInfo().markObjectDead(*FrameIndex).has_value() && + !rcc::MachineVerifier::verify(*Function, *InstructionSet), + "verifier must reject an instruction that references a dead stack object"); + + auto Lifetime = createFunction(Context, "frame_lifetime", 15); + if (!Lifetime) + return; + rcc::StackObjectDesc LifetimeSlot; + LifetimeSlot.Size = 8; + LifetimeSlot.Align = *rcc::Alignment::create(8); + LifetimeSlot.Lifetime.push_back( + {rcc::MachineInstrStableID::fromValue(1), rcc::MachineInstrStableID::fromValue(2)}); + Context.expect(Lifetime->frameInfo().createObject(LifetimeSlot).has_value() && + !rcc::MachineVerifier::verify(*Lifetime, *InstructionSet), + "verifier must reject stack lifetimes that reference unknown instructions"); +} + } // namespace int main() { @@ -455,5 +501,6 @@ int main() { testReachableStructuralFailures(Context); testPropertyFailures(Context); testSelectedStateAndPseudoDeadline(Context); + testFrameStateFailures(Context); return Context.result(); } |
