// SPDX-License-Identifier: GPL-3.0-or-later #include "MIRDocumentReferenceValidation.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineCompilationStatus.h" #include "rcc/CodeGen/MachineConstantPool.h" #include "rcc/CodeGen/MachineFrameInfo.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionAttributes.h" #include "rcc/CodeGen/MachineFunctionProperties.h" #include "rcc/CodeGen/MachineFunctionRecords.h" #include "rcc/CodeGen/MachineInstr.h" #include "rcc/CodeGen/MachineJumpTableInfo.h" #include "rcc/CodeGen/MachineMetadata.h" #include "rcc/CodeGen/MachineRegisterInfo.h" #include "rcc/CodeGen/TargetDescription.h" #include "rcc/CodeGen/TargetMIRTextInfo.h" #include "rcc/CodeGen/TargetRegisterInfo.h" #include "rcc/MIR/LLVMConstantSyntax.h" #include "rcc/MIR/MIRDocument.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rcc::mir { namespace { [[nodiscard]] std::string yamlScalar(std::string_view Value) { std::string Result; Result.reserve(Value.size() + 2U); Result.push_back('\''); for (const char Character : Value) { Result.push_back(Character); if (Character == '\'') Result.push_back('\''); } Result.push_back('\''); return Result; } void appendBlockScalar(std::string &Output, std::string_view Text, std::size_t Indent) { std::size_t Position = 0; do { Output.append(Indent, ' '); const std::size_t End = Text.find('\n', Position); if (End == std::string_view::npos) { Output.append(Text.substr(Position)); Output.push_back('\n'); return; } Output.append(Text.substr(Position, End - Position)); Output.push_back('\n'); Position = End + 1U; } while (Position < Text.size()); } [[nodiscard]] std::optional parseDecimal32(std::string_view Text) noexcept { std::uint32_t Value = 0; if (Text.empty()) return std::nullopt; const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, 10); if (Error != std::errc{} || End != Text.data() + Text.size()) return std::nullopt; return Value; } [[nodiscard]] bool isDefaultFrameState(const MachineFrameState &State) { return State == MachineFrameState{}; } class MIRDocumentPrinter final { public: MIRDocumentPrinter(const MIRDocument &Document, const TargetDescription &Target, MIRDocumentPrinterOptions Options) : Document(Document), Target(Target), Options(Options) {} [[nodiscard]] Expected run(); private: struct InstructionLocation final { std::uint32_t Block = 0; std::uint32_t Offset = 0; }; [[nodiscard]] Expected printFunction(const MachineFunction &Function, const MIRFunctionDocumentInfo *DocumentInfo); [[nodiscard]] Expected createBodyContext(const MachineFunction &Function) const; [[nodiscard]] Expected printRegisters(const MachineFunction &Function, const MIParserContext &Context); [[nodiscard]] Expected printFrameInfo(const MachineFunction &Function, const MIParserContext &Context); [[nodiscard]] Expected printStackObjects(const MachineFunction &Function, const MIParserContext &Context, bool Fixed); [[nodiscard]] Expected printConstants(const MachineFunction &Function, const MIParserContext &Context); [[nodiscard]] Expected printJumpTable(const MachineFunction &Function, const MIParserContext &Context); [[nodiscard]] Expected printMetadata(const MachineFunction &Function, const MIParserContext &Context); [[nodiscard]] Expected printTargetState(const MachineFunction &Function); [[nodiscard]] Expected printFunctionRecords(const MachineFunction &Function, const MIParserContext &Context); [[nodiscard]] Expected verifyOmittedEmbeddedIRLayout(const MachineFunction &Function) const; [[nodiscard]] Expected physicalRegister(PhysicalRegister Register) const; [[nodiscard]] Expected frameReference(FrameIndex Index, const MIParserContext &Context) const; [[nodiscard]] Expected metadataReference(MachineMetadataReference Reference, const MIParserContext &Context) const; [[nodiscard]] Expected metadataValue(const MachineMetadataValue &Value, const MIParserContext &Context) const; [[nodiscard]] Expected debugLocation(const MachineDebugLocation &Location, const MIParserContext &Context) const; [[nodiscard]] Expected diLocation(const MachineDILocation &Location, std::size_t FrameIndex, const MIParserContext &Context) const; [[nodiscard]] Expected instructionLocation(const MachineFunction &Function, MachineInstrStableID ID) const; [[nodiscard]] Expected blockReference(const MachineFunction &Function, MachineBlockStableID ID) const; [[nodiscard]] Expected targetNode(const MIRTargetNode &Node) const; [[nodiscard]] const MIRFunctionDocumentInfo * documentInfo(const MachineFunction &Function) const noexcept; void printBoolean(std::string_view Key, bool Value, bool Always = false); void printUnsigned(std::string_view Key, std::uint64_t Value, bool Always = false); void printSigned(std::string_view Key, std::int64_t Value, bool Always = false); const MIRDocument &Document; const TargetDescription &Target; MIRDocumentPrinterOptions Options; std::string Output; }; void MIRDocumentPrinter::printBoolean(std::string_view Key, bool Value, bool Always) { if (!Always && Options.Mode == MIPrintMode::Readable && !Value) return; Output.append(Key); Output.append(Value ? ": true\n" : ": false\n"); } void MIRDocumentPrinter::printUnsigned(std::string_view Key, std::uint64_t Value, bool Always) { if (!Always && Options.Mode == MIPrintMode::Readable && Value == 0) return; Output.append(Key); Output.append(": "); Output.append(std::to_string(Value)); Output.push_back('\n'); } void MIRDocumentPrinter::printSigned(std::string_view Key, std::int64_t Value, bool Always) { if (!Always && Options.Mode == MIPrintMode::Readable && Value == 0) return; Output.append(Key); Output.append(": "); Output.append(std::to_string(Value)); Output.push_back('\n'); } Expected MIRDocumentPrinter::physicalRegister(PhysicalRegister Register) const { const auto Name = Target.registers().physicalRegisterMIRName(Register); if (!Name) { return makeUnexpected(ErrorCode::VerificationError, "physical register has no canonical MIR name"); } return "$" + std::string(*Name); } const MIRFunctionDocumentInfo * MIRDocumentPrinter::documentInfo(const MachineFunction &Function) const noexcept { const auto Infos = Document.functionDocumentInfo(); const auto Found = std::ranges::find_if(Infos, [&Function](const MIRFunctionDocumentInfo &Info) { return Info.Function == Function.stableID(); }); return Found == Infos.end() ? nullptr : &*Found; } Expected MIRDocumentPrinter::verifyOmittedEmbeddedIRLayout(const MachineFunction &Function) const { if (Options.EmitEmbeddedIR) return {}; const EmbeddedIRReferenceIndex *Embedded = Document.embeddedIR(); if (Embedded == nullptr) return {}; const auto EmbeddedDataLayout = Embedded->dataLayout(); if (!EmbeddedDataLayout) return {}; bool HasLayoutDependentContent = false; for (const VirtualRegisterInfo &RegisterInfo : Function.registerInfo().virtualRegisters()) { if (!RegisterInfo.IsTombstone && RegisterInfo.Type && RegisterInfo.Type->isPointerOrPointerVector()) { HasLayoutDependentContent = true; break; } } if (!HasLayoutDependentContent) { for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) { const MachineConstantPoolEntryDesc *Description = Entry.description(); if (Description != nullptr && std::holds_alternative(Description->Value)) { HasLayoutDependentContent = true; break; } } } if (!HasLayoutDependentContent) return {}; if (Options.DataLayout != *EmbeddedDataLayout) { return makeUnexpected( ErrorCode::InvalidArgument, "omitting embedded LLVM IR requires printer DataLayout to exactly match its data layout"); } const LLVMConstantSyntaxOptions LayoutOptions{ .DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits, .DataLayout = Options.DataLayout, .NamedTypes = {}, }; for (const VirtualRegisterInfo &RegisterInfo : Function.registerInfo().virtualRegisters()) { if (RegisterInfo.IsTombstone || !RegisterInfo.Type || !RegisterInfo.Type->isPointerOrPointerVector()) { continue; } const auto AddressSpace = RegisterInfo.Type->addressSpace(); if (!AddressSpace) { return makeUnexpected(ErrorCode::VerificationError, "pointer virtual register has no address space"); } auto PointerSize = llvmPointerSizeInBits(*AddressSpace, LayoutOptions); if (!PointerSize) { return makeUnexpected(ErrorCode::InvalidArgument, "printer DataLayout cannot determine pointer width: " + std::string(PointerSize.error().message())); } if (*PointerSize != RegisterInfo.Type->scalarBitWidth()) { return makeUnexpected(ErrorCode::VerificationError, "printer DataLayout does not match pointer virtual-register width"); } } for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) { const MachineConstantPoolEntryDesc *Description = Entry.description(); if (Description == nullptr) continue; const auto *Generic = std::get_if(&Description->Value); if (Generic == nullptr) continue; auto StorageSize = llvmConstantStorageSize(Generic->Type, LayoutOptions); if (!StorageSize) { return makeUnexpected(ErrorCode::InvalidArgument, "printer DataLayout cannot lay out generic constant: " + std::string(StorageSize.error().message())); } if (*StorageSize != Description->Size) { return makeUnexpected(ErrorCode::VerificationError, "printer DataLayout does not match generic constant storage size"); } } return {}; } Expected MIRDocumentPrinter::createBodyContext(const MachineFunction &Function) const { MIParserContext Context; Context.SyncScopes.assign(Document.syncScopes().begin(), Document.syncScopes().end()); const auto VirtualRegisters = Function.registerInfo().virtualRegisters(); for (std::size_t Index = 0; Index < VirtualRegisters.size(); ++Index) { if (VirtualRegisters[Index].IsTombstone) continue; auto Reg = Register::makeVirtual(Index); if (!Reg) return std::unexpected(Reg.error()); if (Index > std::numeric_limits::max()) { return makeUnexpected(ErrorCode::Unsupported, "MIR cannot represent more than 2^32 virtual registers"); } Context.VirtualRegisters.push_back({.TextID = static_cast(Index), .Reg = *Reg}); } std::uint32_t StackID = 0; std::uint32_t FixedID = 0; for (const StackObject &Object : Function.frameInfo().objects()) { const StackObjectDesc *Description = Object.description(); if (Description == nullptr) continue; MIFrameObjectSlot Slot{ .TextID = Object.isFixed() ? FixedID++ : StackID++, .Index = Object.index(), .Name = Description->AllocaName, }; (Object.isFixed() ? Context.FixedStackObjects : Context.StackObjects) .push_back(std::move(Slot)); } std::uint32_t ConstantID = 0; for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) { if (Entry.description() != nullptr) Context.ConstantPoolItems.push_back({.TextID = ConstantID++, .Index = Entry.index()}); } std::uint32_t JumpTableID = 0; for (const MachineJumpTableEntry &Entry : Function.jumpTableInfo().tables()) { if (Entry.description() != nullptr) Context.JumpTables.push_back({.TextID = JumpTableID++, .Index = Entry.index()}); } std::unordered_set EmbeddedMetadataSlots; if (const EmbeddedIRReferenceIndex *Embedded = Document.embeddedIR()) { for (const EmbeddedIRMetadataDefinition &Definition : Embedded->metadataDefinitions()) { const auto Slot = parseDecimal32(Definition.Name); if (Slot) { if (!EmbeddedMetadataSlots.insert(*Slot).second) { return makeUnexpected(ErrorCode::VerificationError, "embedded LLVM IR metadata slots are not unique"); } Context.Metadata.push_back( {.TextID = *Slot, .Reference = MachineMetadataReference::embeddedIR(*Slot)}); } } } std::unordered_set UsedMetadataSlots = EmbeddedMetadataSlots; for (const MachineMetadataNode &Node : Function.metadata().nodes()) { if (!EmbeddedMetadataSlots.contains(Node.Slot)) UsedMetadataSlots.insert(Node.Slot); } std::uint64_t NextMetadataSlot = 0; for (const MachineMetadataNode &Node : Function.metadata().nodes()) { std::uint32_t TextID = Node.Slot; if (EmbeddedMetadataSlots.contains(TextID)) { while (NextMetadataSlot <= std::numeric_limits::max() && UsedMetadataSlots.contains(static_cast(NextMetadataSlot))) { ++NextMetadataSlot; } if (NextMetadataSlot > std::numeric_limits::max()) { return makeUnexpected(ErrorCode::Unsupported, "MIR metadata text slot space is exhausted"); } TextID = static_cast(NextMetadataSlot); UsedMetadataSlots.insert(TextID); ++NextMetadataSlot; } Context.Metadata.push_back( {.TextID = TextID, .Reference = MachineMetadataReference::machineFunction(Node.Slot)}); } return Context; } Expected MIRDocumentPrinter::frameReference(FrameIndex Index, const MIParserContext &Context) const { const auto Stack = std::ranges::find_if( Context.StackObjects, [Index](const MIFrameObjectSlot &Slot) { return Slot.Index == Index; }); if (Stack != Context.StackObjects.end()) return "%stack." + std::to_string(Stack->TextID); const auto Fixed = std::ranges::find_if(Context.FixedStackObjects, [Index](const MIFrameObjectSlot &Slot) { return Slot.Index == Index; }); if (Fixed != Context.FixedStackObjects.end()) return "%fixed-stack." + std::to_string(Fixed->TextID); return makeUnexpected(ErrorCode::VerificationError, "frame state references an unknown stack object"); } Expected MIRDocumentPrinter::metadataReference(MachineMetadataReference Reference, const MIParserContext &Context) const { if (!Reference.isValid()) return makeUnexpected(ErrorCode::VerificationError, "invalid metadata reference"); const auto Slot = std::ranges::find_if(Context.Metadata, [Reference](const MIMetadataSlot &Candidate) { return Candidate.Reference == Reference; }); if (Slot == Context.Metadata.end()) { return makeUnexpected(ErrorCode::VerificationError, "metadata reference has no MIR text slot"); } return "!" + std::to_string(Slot->TextID); } Expected MIRDocumentPrinter::metadataValue(const MachineMetadataValue &Value, const MIParserContext &Context) const { if (const MachineMetadataReference *Reference = Value.reference()) return metadataReference(*Reference, Context); const MachineDIExpression *Expression = Value.expression(); if (Expression == nullptr) return makeUnexpected(ErrorCode::VerificationError, "metadata value is not printable"); std::string Text = "!DIExpression("; for (std::size_t Index = 0; Index < Expression->elements().size(); ++Index) { if (Index != 0) Text.append(", "); Text.append(std::to_string(Expression->elements()[Index])); } Text.push_back(')'); return Text; } Expected MIRDocumentPrinter::diLocation(const MachineDILocation &Location, std::size_t FrameIndex, const MIParserContext &Context) const { if (FrameIndex >= Location.inlineChain().size()) { return makeUnexpected(ErrorCode::VerificationError, "inline DILocation has an invalid frame chain"); } const MachineDILocationFrame &Frame = Location.inlineChain()[FrameIndex]; auto Scope = metadataReference(Frame.Scope, Context); if (!Scope) return std::unexpected(Scope.error()); std::string Text = "!DILocation(line: " + std::to_string(Frame.Line); if (Options.Mode == MIPrintMode::Complete || Frame.Column != 0) Text.append(", column: " + std::to_string(Frame.Column)); Text.append(", scope: " + *Scope); if (FrameIndex + 1U < Location.inlineChain().size()) { auto Nested = diLocation(Location, FrameIndex + 1U, Context); if (!Nested) return std::unexpected(Nested.error()); Text.append(", inlinedAt: " + *Nested); } else if (Location.terminalInlinedAt()) { auto Terminal = metadataReference(*Location.terminalInlinedAt(), Context); if (!Terminal) return std::unexpected(Terminal.error()); Text.append(", inlinedAt: " + *Terminal); } if (Options.Mode == MIPrintMode::Complete || Frame.IsImplicitCode) Text.append(Frame.IsImplicitCode ? ", isImplicitCode: true" : ", isImplicitCode: false"); if (Options.Mode == MIPrintMode::Complete || Frame.AtomGroup != 0) Text.append(", atomGroup: " + std::to_string(Frame.AtomGroup)); if (Options.Mode == MIPrintMode::Complete || Frame.AtomRank != 0) Text.append(", atomRank: " + std::to_string(Frame.AtomRank)); Text.push_back(')'); return Text; } Expected MIRDocumentPrinter::debugLocation(const MachineDebugLocation &Location, const MIParserContext &Context) const { if (const MachineMetadataReference *Reference = Location.reference()) return metadataReference(*Reference, Context); if (const MachineDILocation *Inline = Location.inlineLocation()) return diLocation(*Inline, 0, Context); return makeUnexpected(ErrorCode::VerificationError, "debug location is not printable"); } Expected MIRDocumentPrinter::instructionLocation(const MachineFunction &Function, MachineInstrStableID ID) const { std::uint64_t BlockNumber = 0; for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next(), ++BlockNumber) { if (BlockNumber > std::numeric_limits::max()) break; std::uint64_t Offset = 0; for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; Instruction = Instruction->next(), ++Offset) { if (Instruction->stableID() != ID) continue; if (Offset > std::numeric_limits::max()) { return makeUnexpected(ErrorCode::Unsupported, "MIR instruction offset exceeds 32 bits"); } return InstructionLocation{static_cast(BlockNumber), static_cast(Offset)}; } } return makeUnexpected(ErrorCode::VerificationError, "function record references an unlinked instruction"); } Expected MIRDocumentPrinter::blockReference(const MachineFunction &Function, MachineBlockStableID ID) const { std::uint64_t Number = 0; for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next(), ++Number) { if (Block->stableID() == ID) { if (Number > std::numeric_limits::max()) break; return "%bb." + std::to_string(Number); } } return makeUnexpected(ErrorCode::VerificationError, "frame record references an unlinked basic block"); } Expected MIRDocumentPrinter::targetNode(const MIRTargetNode &Node) const { switch (Node.Kind) { case MIRTargetNodeKind::Null: return std::string("null"); case MIRTargetNodeKind::Scalar: return yamlScalar(Node.Scalar); case MIRTargetNodeKind::Sequence: { std::string Text = "[ "; for (std::size_t Index = 0; Index < Node.Sequence.size(); ++Index) { if (Index != 0) Text.append(", "); auto Element = targetNode(Node.Sequence[Index]); if (!Element) return std::unexpected(Element.error()); Text.append(*Element); } Text.append(" ]"); return Text; } case MIRTargetNodeKind::Mapping: { std::string Text = "{ "; for (std::size_t Index = 0; Index < Node.Mapping.size(); ++Index) { if (Index != 0) Text.append(", "); Text.append(yamlScalar(Node.Mapping[Index].Key)); Text.append(": "); auto Value = targetNode(Node.Mapping[Index].Value); if (!Value) return std::unexpected(Value.error()); Text.append(*Value); } Text.append(" }"); return Text; } case MIRTargetNodeKind::Count: break; } return makeUnexpected(ErrorCode::VerificationError, "target MIR node has an invalid kind"); } Expected MIRDocumentPrinter::printRegisters(const MachineFunction &Function, const MIParserContext &Context) { if (Context.VirtualRegisters.empty()) { if (Options.Mode == MIPrintMode::Complete) Output.append("registers: []\n"); } else { Output.append("registers:\n"); for (const MIVirtualRegisterSlot &Slot : Context.VirtualRegisters) { auto InfoResult = Function.registerInfo().virtualRegisterInfo(Slot.Reg); if (!InfoResult) return std::unexpected(InfoResult.error()); const VirtualRegisterInfo &Info = InfoResult->get(); std::string Class = "_"; if (Info.RegClass) { const auto Name = Target.registers().registerClassMIRName(*Info.RegClass); if (!Name) return makeUnexpected(ErrorCode::VerificationError, "virtual register class has no canonical MIR name"); Class = *Name; } else if (Info.Bank) { const auto Name = Target.registers().registerBankMIRName(*Info.Bank); if (!Name) return makeUnexpected(ErrorCode::VerificationError, "virtual register bank has no canonical MIR name"); Class = *Name; } if (Info.AllocationHints.size() > 1) { return makeUnexpected( ErrorCode::Unsupported, "fixed LLVM MIR can serialize only one simple preferred-register hint"); } Output.append(" - { id: "); Output.append(std::to_string(Slot.TextID)); Output.append(", class: "); Output.append(yamlScalar(Class)); if (!Info.AllocationHints.empty()) { auto Preferred = physicalRegister(Info.AllocationHints.front()); if (!Preferred) return std::unexpected(Preferred.error()); Output.append(", preferred-register: "); Output.append(yamlScalar(*Preferred)); } if (Info.TargetFlags != 0) { auto Names = Target.mirText().virtualRegisterFlagNames(Info.TargetFlags); if (!Names) return std::unexpected(Names.error()); Output.append(", flags: [ "); for (std::size_t Index = 0; Index < Names->size(); ++Index) { if (Index != 0) Output.append(", "); Output.append(yamlScalar((*Names)[Index])); } Output.append(" ]"); } Output.append(" }\n"); } } if (Function.registerInfo().liveIns().empty()) { if (Options.Mode == MIPrintMode::Complete) Output.append("liveins: []\n"); } else { Output.append("liveins:\n"); for (const PhysicalRegisterLiveIn &LiveIn : Function.registerInfo().liveIns()) { auto Name = physicalRegister(LiveIn.PhysReg); if (!Name) return std::unexpected(Name.error()); Output.append(" - { reg: "); Output.append(yamlScalar(*Name)); if (LiveIn.InitialVirtualRegister) { const auto Slot = std::ranges::find_if( Context.VirtualRegisters, [&LiveIn](const MIVirtualRegisterSlot &Candidate) { return Candidate.Reg == *LiveIn.InitialVirtualRegister; }); if (Slot == Context.VirtualRegisters.end()) { return makeUnexpected(ErrorCode::VerificationError, "live-in references an unknown virtual register"); } Output.append(", virtual-reg: "); Output.append(yamlScalar("%" + std::to_string(Slot->TextID))); } Output.append(" }\n"); } } if (Function.records().hasCalleeSavedRegisters()) { Output.append("calleeSavedRegisters: [ "); const auto Registers = Function.records().calleeSavedRegisters(); for (std::size_t Index = 0; Index < Registers.size(); ++Index) { if (Index != 0) Output.append(", "); auto Name = physicalRegister(Registers[Index]); if (!Name) return std::unexpected(Name.error()); Output.append(yamlScalar(*Name)); } Output.append(" ]\n"); } return {}; } Expected MIRDocumentPrinter::printFrameInfo(const MachineFunction &Function, const MIParserContext &Context) { const MachineFrameState &State = Function.frameInfo().state(); if (Options.Mode == MIPrintMode::Readable && isDefaultFrameState(State)) return {}; if (State.MaxCallFrameSize && *State.MaxCallFrameSize > std::numeric_limits::max()) { return makeUnexpected(ErrorCode::Unsupported, "fixed LLVM MIR maxCallFrameSize is limited to 32 bits"); } Output.append("frameInfo:\n"); const auto Bool = [this](std::string_view Key, bool Value) { if (Options.Mode == MIPrintMode::Readable && !Value) return; Output.append(" "); Output.append(Key); Output.append(Value ? ": true\n" : ": false\n"); }; const auto Unsigned = [this](std::string_view Key, std::uint64_t Value) { if (Options.Mode == MIPrintMode::Readable && Value == 0) return; Output.append(" "); Output.append(Key); Output.append(": "); Output.append(std::to_string(Value)); Output.push_back('\n'); }; Bool("isFrameAddressTaken", State.IsFrameAddressTaken); Bool("isReturnAddressTaken", State.IsReturnAddressTaken); Bool("hasStackMap", State.HasStackMap); Bool("hasPatchPoint", State.HasPatchPoint); Unsigned("stackSize", State.StackSize); if (Options.Mode == MIPrintMode::Complete || State.OffsetAdjustment != 0) { Output.append(" offsetAdjustment: "); Output.append(std::to_string(State.OffsetAdjustment)); Output.push_back('\n'); } Unsigned("maxAlignment", State.MaxAlignment ? State.MaxAlignment->bytes() : 0); Bool("adjustsStack", State.AdjustsStack); Bool("hasCalls", State.HasCalls); if (Options.Mode == MIPrintMode::Complete || State.FramePointerPolicy != MachineFramePointerPolicy::None) { Output.append(" framePointerPolicy: "); Output.append(getMachineFramePointerPolicyName(State.FramePointerPolicy)); Output.push_back('\n'); } if (State.StackProtector) { auto Reference = frameReference(*State.StackProtector, Context); if (!Reference) return std::unexpected(Reference.error()); Output.append(" stackProtector: "); Output.append(yamlScalar(*Reference)); Output.push_back('\n'); } if (State.FunctionContext) { auto Reference = frameReference(*State.FunctionContext, Context); if (!Reference) return std::unexpected(Reference.error()); Output.append(" functionContext: "); Output.append(yamlScalar(*Reference)); Output.push_back('\n'); } if (State.MaxCallFrameSize) Unsigned("maxCallFrameSize", *State.MaxCallFrameSize); Unsigned("cvBytesOfCalleeSavedRegisters", State.CVBytesOfCalleeSavedRegisters); Bool("hasOpaqueSPAdjustment", State.HasOpaqueSPAdjustment); Bool("hasVAStart", State.HasVAStart); Bool("hasMustTailInVarArgFunc", State.HasMustTailInVarArgFunc); Bool("hasTailCall", State.HasTailCall); Bool("isCalleeSavedInfoValid", State.IsCalleeSavedInfoValid); Unsigned("localFrameSize", State.LocalFrameSize); const auto PrintPoints = [this, &Function](std::string_view Key, std::span Points) -> Expected { if (Points.empty()) return {}; Output.append(" "); Output.append(Key); Output.append(":\n"); for (const MachineFrameSaveRestorePoint &Point : Points) { auto Block = blockReference(Function, Point.Block); if (!Block) return std::unexpected(Block.error()); Output.append(" - { point: "); Output.append(yamlScalar(*Block)); if (!Point.Registers.empty()) { Output.append(", registers: [ "); for (std::size_t Index = 0; Index < Point.Registers.size(); ++Index) { if (Index != 0) Output.append(", "); auto Name = physicalRegister(Point.Registers[Index]); if (!Name) return std::unexpected(Name.error()); Output.append(yamlScalar(*Name)); } Output.append(" ]"); } Output.append(" }\n"); } return {}; }; if (auto Printed = PrintPoints("savePoint", State.SavePoints); !Printed) return Printed; return PrintPoints("restorePoint", State.RestorePoints); } Expected MIRDocumentPrinter::printStackObjects(const MachineFunction &Function, const MIParserContext &Context, bool Fixed) { const auto &Slots = Fixed ? Context.FixedStackObjects : Context.StackObjects; if (Slots.empty()) { if (Options.Mode == MIPrintMode::Complete) Output.append(Fixed ? "fixedStack: []\n" : "stack: []\n"); return {}; } Output.append(Fixed ? "fixedStack:\n" : "stack:\n"); for (const MIFrameObjectSlot &Slot : Slots) { const StackObject *Object = Function.frameInfo().object(Slot.Index); const StackObjectDesc *Description = Object == nullptr ? nullptr : Object->description(); if (Description == nullptr) return makeUnexpected(ErrorCode::VerificationError, "MIR stack slot is a tombstone"); if (Description->IsDead || Description->AddressTaken || !Description->Lifetime.empty() || Description->TargetFlags != 0) { return makeUnexpected(ErrorCode::Unsupported, "stack object contains state not represented by fixed LLVM MIR"); } std::string_view Type; switch (Description->Kind) { case StackObjectKind::Default: case StackObjectKind::FixedABI: Type = "default"; break; case StackObjectKind::FixedSpillSlot: case StackObjectKind::SpillSlot: Type = "spill-slot"; break; case StackObjectKind::VariableSized: Type = "variable-sized"; break; case StackObjectKind::DynamicAlloca: case StackObjectKind::CalleeSaved: case StackObjectKind::EmergencySpill: case StackObjectKind::Target: case StackObjectKind::Count: return makeUnexpected(ErrorCode::Unsupported, "stack object kind has no fixed LLVM MIR representation"); } Output.append(" - { id: "); Output.append(std::to_string(Slot.TextID)); if (!Fixed && Description->AllocaName) { Output.append(", name: "); Output.append(yamlScalar(*Description->AllocaName)); } if (Options.Mode == MIPrintMode::Complete || Type != "default") { Output.append(", type: "); Output.append(Type); } if (Options.Mode == MIPrintMode::Complete || Description->Offset.value_or(0) != 0) { Output.append(", offset: "); Output.append(std::to_string(Description->Offset.value_or(0))); } if (Description->Kind != StackObjectKind::VariableSized) { Output.append(", size: "); Output.append(std::to_string(Description->Size.value_or(0))); } if (Options.Mode == MIPrintMode::Complete || Description->Align.bytes() != 1) { Output.append(", alignment: "); Output.append(std::to_string(Description->Align.bytes())); } if (Options.Mode == MIPrintMode::Complete || Description->Stack != StackID::defaultStack()) { Output.append(", stack-id: "); Output.append(getStackIDName(Description->Stack)); } if (Fixed && Description->Kind != StackObjectKind::FixedSpillSlot) { if (Options.Mode == MIPrintMode::Complete || Description->IsImmutable) Output.append(Description->IsImmutable ? ", isImmutable: true" : ", isImmutable: false"); if (Options.Mode == MIPrintMode::Complete || Description->IsAliased) Output.append(Description->IsAliased ? ", isAliased: true" : ", isAliased: false"); } else if (!Fixed && (Description->IsImmutable || Description->IsAliased)) { return makeUnexpected(ErrorCode::Unsupported, "ordinary stack object immutable/aliased state is not serialized"); } if (Description->CalleeSavedRegister) { auto Name = physicalRegister(*Description->CalleeSavedRegister); if (!Name) return std::unexpected(Name.error()); Output.append(", callee-saved-register: "); Output.append(yamlScalar(*Name)); } if (Options.Mode == MIPrintMode::Complete || !Description->CalleeSavedRestored) { Output.append(Description->CalleeSavedRestored ? ", callee-saved-restored: true" : ", callee-saved-restored: false"); } if (!Fixed && Description->LocalOffset) { Output.append(", local-offset: "); Output.append(std::to_string(*Description->LocalOffset)); } if (Description->DebugInfo) { const MachineVariableDebugInfo &Debug = *Description->DebugInfo; if (Debug.Variable) { auto Value = metadataReference(*Debug.Variable, Context); if (!Value) return std::unexpected(Value.error()); Output.append(", debug-info-variable: "); Output.append(yamlScalar(*Value)); } if (Debug.Expression) { auto Value = metadataValue(*Debug.Expression, Context); if (!Value) return std::unexpected(Value.error()); Output.append(", debug-info-expression: "); Output.append(yamlScalar(*Value)); } if (Debug.Location) { auto Value = debugLocation(*Debug.Location, Context); if (!Value) return std::unexpected(Value.error()); Output.append(", debug-info-location: "); Output.append(yamlScalar(*Value)); } } Output.append(" }\n"); } return {}; } Expected MIRDocumentPrinter::printConstants(const MachineFunction &Function, const MIParserContext &Context) { if (Context.ConstantPoolItems.empty()) { if (Options.Mode == MIPrintMode::Complete) Output.append("constants: []\n"); return {}; } Output.append("constants:\n"); for (const MIConstantPoolSlot &Slot : Context.ConstantPoolItems) { const MachineConstantPoolEntry *Entry = Function.constantPool().entry(Slot.Index); const MachineConstantPoolEntryDesc *Description = Entry == nullptr ? nullptr : Entry->description(); if (Description == nullptr) return makeUnexpected(ErrorCode::VerificationError, "constant-pool slot is a tombstone"); if (Description->Merge != ConstantPoolMergeKind::Never || Description->Relocation != ConstantPoolRelocationMode::None || Description->Placement != ConstantPoolPlacement{}) { return makeUnexpected(ErrorCode::Unsupported, "constant-pool placement state is not represented by fixed LLVM MIR"); } bool IsTargetSpecific = false; Expected Value = makeUnexpected(ErrorCode::Unsupported, "constant-pool kind is not MIR serializable"); if (const auto *Generic = std::get_if(&Description->Value)) { Value = printLLVMConstant(*Generic); } else if (const auto *TargetConstantValue = std::get_if(&Description->Value)) { IsTargetSpecific = true; Value = Target.mirText().printTargetConstant(*TargetConstantValue); } if (!Value) return std::unexpected(Value.error()); Output.append(" - id: "); Output.append(std::to_string(Slot.TextID)); Output.append("\n value: "); Output.append(yamlScalar(*Value)); Output.append("\n alignment: "); Output.append(std::to_string(Description->Align.bytes())); Output.push_back('\n'); if (IsTargetSpecific) Output.append(" isTargetSpecific: true\n"); } return {}; } Expected MIRDocumentPrinter::printJumpTable(const MachineFunction &Function, const MIParserContext &Context) { if (Context.JumpTables.empty()) return {}; const MachineJumpTableEntry *FirstEntry = Function.jumpTableInfo().table(Context.JumpTables.front().Index); const MachineJumpTableDesc *First = FirstEntry == nullptr ? nullptr : FirstEntry->description(); if (First == nullptr) return makeUnexpected(ErrorCode::VerificationError, "jump-table slot is not defined"); std::string_view Kind; std::uint8_t ExpectedSize = 8; switch (First->Encoding) { case JumpTableEntryEncoding::BlockAddress: Kind = "block-address"; break; case JumpTableEntryEncoding::GPRelative64BlockAddress: Kind = "gp-rel64-block-address"; break; case JumpTableEntryEncoding::GPRelative32BlockAddress: Kind = "gp-rel32-block-address"; ExpectedSize = 4; break; case JumpTableEntryEncoding::LabelDifference32: Kind = "label-difference32"; ExpectedSize = 4; break; case JumpTableEntryEncoding::LabelDifference64: Kind = "label-difference64"; break; case JumpTableEntryEncoding::Inline: Kind = "inline"; ExpectedSize = 4; break; case JumpTableEntryEncoding::Custom32: Kind = "custom32"; ExpectedSize = 4; break; case JumpTableEntryEncoding::Target: case JumpTableEntryEncoding::Count: return makeUnexpected(ErrorCode::Unsupported, "target jump-table encoding has no fixed LLVM MIR spelling"); } Output.append("jumpTable:\n kind: "); Output.append(Kind); Output.append("\n entries:\n"); for (const MIJumpTableSlot &Slot : Context.JumpTables) { const MachineJumpTableEntry *Entry = Function.jumpTableInfo().table(Slot.Index); const MachineJumpTableDesc *Description = Entry == nullptr ? nullptr : Entry->description(); if (Description == nullptr) return makeUnexpected(ErrorCode::VerificationError, "jump-table slot is not defined"); if (Description->Encoding != First->Encoding || Description->EntrySize != ExpectedSize || Description->Align.bytes() != ExpectedSize || Description->Placement != JumpTablePlacement{} || Description->TargetEncoding != 0) { return makeUnexpected(ErrorCode::Unsupported, "jump-table entries cannot share one fixed LLVM MIR kind"); } Output.append(" - { id: "); Output.append(std::to_string(Slot.TextID)); Output.append(", blocks: [ "); for (std::size_t Index = 0; Index < Description->Targets.size(); ++Index) { if (Index != 0) Output.append(", "); if (Description->Targets[Index] == nullptr) return makeUnexpected(ErrorCode::VerificationError, "jump-table entry contains a null block"); auto Block = blockReference(Function, Description->Targets[Index]->stableID()); if (!Block) return std::unexpected(Block.error()); Output.append(yamlScalar(*Block)); } Output.append(" ] }\n"); } return {}; } Expected MIRDocumentPrinter::printMetadata(const MachineFunction &Function, const MIParserContext &Context) { if (Function.metadata().nodes().empty()) return {}; Output.append("machineMetadataNodes:\n"); for (const MachineMetadataNode &Node : Function.metadata().nodes()) { auto NodeReference = metadataReference(MachineMetadataReference::machineFunction(Node.Slot), Context); if (!NodeReference) return std::unexpected(NodeReference.error()); std::string Text = *NodeReference + " = "; if (Node.IsDistinct) Text.append("distinct "); Text.append("!{"); for (std::size_t Index = 0; Index < Node.Elements.size(); ++Index) { if (Index != 0) Text.append(", "); const MachineMetadataElement &Element = Node.Elements[Index]; if (Element.isNull()) { Text.append("null"); } else if (const MachineMetadataReference *Reference = Element.reference()) { auto Printed = metadataReference(*Reference, Context); if (!Printed) return std::unexpected(Printed.error()); Text.append(*Printed); } else if (const std::string *String = Element.string()) { Text.append("!\""); for (const char Character : *String) { if (Character == '\\' || Character == '"') Text.push_back('\\'); Text.push_back(Character); } Text.push_back('"'); } else { return makeUnexpected(ErrorCode::VerificationError, "machine metadata tuple contains an invalid element"); } } Text.push_back('}'); Output.append(" - "); Output.append(yamlScalar(Text)); Output.push_back('\n'); } return {}; } Expected MIRDocumentPrinter::printTargetState(const MachineFunction &Function) { if (!Function.targetState().hasValue()) return {}; auto Node = Target.mirText().printFunctionState(Function.targetState()); if (!Node) return std::unexpected(Node.error()); auto Text = targetNode(*Node); if (!Text) return std::unexpected(Text.error()); Output.append("machineFunctionInfo: "); Output.append(*Text); Output.push_back('\n'); return {}; } Expected MIRDocumentPrinter::printFunctionRecords(const MachineFunction &Function, const MIParserContext &Context) { const MachineFunctionRecords &Records = Function.records(); if (!Records.entryValues().empty()) { Output.append("entry_values:\n"); for (const MachineEntryValueInfo &Entry : Records.entryValues()) { auto RegisterName = physicalRegister(Entry.Reg); if (!RegisterName) return std::unexpected(RegisterName.error()); if (!Entry.DebugInfo.Variable || !Entry.DebugInfo.Expression || !Entry.DebugInfo.Location) { return makeUnexpected(ErrorCode::Unsupported, "MIR entry value requires complete debug metadata"); } auto Variable = metadataReference(*Entry.DebugInfo.Variable, Context); auto Expression = metadataValue(*Entry.DebugInfo.Expression, Context); auto Location = debugLocation(*Entry.DebugInfo.Location, Context); if (!Variable) return std::unexpected(Variable.error()); if (!Expression) return std::unexpected(Expression.error()); if (!Location) return std::unexpected(Location.error()); Output.append(" - { entry-value-register: "); Output.append(yamlScalar(*RegisterName)); Output.append(", debug-info-variable: "); Output.append(yamlScalar(*Variable)); Output.append(", debug-info-expression: "); Output.append(yamlScalar(*Expression)); Output.append(", debug-info-location: "); Output.append(yamlScalar(*Location)); Output.append(" }\n"); } } else if (Options.Mode == MIPrintMode::Complete) { Output.append("entry_values: []\n"); } if (!Records.callSites().empty()) { Output.append("callSites:\n"); for (const MachineCallSiteInfo &CallSite : Records.callSites()) { auto Location = instructionLocation(Function, CallSite.CallInstruction); if (!Location) return std::unexpected(Location.error()); Output.append(" - { bb: "); Output.append(std::to_string(Location->Block)); Output.append(", offset: "); Output.append(std::to_string(Location->Offset)); if (!CallSite.ForwardedArguments.empty()) { Output.append(", fwdArgRegs: [ "); for (std::size_t Index = 0; Index < CallSite.ForwardedArguments.size(); ++Index) { if (Index != 0) Output.append(", "); const MachineCallSiteArgument &Argument = CallSite.ForwardedArguments[Index]; auto RegisterName = physicalRegister(Argument.Reg); if (!RegisterName) return std::unexpected(RegisterName.error()); Output.append("{ arg: "); Output.append(std::to_string(Argument.ArgumentNumber)); Output.append(", reg: "); Output.append(yamlScalar(*RegisterName)); Output.append(" }"); } Output.append(" ]"); } if (!CallSite.CalleeTypeIDs.empty()) { Output.append(", calleeTypeIds: [ "); for (std::size_t Index = 0; Index < CallSite.CalleeTypeIDs.size(); ++Index) { if (Index != 0) Output.append(", "); Output.append(std::to_string(CallSite.CalleeTypeIDs[Index])); } Output.append(" ]"); } Output.append(" }\n"); } } else if (Options.Mode == MIPrintMode::Complete) { Output.append("callSites: []\n"); } if (!Records.debugValueSubstitutions().empty()) { Output.append("debugValueSubstitutions:\n"); for (const MachineDebugValueSubstitution &Substitution : Records.debugValueSubstitutions()) { Output.append(" - { srcinst: "); Output.append(std::to_string(Substitution.Source.InstructionNumber)); Output.append(", srcop: "); Output.append(std::to_string(Substitution.Source.OperandNumber)); Output.append(", dstinst: "); Output.append(std::to_string(Substitution.Destination.InstructionNumber)); Output.append(", dstop: "); Output.append(std::to_string(Substitution.Destination.OperandNumber)); Output.append(", subreg: "); Output.append(std::to_string(Substitution.Subregister.value())); Output.append(" }\n"); } } else if (Options.Mode == MIPrintMode::Complete) { Output.append("debugValueSubstitutions: []\n"); } if (!Records.calledGlobals().empty()) { Output.append("calledGlobals:\n"); for (const MachineCalledGlobal &Called : Records.calledGlobals()) { auto Location = instructionLocation(Function, Called.CallInstruction); if (!Location) return std::unexpected(Location.error()); Output.append(" - { bb: "); Output.append(std::to_string(Location->Block)); Output.append(", offset: "); Output.append(std::to_string(Location->Offset)); Output.append(", callee: "); Output.append(yamlScalar(Called.Callee)); Output.append(", flags: "); Output.append(std::to_string(Called.Flags)); Output.append(" }\n"); } } if (!Records.prefetchTargets().empty()) { Output.append("prefetch-targets:\n"); for (const MachinePrefetchTarget &TargetValue : Records.prefetchTargets()) { std::string Text = "bb_id " + std::to_string(TargetValue.Block.BaseID) + ", " + std::to_string(TargetValue.Block.CloneID) + ", " + std::to_string(TargetValue.CallsiteIndex); Output.append(" - "); Output.append(yamlScalar(Text)); Output.push_back('\n'); } } return {}; } Expected MIRDocumentPrinter::printFunction(const MachineFunction &Function, const MIRFunctionDocumentInfo *) { if (Function.compilationStatus().state() != MachineCompilationState::Success) { return makeUnexpected(ErrorCode::Unsupported, "fixed LLVM MIR cannot serialize this compilation failure state"); } auto Context = createBodyContext(Function); if (!Context) return std::unexpected(Context.error()); Output.append("---\nname: "); Output.append(yamlScalar(Function.name())); Output.push_back('\n'); if (Function.attributes().alignment()) { Output.append("alignment: "); Output.append(std::to_string(Function.attributes().alignment()->bytes())); Output.push_back('\n'); } const MachineFunctionProperties &Properties = Function.properties(); const MachineFunctionAttributes &Attributes = Function.attributes(); printBoolean("exposesReturnsTwice", Attributes.has(MachineFunctionAttribute::ExposesReturnsTwice)); printBoolean("legalized", Properties.has(MachineFunctionProperty::Legalized)); printBoolean("regBankSelected", Properties.has(MachineFunctionProperty::RegBankSelected)); printBoolean("selected", Properties.has(MachineFunctionProperty::Selected)); printBoolean("failedISel", Properties.has(MachineFunctionProperty::FailedISel)); printBoolean("tracksRegLiveness", Properties.has(MachineFunctionProperty::TracksLiveness)); printBoolean("hasWinCFI", Attributes.has(MachineFunctionAttribute::HasWinCFI)); printBoolean("noPhis", Properties.has(MachineFunctionProperty::NoPHIs), true); printBoolean("isSSA", Properties.has(MachineFunctionProperty::IsSSA), true); printBoolean("noVRegs", Properties.has(MachineFunctionProperty::NoVRegs), true); printBoolean("hasFakeUses", Attributes.has(MachineFunctionAttribute::HasFakeUses), true); printBoolean("callsEHReturn", Attributes.has(MachineFunctionAttribute::CallsEHReturn)); printBoolean("callsUnwindInit", Attributes.has(MachineFunctionAttribute::CallsUnwindInit)); printBoolean("hasEHContTarget", Attributes.has(MachineFunctionAttribute::HasEHContTarget)); printBoolean("hasEHScopes", Attributes.has(MachineFunctionAttribute::HasEHScopes)); printBoolean("hasEHFunclets", Attributes.has(MachineFunctionAttribute::HasEHFunclets)); printBoolean("isOutlined", Attributes.has(MachineFunctionAttribute::IsOutlined)); printBoolean("debugInstrRef", Attributes.has(MachineFunctionAttribute::DebugInstrRef)); printBoolean("failsVerification", Properties.has(MachineFunctionProperty::FailsVerification)); printBoolean("tracksDebugUserValues", Attributes.has(MachineFunctionAttribute::TracksDebugUserValues)); if (auto Printed = printRegisters(Function, *Context); !Printed) return Printed; if (auto Printed = printFrameInfo(Function, *Context); !Printed) return Printed; if (auto Printed = printStackObjects(Function, *Context, true); !Printed) return Printed; if (auto Printed = printStackObjects(Function, *Context, false); !Printed) return Printed; if (auto Printed = printFunctionRecords(Function, *Context); !Printed) return Printed; if (auto Printed = printConstants(Function, *Context); !Printed) return Printed; if (auto Printed = printTargetState(Function); !Printed) return Printed; if (auto Printed = printJumpTable(Function, *Context); !Printed) return Printed; if (auto Printed = printMetadata(Function, *Context); !Printed) return Printed; auto Body = printMachineFunctionBody(Function, Target, *Context, MIPrinterOptions{.Mode = Options.Mode}); if (!Body) return std::unexpected(Body.error()); Output.append("body: |\n"); appendBlockScalar(Output, *Body, 2); return {}; } Expected MIRDocumentPrinter::run() { auto UnitVerification = Document.unit().verify(); if (!UnitVerification) return std::unexpected(UnitVerification.error()); if (Document.unit().functionCount() == 0) { return makeUnexpected(ErrorCode::InvalidArgument, "MIR document must contain at least one machine function"); } for (const MIRFunctionDocumentInfo &Info : Document.functionDocumentInfo()) { if (Document.unit().function(Info.Function) == nullptr) { return makeUnexpected(ErrorCode::VerificationError, "MIR document info references an unknown machine function"); } } for (std::size_t Index = 0; Index < Document.unit().functionCount(); ++Index) { const MachineFunction *Function = Document.unit().functionAt(Index); if (Function == nullptr) { return makeUnexpected(ErrorCode::VerificationError, "compilation unit contains a null machine function"); } auto ReferenceVerification = verifyMIRDocumentReferences(Document, *Function, Options.EmitEmbeddedIR); if (!ReferenceVerification) return std::unexpected(ReferenceVerification.error()); auto LayoutVerification = verifyOmittedEmbeddedIRLayout(*Function); if (!LayoutVerification) return std::unexpected(LayoutVerification.error()); } if (Options.EmitEmbeddedIR && Document.embeddedIR() != nullptr) { Output.append("--- |\n"); appendBlockScalar(Output, Document.embeddedIR()->source().text(), 2); } for (std::size_t Index = 0; Index < Document.unit().functionCount(); ++Index) { const MachineFunction *Function = Document.unit().functionAt(Index); if (Function == nullptr) return makeUnexpected(ErrorCode::VerificationError, "compilation unit contains a null machine function"); if (auto Printed = printFunction(*Function, documentInfo(*Function)); !Printed) return std::unexpected(Printed.error()); } return std::move(Output); } } // namespace Expected printMIRDocument(const MIRDocument &Document, const TargetDescription &Target, MIRDocumentPrinterOptions Options) { return MIRDocumentPrinter(Document, Target, Options).run(); } } // namespace rcc::mir