From adcf8c9b1bd1b47e00bf859693112cea1f7e8c9f Mon Sep 17 00:00:00 2001 From: Richard-Qin-X Date: Mon, 10 Aug 2026 20:36:50 +0800 Subject: [Tooling] Complete backend tooling and validation --- backend/lib/CMakeLists.txt | 1 + backend/lib/CodeGen/InlineSpiller.cpp | 45 +- backend/lib/CodeGen/MachineBlockPlacement.cpp | 37 +- backend/lib/CodeGen/MachineBranchRelaxation.cpp | 21 +- backend/lib/CodeGen/MachineCSE.cpp | 7 +- backend/lib/CodeGen/MachineCombiner.cpp | 22 +- backend/lib/CodeGen/MachineCompilationStatus.cpp | 6 + backend/lib/CodeGen/MachineCopyPropagation.cpp | 21 +- backend/lib/CodeGen/MachineLICM.cpp | 4 +- backend/lib/CodeGen/MachinePass.cpp | 136 +- backend/lib/CodeGen/MachinePipelinePasses.cpp | 76 +- backend/lib/CodeGen/MachinePseudoExpansion.cpp | 61 +- backend/lib/CodeGen/MachineSSAExit.cpp | 103 +- backend/lib/CodeGen/MachineScheduler.cpp | 38 +- backend/lib/CodeGen/MachineSchedulerInternal.h | 13 +- backend/lib/CodeGen/MachineSink.cpp | 4 +- backend/lib/CodeGen/MachineStackSlotColoring.cpp | 2 +- backend/lib/CodeGen/MachineTailDuplication.cpp | 5 +- backend/lib/CodeGen/PostRAScheduler.cpp | 9 +- backend/lib/CodeGen/RegAllocBase.cpp | 3 +- backend/lib/CodeGen/RegAllocBase.h | 5 +- backend/lib/CodeGen/RegAllocPBQP.cpp | 73 +- backend/lib/CodeGen/RegisterPressure.cpp | 6 +- backend/lib/MIR/CMakeLists.txt | 27 + backend/lib/MIR/MIParser.cpp | 11 +- backend/lib/MIR/MIRDataParser.cpp | 288 ++ backend/lib/MIR/MIRDocument.cpp | 2745 +------------------- backend/lib/MIR/MIRDocumentParser.cpp | 358 +++ backend/lib/MIR/MIRDocumentParser.h | 131 + backend/lib/MIR/MIRDocumentReferenceValidation.cpp | 410 +++ backend/lib/MIR/MIRFrameParser.cpp | 451 ++++ backend/lib/MIR/MIRFunctionParser.cpp | 536 ++++ backend/lib/MIR/MIRFunctionRecordsParser.cpp | 301 +++ backend/lib/MIR/MIRMetadataParser.cpp | 285 ++ backend/lib/MIR/MIRMetadataTextParser.cpp | 259 ++ backend/lib/MIR/MIRMetadataTextParser.h | 70 + backend/lib/MIR/MIRParserUtilities.h | 85 + backend/lib/Support/CMakeLists.txt | 5 +- backend/lib/Support/Error.cpp | 12 + backend/lib/Support/SHA256.cpp | 149 ++ backend/lib/Target/RISCV/CMakeLists.txt | 4 + backend/lib/Target/RISCV/RISCVPipeline.cpp | 162 ++ backend/lib/Tooling/ArtifactStore.cpp | 532 ++++ backend/lib/Tooling/BackendDriver.cpp | 214 ++ backend/lib/Tooling/BackendInvocation.cpp | 500 ++++ backend/lib/Tooling/BackendManifest.cpp | 733 ++++++ backend/lib/Tooling/CMakeLists.txt | 76 + backend/lib/Tooling/MIRFuzzer.cpp | 613 +++++ backend/lib/Tooling/MIRReducer.cpp | 534 ++++ backend/lib/Tooling/PipelineDriver.cpp | 436 ++++ 50 files changed, 7754 insertions(+), 2871 deletions(-) create mode 100644 backend/lib/MIR/MIRDataParser.cpp create mode 100644 backend/lib/MIR/MIRDocumentParser.cpp create mode 100644 backend/lib/MIR/MIRDocumentParser.h create mode 100644 backend/lib/MIR/MIRDocumentReferenceValidation.cpp create mode 100644 backend/lib/MIR/MIRFrameParser.cpp create mode 100644 backend/lib/MIR/MIRFunctionParser.cpp create mode 100644 backend/lib/MIR/MIRFunctionRecordsParser.cpp create mode 100644 backend/lib/MIR/MIRMetadataParser.cpp create mode 100644 backend/lib/MIR/MIRMetadataTextParser.cpp create mode 100644 backend/lib/MIR/MIRMetadataTextParser.h create mode 100644 backend/lib/MIR/MIRParserUtilities.h create mode 100644 backend/lib/Support/SHA256.cpp create mode 100644 backend/lib/Target/RISCV/RISCVPipeline.cpp create mode 100644 backend/lib/Tooling/ArtifactStore.cpp create mode 100644 backend/lib/Tooling/BackendDriver.cpp create mode 100644 backend/lib/Tooling/BackendInvocation.cpp create mode 100644 backend/lib/Tooling/BackendManifest.cpp create mode 100644 backend/lib/Tooling/CMakeLists.txt create mode 100644 backend/lib/Tooling/MIRFuzzer.cpp create mode 100644 backend/lib/Tooling/MIRReducer.cpp create mode 100644 backend/lib/Tooling/PipelineDriver.cpp (limited to 'backend/lib') diff --git a/backend/lib/CMakeLists.txt b/backend/lib/CMakeLists.txt index 0160f1b..a6ba7ef 100644 --- a/backend/lib/CMakeLists.txt +++ b/backend/lib/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory(CodeGen) add_subdirectory(MIR) add_subdirectory(MC) add_subdirectory(AsmPrinter) +add_subdirectory(Tooling) diff --git a/backend/lib/CodeGen/InlineSpiller.cpp b/backend/lib/CodeGen/InlineSpiller.cpp index 589b7b8..14de321 100644 --- a/backend/lib/CodeGen/InlineSpiller.cpp +++ b/backend/lib/CodeGen/InlineSpiller.cpp @@ -7,6 +7,7 @@ #include "rcc/CodeGen/MachineIRBuilder.h" #include "rcc/CodeGen/MachineInstr.h" #include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachinePass.h" #include "rcc/CodeGen/MachineRegisterInfo.h" #include "rcc/CodeGen/Spiller.h" #include "rcc/CodeGen/TargetDescription.h" @@ -42,11 +43,11 @@ Expected>> Spiller::tryRematerialize(Registe const MachineInstr *DefiningInstruction = Definition == nullptr ? nullptr : Definition->parent(); if (DefiningInstruction == nullptr || DefiningInstruction->parent() == nullptr) return std::optional>{}; - auto Accepted = + auto Rematerializable = TargetInfo->isRematerializable(*DefiningInstruction, VirtualReg, *Function, *Target); - if (!Accepted) - return std::unexpected(Accepted.error()); - if (!*Accepted) + if (!Rematerializable) + return std::unexpected(Rematerializable.error()); + if (!*Rematerializable) return std::optional>{}; struct UseSite final { @@ -77,6 +78,16 @@ Expected>> Spiller::tryRematerialize(Registe if (Sites.empty()) return std::optional>{}; + if (Actions != nullptr) { + for (const UseSite &Site : Sites) { + auto Accepted = Actions->request("rematerialize", *Function, Site.Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) + return std::optional>{}; + } + } + std::vector NewRegisters; NewRegisters.reserve(Sites.size()); for (UseSite &Site : Sites) { @@ -169,6 +180,19 @@ Expected> Spiller::spillAtInstructions(Register VirtualReg } std::vector NewRegisters; + if (Actions != nullptr) { + for (const SpillSite &Site : Sites) { + if (Site.OperandIndices.empty()) + continue; + auto Accepted = Actions->request("split-live-range", *Function, Site.Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "required live-range split action was rejected"); + } + } + } for (SpillSite &Site : Sites) { for (const std::size_t Index : Site.DebugIndices) { MachineOperand *Operand = Site.Instruction->operandAt(Index); @@ -259,6 +283,19 @@ Expected> Spiller::spill(Register VirtualReg) { return makeUnexpected(ErrorCode::RegisterAllocationFailed, "register pressure requires spilling a non-spillable value"); } + if (Actions != nullptr) { + const MachineOperand *Definition = Function->registerInfo().getUniqueVRegDef(VirtualReg); + const MachineInstr *Instruction = Definition == nullptr ? nullptr : Definition->parent(); + auto Accepted = + Actions->request("spill", *Function, + Instruction == nullptr ? MachineInstrStableID{} : Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "required spill action was rejected"); + } + } auto Rematerialized = tryRematerialize(VirtualReg); if (!Rematerialized) return std::unexpected(Rematerialized.error()); diff --git a/backend/lib/CodeGen/MachineBlockPlacement.cpp b/backend/lib/CodeGen/MachineBlockPlacement.cpp index fea1283..daccba0 100644 --- a/backend/lib/CodeGen/MachineBlockPlacement.cpp +++ b/backend/lib/CodeGen/MachineBlockPlacement.cpp @@ -114,21 +114,25 @@ Expected MachineBlockPlacementPass::run(MachineFunction &Func if (!Trial) return std::unexpected(prefixedError(Trial.error(), "block placement setup failed: ")); MachineAnalysisManager TrialAnalyses; - auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = runImplementation(Function, Target, Analyses); + Actions.beginReplay(); + auto Result = runImplementation(Function, Target, Analyses, Actions); if (!Result) { return std::unexpected(prefixedError( Result.error(), "block placement diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } -Expected -MachineBlockPlacementPass::runImplementation(MachineFunction &Function, - const TargetDescription &Target, - MachineAnalysisManager &Analyses) const { +Expected MachineBlockPlacementPass::runImplementation( + MachineFunction &Function, const TargetDescription &Target, MachineAnalysisManager &Analyses, + MachineActionReplay &Actions) const { auto Probabilities = Analyses.getResult(Function, Target); if (!Probabilities) return std::unexpected(Probabilities.error()); @@ -154,7 +158,7 @@ MachineBlockPlacementPass::runImplementation(MachineFunction &Function, return Result; } - auto factsFor = [&Facts](const MachineBasicBlock &Block) -> const PlacementFacts & { + auto FactsFor = [&Facts](const MachineBasicBlock &Block) -> const PlacementFacts & { return *std::ranges::find_if( Facts, [&Block](const PlacementFacts &Value) { return Value.Block == &Block; }); }; @@ -186,7 +190,7 @@ MachineBlockPlacementPass::runImplementation(MachineFunction &Function, continue; if (BestSuccessor == nullptr || BestProbability < Successor.Probability || (BestProbability == Successor.Probability && - betterRoot(factsFor(*Successor.Block), factsFor(*BestSuccessor)))) { + betterRoot(FactsFor(*Successor.Block), FactsFor(*BestSuccessor)))) { BestSuccessor = Successor.Block; BestProbability = Successor.Probability; } @@ -223,7 +227,22 @@ MachineBlockPlacementPass::runImplementation(MachineFunction &Function, bool Changed = false; for (std::size_t Index = 1; Index < Order.size(); ++Index) { - Changed |= Order[Index - 1U]->next() != Order[Index]; + if (Order[Index - 1U]->next() != Order[Index]) { + Changed = true; + break; + } + } + if (Changed) { + auto Accepted = Actions.request("apply-block-layout", Function); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + MachinePassResult Result; + Result.Preserved = PreservedAnalyses::all(); + return Result; + } + } + for (std::size_t Index = 1; Index < Order.size(); ++Index) { auto Moved = Function.moveBlockAfter(*Order[Index - 1U], *Order[Index]); if (!Moved) return std::unexpected(Moved.error()); diff --git a/backend/lib/CodeGen/MachineBranchRelaxation.cpp b/backend/lib/CodeGen/MachineBranchRelaxation.cpp index d56f3a0..b2a330a 100644 --- a/backend/lib/CodeGen/MachineBranchRelaxation.cpp +++ b/backend/lib/CodeGen/MachineBranchRelaxation.cpp @@ -109,7 +109,7 @@ const MachinePassContract &MachineBranchRelaxationPass::contract() const noexcep Expected MachineBranchRelaxationPass::run(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineAnalysisManager &Analyses) { if (!Function.properties().containsAll(contract().Requires)) { return makeUnexpected(ErrorCode::VerificationError, "branch relaxation requires finalized frame and tracked liveness"); @@ -119,20 +119,26 @@ Expected MachineBranchRelaxationPass::run(MachineFunction &Fu MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt}); if (!Trial) return std::unexpected(prefixedError(Trial.error(), "branch relaxation setup failed: ")); - auto TrialResult = runImplementation(*Trial->ClonedFunction, Target); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = runImplementation(Function, Target); + Actions.beginReplay(); + auto Result = runImplementation(Function, Target, Actions); if (!Result) { return std::unexpected(prefixedError( Result.error(), "branch relaxation diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } Expected MachineBranchRelaxationPass::runImplementation(MachineFunction &Function, - const TargetDescription &Target) const { + const TargetDescription &Target, + MachineActionReplay &Actions) const { if (Options.ConvergenceWatchdog == 0) return makeUnexpected(ErrorCode::InvalidArgument, "branch relaxation watchdog must be nonzero"); MachinePassResult Result; @@ -162,6 +168,13 @@ MachineBranchRelaxationPass::runImplementation(MachineFunction &Function, return std::unexpected(Offset.error()); if (Info->isBranchOffsetInRange(*Instruction, *Offset)) continue; + auto Accepted = Actions.request("relax-branch", Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + return makeUnexpected(ErrorCode::BranchRelaxationFailed, + "required branch relaxation action was rejected"); + } auto Relaxed = Info->relaxBranch(*Instruction, *Offset, Function, Target); if (!Relaxed) return std::unexpected(Relaxed.error()); diff --git a/backend/lib/CodeGen/MachineCSE.cpp b/backend/lib/CodeGen/MachineCSE.cpp index a144403..47bba04 100644 --- a/backend/lib/CodeGen/MachineCSE.cpp +++ b/backend/lib/CodeGen/MachineCSE.cpp @@ -161,8 +161,11 @@ template if (Reg == nullptr || !Reg->isVirtual()) continue; auto Info = Registers.virtualRegisterInfo(*Reg); - if (Info && Info->get().Type && Info->get().Type->isFloat()) - return true; + if (Info) { + const auto &Type = Info->get().Type; + if (Type && Type->isFloat()) + return true; + } } return false; } diff --git a/backend/lib/CodeGen/MachineCombiner.cpp b/backend/lib/CodeGen/MachineCombiner.cpp index 513d282..54915ab 100644 --- a/backend/lib/CodeGen/MachineCombiner.cpp +++ b/backend/lib/CodeGen/MachineCombiner.cpp @@ -279,7 +279,7 @@ const MachinePassContract &MachineCombinerPass::contract() const noexcept { Expected MachineCombinerPass::run(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineAnalysisManager &Analyses) { if (!Function.properties().containsAll(contract().Requires)) { return makeUnexpected(ErrorCode::VerificationError, "machine combiner input does not satisfy its stage contract"); @@ -291,20 +291,25 @@ Expected MachineCombinerPass::run(MachineFunction &Function, return std::unexpected( prefixedError(Trial.error(), "machine combiner transaction setup failed: ")); } - auto TrialResult = runImplementation(*Trial->ClonedFunction, Target); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = runImplementation(Function, Target); + Actions.beginReplay(); + auto Result = runImplementation(Function, Target, Actions); if (!Result) { return std::unexpected(prefixedError( Result.error(), "machine combiner diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } Expected -MachineCombinerPass::runImplementation(MachineFunction &Function, - const TargetDescription &Target) const { +MachineCombinerPass::runImplementation(MachineFunction &Function, const TargetDescription &Target, + MachineActionReplay &Actions) const { if (Options.MaximumIterations == 0) return makeUnexpected(ErrorCode::InvalidArgument, "machine combiner iteration budget must be nonzero"); @@ -321,6 +326,13 @@ MachineCombinerPass::runImplementation(MachineFunction &Function, continue; if (Rewrites == Options.MaximumRewrites) break; + std::string Rule = "combine."; + Rule.append(Pattern.Name); + auto Accepted = Actions.request(Rule, Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) + continue; auto Executed = executePattern(*Instruction, Pattern, Function, Target); if (!Executed) return std::unexpected(Executed.error()); diff --git a/backend/lib/CodeGen/MachineCompilationStatus.cpp b/backend/lib/CodeGen/MachineCompilationStatus.cpp index 3ebfa0f..df009a4 100644 --- a/backend/lib/CodeGen/MachineCompilationStatus.cpp +++ b/backend/lib/CodeGen/MachineCompilationStatus.cpp @@ -55,6 +55,12 @@ isKnownDiagnosticSeverity(MachineCompilationDiagnosticSeverity Severity) noexcep case ErrorCode::FixupApplicationFailed: case ErrorCode::RelocationFailed: case ErrorCode::ObjectWritingFailed: + case ErrorCode::InvalidInvocation: + case ErrorCode::ManifestError: + case ErrorCode::CacheMiss: + case ErrorCode::ReproducerError: + case ErrorCode::ReductionFailed: + case ErrorCode::FuzzingFailure: case ErrorCode::IoError: case ErrorCode::InternalError: return true; diff --git a/backend/lib/CodeGen/MachineCopyPropagation.cpp b/backend/lib/CodeGen/MachineCopyPropagation.cpp index e537a06..f9211f9 100644 --- a/backend/lib/CodeGen/MachineCopyPropagation.cpp +++ b/backend/lib/CodeGen/MachineCopyPropagation.cpp @@ -292,7 +292,7 @@ const MachinePassContract &MachineCopyPropagationPass::contract() const noexcept Expected MachineCopyPropagationPass::run(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineAnalysisManager &Analyses) { if (!Function.properties().containsAll(contract().Requires)) { return makeUnexpected(ErrorCode::VerificationError, "machine COPY propagation has unmet stage properties"); @@ -304,20 +304,26 @@ Expected MachineCopyPropagationPass::run(MachineFunction &Fun return std::unexpected( prefixedError(Trial.error(), "COPY propagation transaction setup failed: ")); } - auto TrialResult = runImplementation(*Trial->ClonedFunction, Target); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = runImplementation(Function, Target); + Actions.beginReplay(); + auto Result = runImplementation(Function, Target, Actions); if (!Result) { return std::unexpected(prefixedError( Result.error(), "COPY propagation diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } Expected MachineCopyPropagationPass::runImplementation(MachineFunction &Function, - const TargetDescription &Target) const { + const TargetDescription &Target, + MachineActionReplay &Actions) const { MachinePassResult Result; std::size_t PropagatedCopies = 0; for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) { @@ -329,6 +335,13 @@ MachineCopyPropagationPass::runImplementation(MachineFunction &Function, Instruction = Next; continue; } + auto Accepted = Actions.request("propagate-copy", Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + Instruction = Next; + continue; + } Expected Changed = Stage == MachineCopyPropagationStage::PreRegisterAllocation ? propagateVirtualCopy(*Instruction, *Operands, Function, Target, *Info) diff --git a/backend/lib/CodeGen/MachineLICM.cpp b/backend/lib/CodeGen/MachineLICM.cpp index 8c8d732..52fffe7 100644 --- a/backend/lib/CodeGen/MachineLICM.cpp +++ b/backend/lib/CodeGen/MachineLICM.cpp @@ -146,8 +146,8 @@ namespace { auto Info = Function.registerInfo().virtualRegisterInfo(*Reg); if (!Info) return std::unexpected(Info.error()); - if (Info->get().Type && Info->get().Type->isFloat() && - !Instruction.flags().has(MachineInstrFlag::NoFPExcept)) { + const auto &Type = Info->get().Type; + if (Type && Type->isFloat() && !Instruction.flags().has(MachineInstrFlag::NoFPExcept)) { return false; } } diff --git a/backend/lib/CodeGen/MachinePass.cpp b/backend/lib/CodeGen/MachinePass.cpp index ae69b1c..0ce6f70 100644 --- a/backend/lib/CodeGen/MachinePass.cpp +++ b/backend/lib/CodeGen/MachinePass.cpp @@ -141,6 +141,79 @@ void combineFingerprint(std::uint64_t &State, std::uint64_t Value) noexcept { } // namespace +std::string MachineActionID::toString() const { + std::string Result; + Result.reserve(Pass.size() + Rule.size() + 48U); + Result.append(Pass); + Result.push_back(':'); + Result.append(Rule); + Result.append(":f"); + Result.append(std::to_string(Function.value())); + Result.append(":i"); + Result.append(std::to_string(Instruction.value())); + return Result; +} + +Expected MachineActionReplay::request(std::string_view Rule, const MachineFunction &Function, + MachineInstrStableID Instruction, std::uint64_t Cost) { + if (!IsReplaying) { + const auto Position = RecordingInstructionIDs.find(Instruction); + const MachineInstrStableID CanonicalInstruction = + Position == RecordingInstructionIDs.end() ? Instruction : Position->second; + auto Accepted = Analyses->requestAction(Rule, Function, CanonicalInstruction, Cost); + if (!Accepted) + return std::unexpected(Accepted.error()); + Decisions.push_back({ + .Rule = std::string(Rule), + .Function = Function.stableID(), + .Instruction = CanonicalInstruction, + .Cost = Cost, + .Accepted = *Accepted, + }); + return *Accepted; + } + + if (NextDecision >= Decisions.size()) { + return makeUnexpected(ErrorCode::InternalError, + "machine action replay requested an unrecorded decision"); + } + const Decision &Recorded = Decisions[NextDecision++]; + if (Recorded.Rule != Rule || Recorded.Function != Function.stableID() || + Recorded.Instruction != Instruction || Recorded.Cost != Cost) { + std::string Message = "machine action replay diverged from transaction preflight: recorded "; + Message.append(Recorded.Rule); + Message.append(":f"); + Message.append(std::to_string(Recorded.Function.value())); + Message.append(":i"); + Message.append(std::to_string(Recorded.Instruction.value())); + Message.append(" cost="); + Message.append(std::to_string(Recorded.Cost)); + Message.append(", requested "); + Message.append(Rule); + Message.append(":f"); + Message.append(std::to_string(Function.stableID().value())); + Message.append(":i"); + Message.append(std::to_string(Instruction.value())); + Message.append(" cost="); + Message.append(std::to_string(Cost)); + return makeUnexpected(ErrorCode::InternalError, std::move(Message)); + } + return Recorded.Accepted; +} + +void MachineActionReplay::beginReplay() noexcept { + IsReplaying = true; + NextDecision = 0; +} + +Expected MachineActionReplay::finishReplay() const { + if (!IsReplaying || NextDecision != Decisions.size()) { + return makeUnexpected(ErrorCode::InternalError, + "machine action replay did not consume every recorded decision"); + } + return {}; +} + Expected MachineAnalysisManager::registerAnalysisImpl(MachineAnalysisRegistration Registration, ErasedRunner Runner, ErasedInvalidator Invalidator) { @@ -374,12 +447,25 @@ Expected MachineAnalysisManager::validateParallelRequirements( Expected MachineAnalysisManager::requestAction(std::string_view Action, const MachineFunction &Function, std::uint64_t Cost) { - if (Action.empty() || Cost == 0) + return requestAction(Action, Function, MachineInstrStableID{}, Cost); +} + +Expected MachineAnalysisManager::requestAction(std::string_view Rule, + const MachineFunction &Function, + MachineInstrStableID Instruction, + std::uint64_t Cost) { + if (Rule.empty() || Cost == 0) return makeUnexpected(ErrorCode::InvalidArgument, "machine action requires a name and nonzero cost"); if (Instrumentation == nullptr) return true; - return Instrumentation->requestAction(Action, Function, Cost); + MachineActionID ID{ + .Pass = ActivePass.empty() ? "unscoped" : ActivePass, + .Rule = std::string(Rule), + .Function = Function.stableID(), + .Instruction = Instruction, + }; + return Instrumentation->requestAction(ID, Function, Cost); } Expected CompilationUnitAnalysisManager::registerAnalysisImpl( @@ -639,6 +725,7 @@ void MachinePassInstrumentation::setPerFunctionFuel(std::optional std::scoped_lock Lock(Mutex); PerFunctionFuel = Fuel; ConsumedFuel.clear(); + ActionEvents.clear(); } void MachinePassInstrumentation::addBeforeUnitCallback(BeforeUnitCallback Callback) { @@ -879,25 +966,37 @@ void MachinePassInstrumentation::change(std::string_view Name, const MachineFunc Callback(Name, Function, Changes, FingerprintChanged); } -Expected MachinePassInstrumentation::requestAction(std::string_view Action, +Expected MachinePassInstrumentation::requestAction(const MachineActionID &Action, const MachineFunction &Function, std::uint64_t Cost) { + if (Action.Pass.empty() || Action.Rule.empty() || Action.Function != Function.stableID() || + Cost == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, "machine action identity is invalid"); + } std::scoped_lock ActionLock(ActionMutex); ActionFilter CurrentFilter; { std::scoped_lock Lock(Mutex); CurrentFilter = Filter; } - if (CurrentFilter && !CurrentFilter(Action, Function, Cost)) + if (CurrentFilter && !CurrentFilter(Action, Cost)) { + std::scoped_lock Lock(Mutex); + ActionEvents.push_back({Action, Cost, false}); return false; + } std::scoped_lock Lock(Mutex); - if (!PerFunctionFuel) + if (!PerFunctionFuel) { + ActionEvents.push_back({Action, Cost, true}); return true; + } std::uint64_t &Consumed = ConsumedFuel[Function.stableID().value()]; - if (Consumed > *PerFunctionFuel || Cost > *PerFunctionFuel - Consumed) + if (Consumed > *PerFunctionFuel || Cost > *PerFunctionFuel - Consumed) { + ActionEvents.push_back({Action, Cost, false}); return false; + } Consumed += Cost; + ActionEvents.push_back({Action, Cost, true}); return true; } @@ -1007,10 +1106,33 @@ std::vector MachinePassInstrumentation::statistics() c return Result; } +std::vector MachinePassInstrumentation::actionEvents() const { + std::vector Result; + { + std::scoped_lock Lock(Mutex); + Result = ActionEvents; + } + std::ranges::sort(Result, [](const MachineActionEvent &Left, const MachineActionEvent &Right) { + if (Left.ID.Function.value() != Right.ID.Function.value()) + return Left.ID.Function.value() < Right.ID.Function.value(); + if (Left.ID.Instruction.value() != Right.ID.Instruction.value()) + return Left.ID.Instruction.value() < Right.ID.Instruction.value(); + if (Left.ID.Pass != Right.ID.Pass) + return Left.ID.Pass < Right.ID.Pass; + if (Left.ID.Rule != Right.ID.Rule) + return Left.ID.Rule < Right.ID.Rule; + if (Left.Cost != Right.Cost) + return Left.Cost < Right.Cost; + return Left.Accepted < Right.Accepted; + }); + return Result; +} + void MachinePassInstrumentation::clearStatistics() { std::scoped_lock Lock(Mutex); Statistics.clear(); ConsumedFuel.clear(); + ActionEvents.clear(); } Expected MachinePassManager::validateContract(const MachinePassContract &Contract) const { @@ -1087,7 +1209,9 @@ Expected MachinePassManager::run(MachineFunction &Function, const TargetDe if (Instrumentation != nullptr) Instrumentation->before(Name, Function); const Clock::time_point Start = Clock::now(); + Analyses.ActivePass = std::string(Name); auto PassResult = Pass->run(Function, Target, Analyses); + Analyses.ActivePass.clear(); const std::uint64_t Duration = elapsedNanoseconds(Start); if (!PassResult) { Error Failure = diff --git a/backend/lib/CodeGen/MachinePipelinePasses.cpp b/backend/lib/CodeGen/MachinePipelinePasses.cpp index 209439c..c7c5dc3 100644 --- a/backend/lib/CodeGen/MachinePipelinePasses.cpp +++ b/backend/lib/CodeGen/MachinePipelinePasses.cpp @@ -233,6 +233,36 @@ registerConfigurationFingerprint(const MachineRegisterInfo &RegInfo) noexcept { return Error(Source.code(), std::move(Prefix)); } +[[nodiscard]] std::string_view legalizationActionName(LegalizationAction Action) noexcept { + switch (Action) { + case LegalizationAction::Legal: + return "legal"; + case LegalizationAction::Unsupported: + return "unsupported"; + case LegalizationAction::Custom: + return "custom"; + case LegalizationAction::Lower: + return "lower"; + case LegalizationAction::Libcall: + return "libcall"; + case LegalizationAction::WidenScalar: + return "widen-scalar"; + case LegalizationAction::NarrowScalar: + return "narrow-scalar"; + case LegalizationAction::MoreElements: + return "more-elements"; + case LegalizationAction::FewerElements: + return "fewer-elements"; + case LegalizationAction::Bitcast: + return "bitcast"; + case LegalizationAction::ChangeElementType: + return "change-element-type"; + case LegalizationAction::ChangeAddressSpace: + return "change-address-space"; + } + return "unknown"; +} + [[nodiscard]] Expected repairTrackedLiveness(MachineFunction &Function, const TargetDescription &Target, MachinePassResult &Result) { @@ -268,21 +298,25 @@ Expected LegalizerPass::run(MachineFunction &Function, MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt}); if (!Trial) return std::unexpected(prefixedError(Trial.error(), "legalizer transaction setup failed: ")); - MachineAnalysisManager TrialAnalyses; - auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = runImplementation(Function, Target, Analyses); + Actions.beginReplay(); + auto Result = runImplementation(Function, Target, Actions); if (!Result) { return std::unexpected(prefixedError( Result.error(), "legalizer diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } Expected LegalizerPass::runImplementation(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineActionReplay &Actions) { MachinePassResult Result; std::unordered_set SeenStates; @@ -328,6 +362,18 @@ Expected LegalizerPass::runImplementation(MachineFunction &Fu if (!LoweringValidation) return std::unexpected( prefixedError(LoweringValidation.error(), "legalizer lowering precondition failed: ")); + std::string Rule = "legalize."; + Rule.append(legalizationActionName(*Action)); + Rule.push_back('.'); + Rule.append(Descriptor->Name); + auto Accepted = Actions.request(Rule, Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + return std::unexpected( + passFailure("required legalizer action was rejected for '" + Descriptor->Name + "'", + ErrorCode::VerificationError)); + } auto Lowered = Info->lower(*Instruction, Function, Target, *Action); if (!Lowered) return std::unexpected(Lowered.error()); @@ -521,22 +567,26 @@ Expected InstructionSelectPass::run(MachineFunction &Function return std::unexpected( prefixedError(Trial.error(), "instruction-selection transaction setup failed: ")); } - MachineAnalysisManager TrialAnalyses; - auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = runImplementation(Function, Target, Analyses); + Actions.beginReplay(); + auto Result = runImplementation(Function, Target, Actions); if (!Result) { return std::unexpected( prefixedError(Result.error(), "instruction selection diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } Expected InstructionSelectPass::runImplementation(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineActionReplay &Actions) { MachinePassResult Result; std::vector Worklist; @@ -580,6 +630,16 @@ InstructionSelectPass::runImplementation(MachineFunction &Function, const Target auto Match = Info->canSelect(*Instruction, Function, Target); if (!Match) return std::unexpected(Match.error()); + std::string Rule = "select."; + Rule.append(CurrentDescriptor->Name); + auto Accepted = Actions.request(Rule, Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + return std::unexpected(passFailure("required selection action was rejected for '" + + CurrentDescriptor->Name + "'", + ErrorCode::VerificationError)); + } auto Selection = Info->select(*Instruction, Function, Target); if (!Selection) return std::unexpected(Selection.error()); diff --git a/backend/lib/CodeGen/MachinePseudoExpansion.cpp b/backend/lib/CodeGen/MachinePseudoExpansion.cpp index e654d97..676804a 100644 --- a/backend/lib/CodeGen/MachinePseudoExpansion.cpp +++ b/backend/lib/CodeGen/MachinePseudoExpansion.cpp @@ -202,7 +202,7 @@ using BlockSizeSnapshot = std::vector prepareImplementation(MachineFunction &Function, const TargetDescription &Target, - const TargetPseudoExpansionInfo &Info) { + const TargetPseudoExpansionInfo &Info, MachineActionReplay &Actions) { bool Changed = false; for (MachineInstr *Instruction : Function.allInstructions()) { if (Instruction == nullptr || Instruction->parent() == nullptr) @@ -213,6 +213,15 @@ prepareImplementation(MachineFunction &Function, const TargetDescription &Target "pseudo preparation encountered an unknown opcode"); if (Descriptor->Owner != PseudoOwner::PostRAExpansion) continue; + std::string Rule = "prepare-pseudo."; + Rule.append(Descriptor->Name); + auto Accepted = Actions.request(Rule, Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + return makeUnexpected(ErrorCode::PseudoExpansionFailed, + "required pseudo preparation action was rejected"); + } auto Prepared = Info.preparePostRAPseudo(*Instruction, Function, Target); if (!Prepared) return std::unexpected(Prepared.error()); @@ -232,7 +241,7 @@ prepareImplementation(MachineFunction &Function, const TargetDescription &Target [[nodiscard]] Expected expandImplementation(MachineFunction &Function, const TargetDescription &Target, const TargetPseudoExpansionInfo &Info, MachinePseudoExpansionPhase Phase, - const TargetBranchInfo *BranchInfo) { + const TargetBranchInfo *BranchInfo, MachineActionReplay &Actions) { const ResourceSnapshot Before = snapshotResources(Function); std::optional SizesBefore; if (Phase == MachinePseudoExpansionPhase::TargetLate) { @@ -261,6 +270,15 @@ expandImplementation(MachineFunction &Function, const TargetDescription &Target, if (!Descriptor->Flags.has(InstructionFlag::Pseudo) || Descriptor->Owner != ownerFor(Phase)) { continue; } + std::string Rule = "expand-pseudo."; + Rule.append(Descriptor->Name); + auto Accepted = Actions.request(Rule, Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + return makeUnexpected(ErrorCode::PseudoExpansionFailed, + "required pseudo expansion action was rejected"); + } MachineBasicBlock *ExpansionBlock = Instruction->parent(); std::optional ExpansionSizeBefore; if (Phase == MachinePseudoExpansionPhase::TargetLate) { @@ -335,40 +353,51 @@ expandImplementation(MachineFunction &Function, const TargetDescription &Target, [[nodiscard]] Expected runPreparationTransaction(MachineFunction &Function, const TargetDescription &Target, - const TargetPseudoExpansionInfo &Info) { + const TargetPseudoExpansionInfo &Info, MachineAnalysisManager &Analyses) { auto Trial = cloneMachineFunction( Function, Target, MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt}); if (!Trial) return std::unexpected(prefixedError(Trial.error(), "pseudo preparation setup failed: ")); - auto TrialResult = prepareImplementation(*Trial->ClonedFunction, Target, Info); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = prepareImplementation(*Trial->ClonedFunction, Target, Info, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = prepareImplementation(Function, Target, Info); + Actions.beginReplay(); + auto Result = prepareImplementation(Function, Target, Info, Actions); if (!Result) { return std::unexpected( prefixedError(Result.error(), "pseudo preparation diverged after successful preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } [[nodiscard]] Expected runExpansionTransaction(MachineFunction &Function, const TargetDescription &Target, const TargetPseudoExpansionInfo &Info, MachinePseudoExpansionPhase Phase, - const TargetBranchInfo *BranchInfo) { + const TargetBranchInfo *BranchInfo, MachineAnalysisManager &Analyses) { auto Trial = cloneMachineFunction( Function, Target, MachineFunctionCloneOptions{.NewStableID = Function.stableID(), .NewName = std::nullopt}); if (!Trial) return std::unexpected(prefixedError(Trial.error(), "pseudo expansion setup failed: ")); - auto TrialResult = expandImplementation(*Trial->ClonedFunction, Target, Info, Phase, BranchInfo); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = + expandImplementation(*Trial->ClonedFunction, Target, Info, Phase, BranchInfo, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = expandImplementation(Function, Target, Info, Phase, BranchInfo); + Actions.beginReplay(); + auto Result = expandImplementation(Function, Target, Info, Phase, BranchInfo, Actions); if (!Result) { return std::unexpected( prefixedError(Result.error(), "pseudo expansion diverged after successful preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } @@ -380,11 +409,11 @@ const MachinePassContract &PreparePostRAPseudosPass::contract() const noexcept { Expected PreparePostRAPseudosPass::run(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineAnalysisManager &Analyses) { if (!Function.properties().containsAll(contract().Requires)) return makeUnexpected(ErrorCode::VerificationError, "post-RA pseudo preparation requires selected MachineIR"); - return runPreparationTransaction(Function, Target, *Info); + return runPreparationTransaction(Function, Target, *Info, Analyses); } const MachinePassContract &PrePEITargetPseudoExpansionPass::contract() const noexcept { @@ -393,12 +422,12 @@ const MachinePassContract &PrePEITargetPseudoExpansionPass::contract() const noe Expected PrePEITargetPseudoExpansionPass::run(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineAnalysisManager &Analyses) { if (!Function.properties().containsAll(contract().Requires)) return makeUnexpected(ErrorCode::VerificationError, "pre-PEI pseudo expansion requires allocated tracked-liveness MIR"); return runExpansionTransaction(Function, Target, *Info, MachinePseudoExpansionPhase::PrePEI, - nullptr); + nullptr, Analyses); } const MachinePassContract &ExpandPostRAPseudosPass::contract() const noexcept { @@ -407,12 +436,12 @@ const MachinePassContract &ExpandPostRAPseudosPass::contract() const noexcept { Expected ExpandPostRAPseudosPass::run(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineAnalysisManager &Analyses) { if (!Function.properties().containsAll(contract().Requires)) return makeUnexpected(ErrorCode::VerificationError, "post-RA pseudo expansion requires finalized frame and liveness"); return runExpansionTransaction(Function, Target, *Info, MachinePseudoExpansionPhase::PostRA, - nullptr); + nullptr, Analyses); } const MachinePassContract &TargetLatePseudoExpansionPass::contract() const noexcept { @@ -421,12 +450,12 @@ const MachinePassContract &TargetLatePseudoExpansionPass::contract() const noexc Expected TargetLatePseudoExpansionPass::run(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineAnalysisManager &Analyses) { if (!Function.properties().containsAll(contract().Requires)) return makeUnexpected(ErrorCode::VerificationError, "target-late pseudo expansion requires finalized allocated MIR"); return runExpansionTransaction(Function, Target, *Info, MachinePseudoExpansionPhase::TargetLate, - BranchInfo); + BranchInfo, Analyses); } } // namespace rcc diff --git a/backend/lib/CodeGen/MachineSSAExit.cpp b/backend/lib/CodeGen/MachineSSAExit.cpp index e413b45..f2b0102 100644 --- a/backend/lib/CodeGen/MachineSSAExit.cpp +++ b/backend/lib/CodeGen/MachineSSAExit.cpp @@ -805,22 +805,26 @@ Expected PHIEliminationPass::run(MachineFunction &Function, return std::unexpected( prefixedError(Trial.error(), "PHI-elimination transaction setup failed: ")); } - MachineAnalysisManager TrialAnalyses; - auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = runImplementation(Function, Target, Analyses); + Actions.beginReplay(); + auto Result = runImplementation(Function, Target, Actions); if (!Result) { return std::unexpected(prefixedError( Result.error(), "PHI elimination diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } Expected PHIEliminationPass::runImplementation(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineActionReplay &Actions) { MachinePassResult Result; std::unordered_map SplitEdges; const std::vector Blocks(Function.allBlocks().begin(), @@ -834,6 +838,13 @@ Expected PHIEliminationPass::runImplementation(MachineFunctio continue; for (PhiPlan &Plan : *Plans) { + auto Accepted = Actions.request("eliminate-phi", Function, Plan.Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + return makeUnexpected(ErrorCode::VerificationError, + "required PHI elimination action was rejected"); + } auto Fresh = cloneRegisterConstraints(Function, Plan.Destination.Reg); if (!Fresh) return std::unexpected(Fresh.error()); @@ -928,22 +939,26 @@ Expected TwoAddressRewritePass::run(MachineFunction &Function if (!Trial) { return std::unexpected(prefixedError(Trial.error(), "two-address transaction setup failed: ")); } - MachineAnalysisManager TrialAnalyses; - auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, TrialAnalyses); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + auto TrialResult = runImplementation(*Trial->ClonedFunction, Target, Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); - auto Result = runImplementation(Function, Target, Analyses); + Actions.beginReplay(); + auto Result = runImplementation(Function, Target, Actions); if (!Result) { return std::unexpected(prefixedError( Result.error(), "two-address rewrite diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); return Result; } Expected TwoAddressRewritePass::runImplementation(MachineFunction &Function, const TargetDescription &Target, - MachineAnalysisManager &) { + MachineActionReplay &Actions) { MachinePassResult Result; const std::vector Instructions(Function.allInstructions().begin(), Function.allInstructions().end()); @@ -951,6 +966,12 @@ TwoAddressRewritePass::runImplementation(MachineFunction &Function, const Target if (Instruction->parent() == nullptr) continue; if (Instruction->opcode() == opcode(GenericOpcode::REG_SEQUENCE)) { + auto Accepted = Actions.request("expand-reg-sequence", Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) + return makeUnexpected(ErrorCode::VerificationError, + "required REG_SEQUENCE expansion action was rejected"); auto Expanded = expandRegSequence(*Instruction, Function, Target, *Info); if (!Expanded) return std::unexpected(Expanded.error()); @@ -959,6 +980,12 @@ TwoAddressRewritePass::runImplementation(MachineFunction &Function, const Target continue; } if (Instruction->opcode() == opcode(GenericOpcode::INSERT_SUBREG)) { + auto Accepted = Actions.request("expand-insert-subreg", Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) + return makeUnexpected(ErrorCode::VerificationError, + "required INSERT_SUBREG expansion action was rejected"); auto Expanded = expandInsertSubreg(*Instruction, Function, Target, *Info); if (!Expanded) return std::unexpected(Expanded.error()); @@ -1004,6 +1031,12 @@ TwoAddressRewritePass::runImplementation(MachineFunction &Function, const Target continue; if (isImplicitlyUndef(Function, *Use)) { + auto Accepted = Actions.request("rewrite-undef-tie", Function, Instruction->stableID()); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) + return makeUnexpected(ErrorCode::VerificationError, + "required undef tie rewrite action was rejected"); auto Rewritten = rewriteUndefTie(*Use, *Def); if (!Rewritten) return std::unexpected(Rewritten.error()); @@ -1012,28 +1045,46 @@ TwoAddressRewritePass::runImplementation(MachineFunction &Function, const Target continue; } - auto Commuted = tryCommute(*Instruction, UseIndex, *DefReg, Target, Function); - if (!Commuted) - return std::unexpected(Commuted.error()); - if (*Commuted) { - Result.Changed = true; - Result.Changes.add(MachinePassChange::Operands); - continue; + auto AllowCommute = + Actions.request("commute-tied-operands", Function, Instruction->stableID()); + if (!AllowCommute) + return std::unexpected(AllowCommute.error()); + if (*AllowCommute) { + auto Commuted = tryCommute(*Instruction, UseIndex, *DefReg, Target, Function); + if (!Commuted) + return std::unexpected(Commuted.error()); + if (*Commuted) { + Result.Changed = true; + Result.Changes.add(MachinePassChange::Operands); + continue; + } } - auto Converted = - Info->tryConvertToThreeAddress(*Instruction, DefIndex, UseIndex, Function, Target); - if (!Converted) - return std::unexpected(Converted.error()); - if (*Converted) { - Result.Changed = true; - Result.Changes.add(MachinePassChange::Instructions); - Result.Changes.add(MachinePassChange::Operands); - Result.Changes.add(MachinePassChange::Registers); - Restart = true; - break; + auto AllowConversion = + Actions.request("convert-three-address", Function, Instruction->stableID()); + if (!AllowConversion) + return std::unexpected(AllowConversion.error()); + if (*AllowConversion) { + auto Converted = + Info->tryConvertToThreeAddress(*Instruction, DefIndex, UseIndex, Function, Target); + if (!Converted) + return std::unexpected(Converted.error()); + if (*Converted) { + Result.Changed = true; + Result.Changes.add(MachinePassChange::Instructions); + Result.Changes.add(MachinePassChange::Operands); + Result.Changes.add(MachinePassChange::Registers); + Restart = true; + break; + } } + auto AllowCopy = Actions.request("insert-tied-copy", Function, Instruction->stableID()); + if (!AllowCopy) + return std::unexpected(AllowCopy.error()); + if (!*AllowCopy) + return makeUnexpected(ErrorCode::VerificationError, + "required tied COPY insertion action was rejected"); MachineBasicBlock *Block = Instruction->parent(); if (Block == nullptr) return makeUnexpected(ErrorCode::InternalError, diff --git a/backend/lib/CodeGen/MachineScheduler.cpp b/backend/lib/CodeGen/MachineScheduler.cpp index f861be2..c85489b 100644 --- a/backend/lib/CodeGen/MachineScheduler.cpp +++ b/backend/lib/CodeGen/MachineScheduler.cpp @@ -129,7 +129,8 @@ struct ScheduleCandidate final { scheduleDAG(const ScheduleDAG &DAG, const MachineFunction &Function, const TargetDescription &Target, const TargetSchedulingModel &Model, ScheduleDAGMode Mode, const MachineBlockLiveness &Liveness, - std::uint64_t BlockFrequency, bool EnableBidirectionalScheduling) { + std::uint64_t BlockFrequency, bool EnableBidirectionalScheduling, + MachineActionReplay *Actions) { auto TopPressure = RegisterPressureTracker::create(DAG, Function, Target, Model, Mode, Liveness, RegisterPressureDirection::TopDown); if (!TopPressure) @@ -214,6 +215,14 @@ scheduleDAG(const ScheduleDAG &DAG, const MachineFunction &Function, } const ScheduleUnit &Selected = DAG.units()[Best->Unit]; + if (Actions != nullptr) { + auto Accepted = Actions->request(Best->Bottom ? "schedule-bottom" : "schedule-top", Function, + Selected.StableID); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) + return std::vector{}; + } Scheduled[Best->Unit] = true; if (Best->Bottom) { auto Issued = BottomHazards->issue(Selected, Best->Cycle); @@ -263,7 +272,7 @@ scheduleRegion(MachineBasicBlock &Block, std::span Instruct MachineFunction &Function, const TargetDescription &Target, const TargetSchedulingModel &Model, ScheduleDAGMode Mode, const MachineBlockLiveness &Liveness, std::uint64_t BlockFrequency, - MachineSchedulerOptions Options) { + MachineSchedulerOptions Options, MachineActionReplay *Actions) { if (Instructions.size() < 2 || Instructions.size() > Options.MaximumRegionSize) return false; auto DAG = ScheduleDAG::build(Block, Instructions, Function, Target, Model, Mode); @@ -272,9 +281,11 @@ scheduleRegion(MachineBasicBlock &Block, std::span Instruct if (DAG->units().size() < 2) return false; auto UnitOrder = scheduleDAG(*DAG, Function, Target, Model, Mode, Liveness, BlockFrequency, - Options.EnableBidirectionalScheduling); + Options.EnableBidirectionalScheduling, Actions); if (!UnitOrder) return std::unexpected(UnitOrder.error()); + if (UnitOrder->empty()) + return false; std::vector NewOrder; NewOrder.reserve(Instructions.size()); @@ -305,10 +316,12 @@ scheduleRegion(MachineBasicBlock &Block, std::span Instruct } // namespace -Expected runMachineScheduler( - MachineFunction &Function, const TargetDescription &Target, const TargetSchedulingModel &Model, - ScheduleDAGMode Mode, const LiveVariablesAnalysis::Result &Liveness, - const MachineBlockFrequencyAnalysis::Result &Frequencies, MachineSchedulerOptions Options) { +Expected +runMachineScheduler(MachineFunction &Function, const TargetDescription &Target, + const TargetSchedulingModel &Model, ScheduleDAGMode Mode, + const LiveVariablesAnalysis::Result &Liveness, + const MachineBlockFrequencyAnalysis::Result &Frequencies, + MachineSchedulerOptions Options, MachineActionReplay *Actions) { if (Options.MaximumRegionSize == 0) { return makeUnexpected(ErrorCode::InvalidArgument, "machine scheduler region budget must be nonzero"); @@ -324,7 +337,7 @@ Expected runMachineScheduler( std::vector Region; auto FlushRegion = [&]() -> Expected { auto Changed = scheduleRegion(*Block, Region, Function, Target, Model, Mode, *BlockLiveness, - Frequencies.frequency(*Block), Options); + Frequencies.frequency(*Block), Options, Actions); if (!Changed) return std::unexpected(Changed.error()); BlockChanged |= *Changed; @@ -403,9 +416,10 @@ Expected MachineSchedulerPass::run(MachineFunction &Function, TrialAnalyses.getResult(*Trial->ClonedFunction, Target); if (!TrialFrequencies) return std::unexpected(TrialFrequencies.error()); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); auto TrialResult = runMachineScheduler(*Trial->ClonedFunction, Target, *Model, ScheduleDAGMode::PreRegisterAllocation, **TrialLiveness, - **TrialFrequencies, Options); + **TrialFrequencies, Options, &Actions); if (!TrialResult) return std::unexpected(TrialResult.error()); @@ -415,12 +429,16 @@ Expected MachineSchedulerPass::run(MachineFunction &Function, auto Frequencies = Analyses.getResult(Function, Target); if (!Frequencies) return std::unexpected(Frequencies.error()); + Actions.beginReplay(); auto Run = runMachineScheduler(Function, Target, *Model, ScheduleDAGMode::PreRegisterAllocation, - **Liveness, **Frequencies, Options); + **Liveness, **Frequencies, Options, &Actions); if (!Run) { return std::unexpected(prefixedError( Run.error(), "machine scheduler diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); MachinePassResult Result; Result.Changed = Run->Changed; diff --git a/backend/lib/CodeGen/MachineSchedulerInternal.h b/backend/lib/CodeGen/MachineSchedulerInternal.h index a612548..92f8eea 100644 --- a/backend/lib/CodeGen/MachineSchedulerInternal.h +++ b/backend/lib/CodeGen/MachineSchedulerInternal.h @@ -1,5 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-or-later +// The private header has no stable include-root-relative guard name. +// NOLINTNEXTLINE(llvm-header-guard) #ifndef RCC_LIB_CODEGEN_MACHINESCHEDULERINTERNAL_H #define RCC_LIB_CODEGEN_MACHINESCHEDULERINTERNAL_H @@ -14,6 +16,7 @@ namespace rcc { class MachineBasicBlock; +class MachineActionReplay; class MachineFunction; class TargetDescription; class TargetSchedulingModel; @@ -22,10 +25,12 @@ struct MachineSchedulingRunResult final { std::vector ChangedBlocks; }; -[[nodiscard]] Expected runMachineScheduler( - MachineFunction &Function, const TargetDescription &Target, const TargetSchedulingModel &Model, - ScheduleDAGMode Mode, const LiveVariablesAnalysis::Result &Liveness, - const MachineBlockFrequencyAnalysis::Result &Frequencies, MachineSchedulerOptions Options); +[[nodiscard]] Expected +runMachineScheduler(MachineFunction &Function, const TargetDescription &Target, + const TargetSchedulingModel &Model, ScheduleDAGMode Mode, + const LiveVariablesAnalysis::Result &Liveness, + const MachineBlockFrequencyAnalysis::Result &Frequencies, + MachineSchedulerOptions Options, MachineActionReplay *Actions = nullptr); } // namespace rcc diff --git a/backend/lib/CodeGen/MachineSink.cpp b/backend/lib/CodeGen/MachineSink.cpp index 9e73cea..873de67 100644 --- a/backend/lib/CodeGen/MachineSink.cpp +++ b/backend/lib/CodeGen/MachineSink.cpp @@ -123,8 +123,8 @@ namespace { auto Info = Function.registerInfo().virtualRegisterInfo(*Reg); if (!Info) return std::unexpected(Info.error()); - if (Info->get().Type && Info->get().Type->isFloat() && - !Instruction.flags().has(MachineInstrFlag::NoFPExcept)) { + const auto &Type = Info->get().Type; + if (Type && Type->isFloat() && !Instruction.flags().has(MachineInstrFlag::NoFPExcept)) { return false; } } diff --git a/backend/lib/CodeGen/MachineStackSlotColoring.cpp b/backend/lib/CodeGen/MachineStackSlotColoring.cpp index b3b9af5..e60a7a2 100644 --- a/backend/lib/CodeGen/MachineStackSlotColoring.cpp +++ b/backend/lib/CodeGen/MachineStackSlotColoring.cpp @@ -178,7 +178,7 @@ collectSlotUses(MachineFunction &Function) { [[nodiscard]] bool intervalsOverlap(const SlotInterval &Left, const SlotInterval &Right) noexcept { if (Left.Block != Right.Block) return true; - return !(Left.Last < Right.First || Right.Last < Left.First); + return Left.Last >= Right.First && Right.Last >= Left.First; } struct StackColor final { diff --git a/backend/lib/CodeGen/MachineTailDuplication.cpp b/backend/lib/CodeGen/MachineTailDuplication.cpp index a130680..40d448f 100644 --- a/backend/lib/CodeGen/MachineTailDuplication.cpp +++ b/backend/lib/CodeGen/MachineTailDuplication.cpp @@ -286,7 +286,8 @@ MachineTailDuplicationPass::runImplementation(MachineFunction &Function, Tail = Next; continue; } - const std::size_t Cost = (*Candidate)->Body.size() + 1U; + const auto &TailCandidate = Candidate->value(); + const std::size_t Cost = TailCandidate.Body.size() + 1U; const std::vector Predecessors(Tail->predecessors().begin(), Tail->predecessors().end()); for (MachineBasicBlock *Predecessor : Predecessors) { @@ -296,7 +297,7 @@ MachineTailDuplicationPass::runImplementation(MachineFunction &Function, break; } auto Duplicated = - duplicateTailIntoPredecessor(**Candidate, *Tail, *Predecessor, Function, Target, *Info); + duplicateTailIntoPredecessor(TailCandidate, *Tail, *Predecessor, Function, Target, *Info); if (!Duplicated) return std::unexpected(Duplicated.error()); if (!*Duplicated) diff --git a/backend/lib/CodeGen/PostRAScheduler.cpp b/backend/lib/CodeGen/PostRAScheduler.cpp index 9aff5ac..026f763 100644 --- a/backend/lib/CodeGen/PostRAScheduler.cpp +++ b/backend/lib/CodeGen/PostRAScheduler.cpp @@ -287,9 +287,10 @@ Expected PostRASchedulerPass::run(MachineFunction &Function, TrialAnalyses.getResult(*Trial->ClonedFunction, Target); if (!TrialFrequencies) return std::unexpected(TrialFrequencies.error()); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); auto TrialRun = runMachineScheduler(*Trial->ClonedFunction, Target, *Model, ScheduleDAGMode::PostRegisterAllocation, **TrialLiveness, - **TrialFrequencies, Options); + **TrialFrequencies, Options, &Actions); if (!TrialRun) return std::unexpected(TrialRun.error()); auto TrialRepair = @@ -303,12 +304,16 @@ Expected PostRASchedulerPass::run(MachineFunction &Function, auto Frequencies = Analyses.getResult(Function, Target); if (!Frequencies) return std::unexpected(Frequencies.error()); + Actions.beginReplay(); auto Run = runMachineScheduler(Function, Target, *Model, ScheduleDAGMode::PostRegisterAllocation, - **Liveness, **Frequencies, Options); + **Liveness, **Frequencies, Options, &Actions); if (!Run) { return std::unexpected(prefixedError( Run.error(), "post-RA scheduler diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); auto Repaired = repairPostRAMachineBlockLiveness(Run->ChangedBlocks, Target, **Liveness); if (!Repaired) return std::unexpected(Repaired.error()); diff --git a/backend/lib/CodeGen/RegAllocBase.cpp b/backend/lib/CodeGen/RegAllocBase.cpp index 1aae22d..5d065dd 100644 --- a/backend/lib/CodeGen/RegAllocBase.cpp +++ b/backend/lib/CodeGen/RegAllocBase.cpp @@ -63,7 +63,8 @@ Expected RegAllocBase::run() { std::ranges::sort(Round->Spilled, [](Register Left, Register Right) { return Left.opaqueValue() < Right.opaqueValue(); }); - Spiller ValueSpiller(Function, Target, TargetInfo, Mapping, Options.EnableRematerialization); + Spiller ValueSpiller(Function, Target, TargetInfo, Mapping, Options.EnableRematerialization, + Actions); for (const Register VirtualReg : Round->Spilled) { VirtRegMapEntry *Entry = Mapping.entry(VirtualReg); if (Entry != nullptr) { diff --git a/backend/lib/CodeGen/RegAllocBase.h b/backend/lib/CodeGen/RegAllocBase.h index 5af5ed0..a1682b2 100644 --- a/backend/lib/CodeGen/RegAllocBase.h +++ b/backend/lib/CodeGen/RegAllocBase.h @@ -28,9 +28,9 @@ public: protected: RegAllocBase(MachineFunction &Function, const TargetDescription &Target, const TargetRegisterAllocationInfo &TargetInfo, MachineAnalysisManager &Analyses, - RegisterAllocationOptions Options) noexcept + RegisterAllocationOptions Options, MachineActionReplay *Actions = nullptr) noexcept : Function(Function), Target(Target), TargetInfo(TargetInfo), Analyses(Analyses), - Options(Options) {} + Options(Options), Actions(Actions) {} [[nodiscard]] virtual Expected allocateRound(const LiveIntervalsAnalysis::Result &Intervals, VirtRegMap &Mapping) = 0; @@ -40,6 +40,7 @@ protected: const TargetRegisterAllocationInfo &TargetInfo; MachineAnalysisManager &Analyses; RegisterAllocationOptions Options; + MachineActionReplay *Actions = nullptr; }; [[nodiscard]] std::vector diff --git a/backend/lib/CodeGen/RegAllocPBQP.cpp b/backend/lib/CodeGen/RegAllocPBQP.cpp index a5a12f7..f1da2c1 100644 --- a/backend/lib/CodeGen/RegAllocPBQP.cpp +++ b/backend/lib/CodeGen/RegAllocPBQP.cpp @@ -15,9 +15,10 @@ #include #include #include +#include #include #include -#include +#include #include #include @@ -30,9 +31,9 @@ struct PBQPAllocationNode final { std::vector> Options; }; -[[nodiscard]] std::set> +[[nodiscard]] std::map, MachineInstrStableID> copyRelatedRegisters(const MachineFunction &Function) { - std::set> Result; + std::map, MachineInstrStableID> Result; for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) { for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; @@ -48,12 +49,33 @@ copyRelatedRegisters(const MachineFunction &Function) { std::uint32_t Right = Source->opaqueValue(); if (Right < Left) std::swap(Left, Right); - Result.emplace(Left, Right); + Result.try_emplace({Left, Right}, Instruction->stableID()); } } return Result; } +[[nodiscard]] std::string_view reductionName(pbqp::ReductionKind Kind) noexcept { + switch (Kind) { + case pbqp::ReductionKind::DegreeZero: + return "degree-zero"; + case pbqp::ReductionKind::DegreeOne: + return "degree-one"; + case pbqp::ReductionKind::DegreeTwo: + return "degree-two"; + case pbqp::ReductionKind::Heuristic: + return "heuristic"; + } + return "unknown"; +} + +[[nodiscard]] MachineInstrStableID actionInstructionForRegister(const MachineFunction &Function, + Register Reg) noexcept { + const MachineOperand *Definition = Function.registerInfo().getUniqueVRegDef(Reg); + const MachineInstr *Instruction = Definition == nullptr ? nullptr : Definition->parent(); + return Instruction == nullptr ? MachineInstrStableID{} : Instruction->stableID(); +} + [[nodiscard]] pbqp::Cost boundedCost(std::uint64_t Value) noexcept { const std::uint64_t Limit = static_cast(pbqp::InfiniteCost - 1); return static_cast(std::min(Value, Limit)); @@ -63,8 +85,9 @@ class PBQPAllocatorEngine final : public RegAllocBase { public: PBQPAllocatorEngine(MachineFunction &Function, const TargetDescription &Target, const TargetRegisterAllocationInfo &TargetInfo, - MachineAnalysisManager &Analyses, RegisterAllocationOptions Options) - : RegAllocBase(Function, Target, TargetInfo, Analyses, Options) {} + MachineAnalysisManager &Analyses, RegisterAllocationOptions Options, + MachineActionReplay *Actions) + : RegAllocBase(Function, Target, TargetInfo, Analyses, Options, Actions) {} private: [[nodiscard]] Expected @@ -152,8 +175,14 @@ private: std::uint32_t RightKey = Right.VirtualReg.opaqueValue(); if (RightKey < LeftKey) std::swap(LeftKey, RightKey); - const bool CopyRelated = - Options.EnableCopyCoalescing && CopyPairs.contains({LeftKey, RightKey}); + const auto CopyPosition = CopyPairs.find({LeftKey, RightKey}); + bool CopyRelated = Options.EnableCopyCoalescing && CopyPosition != CopyPairs.end(); + if (CopyRelated && Actions != nullptr) { + auto Accepted = Actions->request("coalesce-copy", Function, CopyPosition->second); + if (!Accepted) + return std::unexpected(Accepted.error()); + CopyRelated = *Accepted; + } pbqp::CostMatrix Costs(Left.Options.size(), Right.Options.size(), 0); bool HasCost = false; for (std::size_t LeftOption = 1; LeftOption < Left.Options.size(); ++LeftOption) { @@ -188,7 +217,23 @@ private: } pbqp::DeterministicSolver Solver; - auto Solution = Solver.solve(Problem); + auto Solution = Solver.solve(Problem, [&](const pbqp::SolverAction &Action) -> Expected { + if (Actions == nullptr) + return true; + if (Action.Node.value() >= Nodes.size()) { + return makeUnexpected(ErrorCode::InternalError, + "PBQP action names an unknown allocation node"); + } + const Register VirtualReg = Nodes[Action.Node.value()].VirtualReg; + std::string Rule = + Action.Kind == pbqp::SolverActionKind::Reduction ? "pbqp-reduce." : "pbqp-choice."; + Rule.append(reductionName(Action.Reduction)); + if (Action.Kind == pbqp::SolverActionKind::Choice) { + Rule.append(".option-"); + Rule.append(std::to_string(Action.Value)); + } + return Actions->request(Rule, Function, actionInstructionForRegister(Function, VirtualReg)); + }); if (!Solution) return std::unexpected(Solution.error()); @@ -267,7 +312,9 @@ Expected PBQPAllocatorPass::run(MachineFunction &Function, auto Registered = registerStage11MachineAnalyses(TrialAnalyses); if (!Registered) return std::unexpected(Registered.error()); - PBQPAllocatorEngine TrialAllocator(*Trial->ClonedFunction, Target, *Info, TrialAnalyses, Options); + MachineActionReplay Actions(Analyses, Trial->InstructionStableIDs); + PBQPAllocatorEngine TrialAllocator(*Trial->ClonedFunction, Target, *Info, TrialAnalyses, Options, + &Actions); auto TrialRun = TrialAllocator.run(); if (!TrialRun) return std::unexpected(TrialRun.error()); @@ -275,12 +322,16 @@ Expected PBQPAllocatorPass::run(MachineFunction &Function, regalloc_detail::verifyAllocationTrial(*Trial->ClonedFunction, Target, contract()); if (!TrialVerification) return std::unexpected(TrialVerification.error()); - PBQPAllocatorEngine Allocator(Function, Target, *Info, Analyses, Options); + Actions.beginReplay(); + PBQPAllocatorEngine Allocator(Function, Target, *Info, Analyses, Options, &Actions); auto Allocated = Allocator.run(); if (!Allocated) { return std::unexpected(regalloc_detail::withPrefix( Allocated.error(), "PBQPAllocator diverged after a successful transaction preflight: ")); } + auto Replayed = Actions.finishReplay(); + if (!Replayed) + return std::unexpected(Replayed.error()); auto Reserved = regalloc_detail::reserveFunctionRegisters(Function, FunctionReserved); if (!Reserved) return std::unexpected(Reserved.error()); diff --git a/backend/lib/CodeGen/RegisterPressure.cpp b/backend/lib/CodeGen/RegisterPressure.cpp index 523a443..cbd8d8e 100644 --- a/backend/lib/CodeGen/RegisterPressure.cpp +++ b/backend/lib/CodeGen/RegisterPressure.cpp @@ -102,12 +102,12 @@ struct RegisterPressureBuilder final { auto Info = Function->registerInfo().virtualRegisterInfo(Reg); if (!Info) return std::unexpected(Info.error()); - if (!Info->get().RegClass) { + const auto &RegClass = Info->get().RegClass; + if (!RegClass) { return makeUnexpected(ErrorCode::VerificationError, "pre-RA pressure tracking requires constrained virtual registers"); } - const std::span Sets = - Model->pressureSetsForClass(*Info->get().RegClass); + const std::span Sets = Model->pressureSetsForClass(*RegClass); return std::vector{Sets.begin(), Sets.end()}; } diff --git a/backend/lib/MIR/CMakeLists.txt b/backend/lib/MIR/CMakeLists.txt index 2ef63ed..f227390 100644 --- a/backend/lib/MIR/CMakeLists.txt +++ b/backend/lib/MIR/CMakeLists.txt @@ -6,8 +6,16 @@ set( LLVMConstantSyntax.cpp MIDebugSyntax.cpp MILexer.cpp + MIRDataParser.cpp MIRDocument.cpp + MIRDocumentParser.cpp + MIRDocumentReferenceValidation.cpp MIRDocumentPrinter.cpp + MIRFrameParser.cpp + MIRFunctionParser.cpp + MIRFunctionRecordsParser.cpp + MIRMetadataParser.cpp + MIRMetadataTextParser.cpp MIParser.cpp MIPrinter.cpp ) @@ -55,9 +63,20 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntax.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIDebugSyntax.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MILexer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDataParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocument.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentParser.h" "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentReferenceValidation.h" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentReferenceValidation.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentPrinter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRFrameParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRFunctionParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRFunctionRecordsParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataTextParser.h" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataTextParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRParserUtilities.h" "${CMAKE_CURRENT_SOURCE_DIR}/MIParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIPrinter.cpp" ${RCCBackendMIRHeaders} @@ -67,8 +86,16 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/LLVMConstantSyntax.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIDebugSyntax.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MILexer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDataParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocument.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentReferenceValidation.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIRDocumentPrinter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRFrameParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRFunctionParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRFunctionRecordsParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataParser.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRMetadataTextParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIParser.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/MIPrinter.cpp" ) diff --git a/backend/lib/MIR/MIParser.cpp b/backend/lib/MIR/MIParser.cpp index ecf4986..483c2f2 100644 --- a/backend/lib/MIR/MIParser.cpp +++ b/backend/lib/MIR/MIParser.cpp @@ -1098,14 +1098,21 @@ bool MIParser::Impl::parseRegisterOperand(bool IsDef, ParsedOperand &Result) { } else if (at(MITokenKind::Identifier)) { if (const RegisterClassDesc *Class = Target.registers().registerClass(token().stringValue())) { - if ((Info.RegClass && *Info.RegClass != Class->ID) || Info.Bank) + if ((Info.RegClass && *Info.RegClass != Class->ID) || + (Info.Bank && *Info.Bank != Class->DefaultBank)) return fail("conflicting virtual register class"); auto SetClass = Function.registerInfo().setRegisterClass(Reg, Class->ID); if (!SetClass) return importError(token(), SetClass.error()); + auto SetBank = Function.registerInfo().setRegisterBank(Reg, Class->DefaultBank); + if (!SetBank) + return importError(token(), SetBank.error()); } else if (const RegisterBankDesc *Bank = Target.registers().registerBank(token().stringValue())) { - if ((Info.Bank && *Info.Bank != Bank->ID) || Info.RegClass) + const RegisterClassDesc *ExistingClass = + Info.RegClass ? Target.registers().registerClass(*Info.RegClass) : nullptr; + if ((Info.Bank && *Info.Bank != Bank->ID) || + (Info.RegClass && (ExistingClass == nullptr || ExistingClass->DefaultBank != Bank->ID))) return fail("conflicting virtual register bank"); auto SetBank = Function.registerInfo().setRegisterBank(Reg, Bank->ID); if (!SetBank) diff --git a/backend/lib/MIR/MIRDataParser.cpp b/backend/lib/MIR/MIRDataParser.cpp new file mode 100644 index 0000000..fc70f0f --- /dev/null +++ b/backend/lib/MIR/MIRDataParser.cpp @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MIRDocumentParser.h" +#include "MIRDocumentReferenceValidation.h" +#include "MIRMetadataTextParser.h" +#include "MIRParserUtilities.h" +#include "rcc/CodeGen/MachineBasicBlock.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/MachineVerifier.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/MIR/LLVMConstantSyntax.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +using parser_detail::parseBool; +using parser_detail::parsePrefixedID; +using parser_detail::parseSigned; +using parser_detail::parseUnsigned; +using parser_detail::parseUnsigned32; +using parser_detail::trim; + +bool MIRDocumentParser::parseConstants(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + const yaml::Node *Constants = mappingValue(Root, "constants"); + if (Constants == nullptr) + return false; + if (requireKind(*Constants, yaml::NodeKind::Sequence, "constant pool")) + return true; + constexpr std::array Keys{ + std::string_view{"id"}, + std::string_view{"value"}, + std::string_view{"alignment"}, + std::string_view{"isTargetSpecific"}, + }; + std::vector NamedTypes; + const std::string_view DataLayout = effectiveDataLayout(); + if (ActiveDocument->EmbeddedIR) { + const auto Definitions = ActiveDocument->EmbeddedIR->typeDefinitions(); + NamedTypes.reserve(Definitions.size()); + for (const EmbeddedIRTypeDefinition &Definition : Definitions) { + NamedTypes.push_back({.Name = Definition.Name, + .Definition = Definition.Definition, + .NumericName = Definition.NumericName}); + } + } + std::unordered_set IDs; + for (const yaml::NodeID ConstantID : Constants->sequence()) { + const yaml::Node *Constant = node(ConstantID); + if (Constant == nullptr || + requireKind(*Constant, yaml::NodeKind::Mapping, "constant pool entry") || + validateKeys(*Constant, Keys, "constant pool entry")) { + return true; + } + const yaml::Node *IDNode = mappingValue(*Constant, "id"); + const yaml::Node *ValueNode = mappingValue(*Constant, "value"); + if (IDNode == nullptr || ValueNode == nullptr) + return fail(Constant->range(), "constant pool entry requires 'id' and 'value'"); + std::uint64_t WideID = 0; + if (unsignedValue(*IDNode, WideID, "a 32-bit constant pool ID") || + WideID > std::numeric_limits::max()) { + return true; + } + const auto TextID = static_cast(WideID); + if (!IDs.insert(TextID).second) + return fail(IDNode->range(), "duplicate constant pool ID"); + + bool IsTargetSpecific = false; + if (const yaml::Node *TargetNode = mappingValue(*Constant, "isTargetSpecific")) { + if (boolean(*TargetNode, IsTargetSpecific)) + return true; + } + if (IsTargetSpecific) { + std::string_view ValueText; + if (scalar(*ValueNode, ValueText, "target constant")) + return true; + auto TargetConstantValue = Target.mirText().parseTargetConstant(ValueText); + if (!TargetConstantValue) + return importError(ValueNode->range(), TargetConstantValue.error(), "mir-target-constant"); + auto Emission = TargetConstantValue->emit(); + if (!Emission) + return importError(ValueNode->range(), Emission.error(), "mir-target-constant"); + auto RequiredAlignment = TargetConstantValue->requiredAlignment(); + if (!RequiredAlignment) + return importError(ValueNode->range(), RequiredAlignment.error(), "mir-target-constant"); + + MachineConstantPoolEntryDesc Description; + Description.Size = Emission->Bytes.size(); + Description.Align = *RequiredAlignment; + Description.Value = std::move(*TargetConstantValue); + if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) { + if (alignment(*AlignmentNode, Description.Align, true)) + return true; + } + auto Added = Function.constantPool().addEntry(std::move(Description)); + if (!Added) + return importError(Constant->range(), Added.error()); + Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added}); + continue; + } + + std::string_view ValueText; + if (scalar(*ValueNode, ValueText, "LLVM constant")) + return true; + const LLVMConstantSyntaxOptions ConstantOptions{ + .DefaultPointerSizeInBits = Context.DefaultPointerSizeInBits, + .DataLayout = DataLayout, + .NamedTypes = NamedTypes, + }; + auto Data = parseLLVMConstant(trim(ValueText), ConstantOptions); + if (!Data) + return importError(ValueNode->range(), Data.error(), "mir-llvm-constant"); + auto Size = llvmConstantStorageSize(Data->Type, ConstantOptions); + if (!Size) + return importError(ValueNode->range(), Size.error(), "mir-llvm-constant"); + + MachineConstantPoolEntryDesc Description; + Description.Size = *Size; + Description.Value = std::move(*Data); + if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) { + if (alignment(*AlignmentNode, Description.Align, true)) + return true; + } else { + auto Preferred = llvmConstantPreferredAlignment( + std::get(Description.Value).Type, ConstantOptions); + if (!Preferred) + return importError(ValueNode->range(), Preferred.error(), "mir-llvm-constant"); + Description.Align = *Preferred; + } + auto Added = Function.constantPool().addEntry(std::move(Description)); + if (!Added) + return importError(Constant->range(), Added.error()); + Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added}); + } + return false; +} + +bool MIRDocumentParser::reserveJumpTables(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context, + std::vector &Pending) { + const yaml::Node *JumpTable = mappingValue(Root, "jumpTable"); + if (JumpTable == nullptr) + return false; + if (requireKind(*JumpTable, yaml::NodeKind::Mapping, "jump table")) + return true; + constexpr std::array JumpTableKeys{ + std::string_view{"kind"}, + std::string_view{"entries"}, + }; + if (validateKeys(*JumpTable, JumpTableKeys, "jump table")) + return true; + const yaml::Node *KindNode = mappingValue(*JumpTable, "kind"); + if (KindNode == nullptr) + return fail(JumpTable->range(), "jump table requires 'kind'"); + std::string_view KindName; + if (scalar(*KindNode, KindName, "jump table kind")) + return true; + + JumpTableEntryEncoding Encoding; + std::uint8_t EntrySize = 8; + if (KindName == "block-address") + Encoding = JumpTableEntryEncoding::BlockAddress; + else if (KindName == "gp-rel64-block-address") + Encoding = JumpTableEntryEncoding::GPRelative64BlockAddress; + else if (KindName == "gp-rel32-block-address") { + Encoding = JumpTableEntryEncoding::GPRelative32BlockAddress; + EntrySize = 4; + } else if (KindName == "label-difference32") { + Encoding = JumpTableEntryEncoding::LabelDifference32; + EntrySize = 4; + } else if (KindName == "label-difference64") + Encoding = JumpTableEntryEncoding::LabelDifference64; + else if (KindName == "inline") { + Encoding = JumpTableEntryEncoding::Inline; + EntrySize = 4; + } else if (KindName == "custom32") { + Encoding = JumpTableEntryEncoding::Custom32; + EntrySize = 4; + } else { + return fail(KindNode->range(), "unknown jump table kind '" + std::string(KindName) + "'"); + } + + const yaml::Node *Entries = mappingValue(*JumpTable, "entries"); + if (Entries == nullptr) + return false; + if (requireKind(*Entries, yaml::NodeKind::Sequence, "jump table entries")) + return true; + constexpr std::array EntryKeys{ + std::string_view{"id"}, + std::string_view{"blocks"}, + }; + std::unordered_set IDs; + for (const yaml::NodeID EntryID : Entries->sequence()) { + const yaml::Node *Entry = node(EntryID); + if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "jump table entry") || + validateKeys(*Entry, EntryKeys, "jump table entry")) { + return true; + } + const yaml::Node *IDNode = mappingValue(*Entry, "id"); + if (IDNode == nullptr) + return fail(Entry->range(), "jump table entry requires 'id'"); + std::uint64_t WideID = 0; + if (unsignedValue(*IDNode, WideID, "a 32-bit jump table ID") || + WideID > std::numeric_limits::max()) { + return true; + } + const auto TextID = static_cast(WideID); + if (!IDs.insert(TextID).second) + return fail(IDNode->range(), "duplicate jump table ID"); + auto Reserved = Function.jumpTableInfo().reserveTable(); + if (!Reserved) + return importError(Entry->range(), Reserved.error()); + PendingJumpTable NewPending{ + .Index = *Reserved, + .TextID = TextID, + .Encoding = Encoding, + .EntrySize = EntrySize, + .Blocks = {}, + }; + if (const yaml::Node *Blocks = mappingValue(*Entry, "blocks")) { + if (requireKind(*Blocks, yaml::NodeKind::Sequence, "jump table blocks")) + return true; + for (const yaml::NodeID BlockID : Blocks->sequence()) { + const yaml::Node *BlockNode = node(BlockID); + std::string_view Text; + if (BlockNode == nullptr || scalar(*BlockNode, Text, "basic block reference")) + return true; + NewPending.Blocks.emplace_back(Text); + } + } + if (NewPending.Blocks.empty()) + return fail(Entry->range(), "jump table entry must contain at least one block"); + Context.JumpTables.push_back({.TextID = TextID, .Index = *Reserved}); + Pending.push_back(std::move(NewPending)); + } + return false; +} + +bool MIRDocumentParser::defineJumpTables(MachineFunction &Function, + std::span Pending) { + for (const PendingJumpTable &Table : Pending) { + MachineJumpTableDesc Description; + Description.Encoding = Table.Encoding; + Description.EntrySize = Table.EntrySize; + Description.Align = *Alignment::create(Table.EntrySize); + for (const std::string &Text : Table.Blocks) { + MachineBasicBlock *Block = nullptr; + if (parseBlockReference(Text, Result.Source.range(0, 0), Function, Block)) + return true; + if (Block == nullptr) + return fail(Result.Source.range(0, 0), "reference to undefined machine basic block"); + Description.Targets.push_back(Block); + } + auto DefineResult = + Function.jumpTableInfo().defineReservedTable(Table.Index, std::move(Description)); + if (!DefineResult) + return importError(Result.Source.range(0, 0), DefineResult.error()); + } + return false; +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRDocument.cpp b/backend/lib/MIR/MIRDocument.cpp index 67e6ffb..ad9ec40 100644 --- a/backend/lib/MIR/MIRDocument.cpp +++ b/backend/lib/MIR/MIRDocument.cpp @@ -2,2755 +2,12 @@ #include "rcc/MIR/MIRDocument.h" -#include "MIRDocumentReferenceValidation.h" -#include "rcc/CodeGen/MachineBasicBlock.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/MachineVerifier.h" -#include "rcc/CodeGen/TargetDescription.h" -#include "rcc/CodeGen/TargetMIRTextInfo.h" -#include "rcc/CodeGen/TargetRegisterInfo.h" -#include "rcc/MIR/LLVMConstantSyntax.h" -#include "rcc/MIR/MIDebugSyntax.h" -#include "rcc/MIR/MILexer.h" +#include "MIRDocumentParser.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include -#include namespace rcc::mir { -namespace { - -[[nodiscard]] std::string_view trim(std::string_view Text) noexcept { - while (!Text.empty() && (Text.front() == ' ' || Text.front() == '\t' || Text.front() == '\r' || - Text.front() == '\n')) { - Text.remove_prefix(1); - } - while (!Text.empty() && (Text.back() == ' ' || Text.back() == '\t' || Text.back() == '\r' || - Text.back() == '\n')) { - Text.remove_suffix(1); - } - return Text; -} - -[[nodiscard]] bool parseUnsigned(std::string_view Text, std::uint64_t &Value) noexcept { - Text = trim(Text); - int Base = 10; - if (Text.starts_with("0x") || Text.starts_with("0X")) { - Text.remove_prefix(2); - Base = 16; - } - if (Text.empty()) - return false; - const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, Base); - return Error == std::errc{} && End == Text.data() + Text.size(); -} - -[[nodiscard]] bool parseSigned(std::string_view Text, std::int64_t &Value) noexcept { - Text = trim(Text); - if (Text.empty()) - return false; - const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, 10); - return Error == std::errc{} && End == Text.data() + Text.size(); -} - -[[nodiscard]] bool parseUnsigned32(std::string_view Text, std::uint32_t &Value) noexcept { - std::uint64_t Wide = 0; - if (!parseUnsigned(Text, Wide) || Wide > std::numeric_limits::max()) - return false; - Value = static_cast(Wide); - return true; -} - -[[nodiscard]] bool parseBool(std::string_view Text, bool &Value) noexcept { - if (Text == "true") { - Value = true; - return true; - } - if (Text == "false") { - Value = false; - return true; - } - return false; -} - -[[nodiscard]] std::optional parsePrefixedID(std::string_view Text, - std::string_view Prefix) noexcept { - Text = trim(Text); - if (!Text.starts_with(Prefix)) - return std::nullopt; - Text.remove_prefix(Prefix.size()); - const std::size_t Suffix = Text.find_first_of(". \t"); - if (Suffix != std::string_view::npos) - Text = Text.substr(0, Suffix); - std::uint32_t ID = 0; - if (!parseUnsigned32(Text, ID)) - return std::nullopt; - return ID; -} - -struct PendingJumpTable final { - JumpTableIndex Index; - std::uint32_t TextID = 0; - JumpTableEntryEncoding Encoding = JumpTableEntryEncoding::BlockAddress; - std::uint8_t EntrySize = 8; - std::vector Blocks; -}; - -class MetadataTextParser final { -public: - using Resolver = std::function(std::uint32_t Slot)>; - - MetadataTextParser(std::string Text, Resolver Resolve) - : Lexer("MIR metadata scalar", std::move(Text)), Resolve(std::move(Resolve)) { - do { - Tokens.push_back(Lexer.next()); - } while (!Tokens.back().isErrorOrEnd()); - } - - [[nodiscard]] Expected parseReferenceOnly(); - [[nodiscard]] Expected parseValue(); - [[nodiscard]] Expected parseLocation(); - -private: - [[nodiscard]] const MIToken &token() const noexcept { - return Tokens[Position]; - } - [[nodiscard]] bool at(MITokenKind Kind) const noexcept { - return token().is(Kind); - } - void advance() noexcept { - if (Position + 1U < Tokens.size()) - ++Position; - } - [[nodiscard]] bool consume(MITokenKind Kind) noexcept { - if (!at(Kind)) - return false; - advance(); - return true; - } - [[nodiscard]] Expected expect(MITokenKind Kind, std::string_view Description); - [[nodiscard]] Expected finish(); - [[nodiscard]] Expected parseReference(); - [[nodiscard]] Expected parseExpression(); - [[nodiscard]] Expected parseDILocation(std::vector &Frames, - std::optional &Terminal); - - MILexer Lexer; - Resolver Resolve; - std::vector Tokens; - std::size_t Position = 0; -}; - -Expected MetadataTextParser::expect(MITokenKind Kind, std::string_view Description) { - if (consume(Kind)) - return {}; - return makeUnexpected(ErrorCode::ParseError, - "expected " + std::string(Description) + " in MIR metadata scalar"); -} - -Expected MetadataTextParser::finish() { - while (consume(MITokenKind::Newline)) { - } - if (Lexer.failed()) { - if (!Lexer.diagnostics().empty()) { - return makeUnexpected(ErrorCode::ParseError, - std::string(Lexer.diagnostics().front().Message)); - } - return makeUnexpected(ErrorCode::ParseError, "invalid MIR metadata scalar"); - } - if (!at(MITokenKind::EndOfFile)) - return makeUnexpected(ErrorCode::ParseError, "unexpected trailing MIR metadata tokens"); - return {}; -} - -Expected MetadataTextParser::parseReference() { - if (auto Exclaim = expect(MITokenKind::Exclaim, "'!' before a metadata slot"); !Exclaim) - return std::unexpected(Exclaim.error()); - if (!at(MITokenKind::IntegerLiteral)) - return makeUnexpected(ErrorCode::ParseError, "expected a 32-bit metadata slot"); - std::uint32_t Slot = 0; - if (!parseUnsigned32(token().integerValue(), Slot)) - return makeUnexpected(ErrorCode::ParseError, "metadata slot is outside the 32-bit range"); - advance(); - return Resolve(Slot); -} - -Expected MetadataTextParser::parseReferenceOnly() { - auto Reference = parseReference(); - if (!Reference) - return std::unexpected(Reference.error()); - if (auto Finished = finish(); !Finished) - return std::unexpected(Finished.error()); - return Reference; -} - -Expected MetadataTextParser::parseExpression() { - if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DIExpression")) { - return makeUnexpected(ErrorCode::ParseError, "expected an inline !DIExpression"); - } - advance(); - if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DIExpression"); !Left) - return std::unexpected(Left.error()); - std::vector Elements; - while (!at(MITokenKind::RightParen)) { - if (at(MITokenKind::Identifier)) { - const auto Opcode = parseDIExpressionOpcodeName(token().stringValue()); - if (!Opcode) { - return makeUnexpected(ErrorCode::ParseError, "unknown DIExpression opcode '" + - std::string(token().stringValue()) + "'"); - } - Elements.push_back(*Opcode); - advance(); - } else if (at(MITokenKind::IntegerLiteral)) { - std::int64_t Signed = 0; - std::uint64_t Unsigned = 0; - if (parseSigned(token().integerValue(), Signed)) - Elements.push_back(static_cast(Signed)); - else if (parseUnsigned(token().integerValue(), Unsigned)) - Elements.push_back(Unsigned); - else - return makeUnexpected(ErrorCode::ParseError, "DIExpression integer exceeds 64 bits"); - advance(); - } else { - return makeUnexpected(ErrorCode::ParseError, "expected a DIExpression opcode or integer"); - } - if (!consume(MITokenKind::Comma)) - break; - } - if (auto Right = expect(MITokenKind::RightParen, "')' after !DIExpression"); !Right) - return std::unexpected(Right.error()); - return MachineDIExpression(std::move(Elements)); -} - -Expected MetadataTextParser::parseValue() { - MachineMetadataValue Value; - if (at(MITokenKind::Exclaim)) { - auto Reference = parseReference(); - if (!Reference) - return std::unexpected(Reference.error()); - auto Created = MachineMetadataValue::reference(*Reference); - if (!Created) - return std::unexpected(Created.error()); - Value = std::move(*Created); - } else { - auto Expression = parseExpression(); - if (!Expression) - return std::unexpected(Expression.error()); - Value = MachineMetadataValue::expression(std::move(*Expression)); - } - if (auto Finished = finish(); !Finished) - return std::unexpected(Finished.error()); - return Value; -} - -Expected -MetadataTextParser::parseDILocation(std::vector &Frames, - std::optional &Terminal) { - if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DILocation")) { - return makeUnexpected(ErrorCode::ParseError, "expected an inline !DILocation"); - } - advance(); - if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DILocation"); !Left) - return Left; - - MachineDILocationFrame Frame; - bool HasLine = false; - bool HasScope = false; - std::vector NestedFrames; - std::optional NestedTerminal; - std::unordered_set Seen; - while (!at(MITokenKind::RightParen)) { - if (!at(MITokenKind::Identifier)) - return makeUnexpected(ErrorCode::ParseError, "expected a DILocation field name"); - const std::string Field(token().stringValue()); - if (!Seen.insert(Field).second) { - return makeUnexpected(ErrorCode::ParseError, "duplicate DILocation field '" + Field + "'"); - } - advance(); - if (auto Colon = expect(MITokenKind::Colon, "':' after a DILocation field"); !Colon) - return Colon; - - if (Field == "line") { - std::uint32_t Value = 0; - if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned32(token().integerValue(), Value)) { - return makeUnexpected(ErrorCode::ParseError, - "DILocation line must be an unsigned 32-bit integer"); - } - Frame.Line = Value; - HasLine = true; - advance(); - } else if (Field == "column") { - std::uint64_t Value = 0; - if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) || - Value > std::numeric_limits::max()) { - return makeUnexpected(ErrorCode::ParseError, - "DILocation column must be an unsigned 16-bit integer"); - } - Frame.Column = static_cast(Value); - advance(); - } else if (Field == "scope") { - auto Scope = parseReference(); - if (!Scope) - return std::unexpected(Scope.error()); - if (Scope->scope() != MachineMetadataScope::EmbeddedIR) { - return makeUnexpected(ErrorCode::ParseError, - "DILocation scope must reference embedded LLVM IR metadata"); - } - Frame.Scope = *Scope; - HasScope = true; - } else if (Field == "inlinedAt") { - if (at(MITokenKind::MetadataKeyword) && token().isMetadataKeyword("!DILocation")) { - if (auto Nested = parseDILocation(NestedFrames, NestedTerminal); !Nested) - return Nested; - } else { - auto Reference = parseReference(); - if (!Reference) - return std::unexpected(Reference.error()); - if (Reference->scope() != MachineMetadataScope::EmbeddedIR) { - return makeUnexpected(ErrorCode::ParseError, - "DILocation inlinedAt must reference embedded LLVM IR metadata"); - } - NestedTerminal = *Reference; - } - } else if (Field == "isImplicitCode") { - if (!at(MITokenKind::Identifier) || - (token().stringValue() != "true" && token().stringValue() != "false")) { - return makeUnexpected(ErrorCode::ParseError, - "DILocation isImplicitCode must be true or false"); - } - Frame.IsImplicitCode = token().stringValue() == "true"; - advance(); - } else if (Field == "atomGroup") { - std::uint64_t Value = 0; - if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) || - Value > MachineDILocation::MaxAtomGroup) { - return makeUnexpected(ErrorCode::ParseError, "DILocation atomGroup exceeds 61 bits"); - } - Frame.AtomGroup = Value; - advance(); - } else if (Field == "atomRank") { - std::uint64_t Value = 0; - if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) || - Value > 7) { - return makeUnexpected(ErrorCode::ParseError, "DILocation atomRank exceeds three bits"); - } - Frame.AtomRank = static_cast(Value); - advance(); - } else { - return makeUnexpected(ErrorCode::ParseError, "unknown DILocation field '" + Field + "'"); - } - if (!consume(MITokenKind::Comma)) - break; - } - if (auto Right = expect(MITokenKind::RightParen, "')' after !DILocation"); !Right) - return Right; - if (!HasLine || !HasScope) - return makeUnexpected(ErrorCode::ParseError, "DILocation requires line and scope fields"); - Frames.push_back(Frame); - Frames.insert(Frames.end(), NestedFrames.begin(), NestedFrames.end()); - Terminal = NestedTerminal; - return {}; -} - -Expected MetadataTextParser::parseLocation() { - MachineDebugLocation Location; - if (at(MITokenKind::Exclaim)) { - auto Reference = parseReference(); - if (!Reference) - return std::unexpected(Reference.error()); - auto Created = MachineDebugLocation::reference(*Reference); - if (!Created) - return std::unexpected(Created.error()); - Location = std::move(*Created); - } else { - std::vector Frames; - std::optional Terminal; - if (auto Parsed = parseDILocation(Frames, Terminal); !Parsed) - return std::unexpected(Parsed.error()); - auto Created = MachineDILocation::create(std::move(Frames), Terminal); - if (!Created) - return std::unexpected(Created.error()); - Location = MachineDebugLocation::inlineLocation(std::move(*Created)); - } - if (auto Finished = finish(); !Finished) - return std::unexpected(Finished.error()); - return Location; -} - -} // namespace - -class MIRDocumentParser final { -public: - MIRDocumentParser(const TargetDescription &Target, std::string SourceName, std::string SourceText, - MIRDocumentParserOptions Options) - : Target(Target), Options(std::move(Options)), - Result(std::move(SourceName), std::move(SourceText)) {} - - [[nodiscard]] MIRDocumentParseResult run(); - -private: - [[nodiscard]] bool fail(const TextRange &Range, std::string Message, - std::string Category = "mir-yaml"); - [[nodiscard]] bool importError(const TextRange &Range, const Error &Failure, - std::string Category = "mir-yaml"); - [[nodiscard]] const yaml::Node *node(yaml::NodeID ID) const noexcept { - return Stream->node(ID); - } - [[nodiscard]] const yaml::Node *mappingValue(const yaml::Node &Mapping, - std::string_view Key) const noexcept; - [[nodiscard]] bool requireKind(const yaml::Node &Node, yaml::NodeKind Kind, - std::string_view Description); - [[nodiscard]] bool validateKeys(const yaml::Node &Mapping, std::span Keys, - std::string_view Description); - [[nodiscard]] bool scalar(const yaml::Node &Node, std::string_view &Value, - std::string_view Description); - [[nodiscard]] bool boolean(const yaml::Node &Node, bool &Value); - [[nodiscard]] bool unsignedValue(const yaml::Node &Node, std::uint64_t &Value, - std::string_view Description); - [[nodiscard]] bool signedValue(const yaml::Node &Node, std::int64_t &Value, - std::string_view Description); - [[nodiscard]] bool alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero); - [[nodiscard]] bool physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg); - [[nodiscard]] bool physicalRegister(std::string_view Text, const TextRange &Range, - PhysicalRegister &Reg); - [[nodiscard]] bool virtualRegister(std::string_view Text, const TextRange &Range, - const MIParserContext &Context, Register &Reg); - [[nodiscard]] Expected - resolveMetadataReference(const MachineFunction &Function, std::uint32_t Slot) const; - [[nodiscard]] bool parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function, - MachineVariableDebugInfo &DebugInfo); - [[nodiscard]] bool parseFunction(const yaml::Node &Root, MIRDocument &Document); - [[nodiscard]] bool parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function, - MIRFunctionDocumentInfo &DocumentInfo); - [[nodiscard]] bool recomputeProperties(const yaml::Node &Root, MachineFunction &Function, - const MIRFunctionDocumentInfo &DocumentInfo); - [[nodiscard]] bool parseRegisters(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context); - [[nodiscard]] bool parseMetadata(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context); - [[nodiscard]] bool parseStackObjects(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context); - [[nodiscard]] bool parseConstants(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context); - [[nodiscard]] bool parseFrameState(const yaml::Node &Root, MachineFunction &Function, - const MIParserContext &Context); - [[nodiscard]] bool reserveJumpTables(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context, - std::vector &Pending); - [[nodiscard]] bool defineJumpTables(MachineFunction &Function, - std::span Pending); - [[nodiscard]] bool parseBody(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context); - [[nodiscard]] TextRange bodyDiagnosticRange(const yaml::Node &Body, - const TextRange &BodyRange) const noexcept; - [[nodiscard]] bool parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function); - [[nodiscard]] bool parseTargetState(const yaml::Node &Root, MachineFunction &Function); - [[nodiscard]] bool convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination); - [[nodiscard]] bool validateDocumentReferences(const MachineFunction &Function); - [[nodiscard]] std::string_view effectiveDataLayout() const noexcept; - [[nodiscard]] LLVMConstantSyntaxOptions pointerLayoutOptions() const noexcept; - - [[nodiscard]] MachineBasicBlock *blockAt(MachineFunction &Function, - std::uint32_t Number) const noexcept; - [[nodiscard]] MachineInstr *instructionAt(MachineFunction &Function, std::uint32_t Block, - std::uint32_t Offset) const noexcept; - [[nodiscard]] bool parseBlockReference(std::string_view Text, const TextRange &Range, - MachineFunction &Function, MachineBasicBlock *&Block); - [[nodiscard]] bool parseFrameReference(const yaml::Node &Node, const MIParserContext &Context, - FrameIndex &Index); - [[nodiscard]] bool parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function, - std::vector &Points); - - const TargetDescription &Target; - MIRDocumentParserOptions Options; - MIRDocumentParseResult Result; - const yaml::Stream *Stream = nullptr; - MIRDocument *ActiveDocument = nullptr; - MachineFunction *ActiveFunction = nullptr; -}; - -bool MIRDocumentParser::fail(const TextRange &Range, std::string Message, std::string Category) { - if (Result.Diagnostics.empty()) { - if (ActiveFunction != nullptr) - Message = "function '" + std::string(ActiveFunction->name()) + "': " + std::move(Message); - Result.Diagnostics.push_back(TextDiagnostic{ - .Severity = DiagnosticSeverity::Error, - .Category = std::move(Category), - .Message = std::move(Message), - .SourceName = std::string(Result.Source.name()), - .Range = Range, - }); - } - return true; -} - -bool MIRDocumentParser::importError(const TextRange &Range, const Error &Failure, - std::string Category) { - return fail(Range, std::string(Failure.message()), std::move(Category)); -} - -const yaml::Node *MIRDocumentParser::mappingValue(const yaml::Node &Mapping, - std::string_view Key) const noexcept { - const auto Value = Mapping.mappingValue(Key); - return Value ? node(*Value) : nullptr; -} - -std::string_view MIRDocumentParser::effectiveDataLayout() const noexcept { - if (ActiveDocument != nullptr) { - if (const EmbeddedIRReferenceIndex *Embedded = ActiveDocument->embeddedIR()) { - if (const auto DataLayout = Embedded->dataLayout()) - return *DataLayout; - } - } - return Options.DataLayout; -} - -LLVMConstantSyntaxOptions MIRDocumentParser::pointerLayoutOptions() const noexcept { - return { - .DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits, - .DataLayout = effectiveDataLayout(), - .NamedTypes = {}, - }; -} - -bool MIRDocumentParser::requireKind(const yaml::Node &Node, yaml::NodeKind Kind, - std::string_view Description) { - if (Node.kind() == Kind) - return false; - return fail(Node.range(), std::string(Description) + " has the wrong YAML node kind"); -} - -bool MIRDocumentParser::validateKeys(const yaml::Node &Mapping, - std::span Keys, - std::string_view Description) { - for (const yaml::MappingEntry &Entry : Mapping.mapping()) { - if (std::ranges::find(Keys, Entry.Key) == Keys.end()) { - return fail(Entry.KeyRange, - "unknown " + std::string(Description) + " key '" + Entry.Key + "'"); - } - } - return false; -} - -bool MIRDocumentParser::scalar(const yaml::Node &Node, std::string_view &Value, - std::string_view Description) { - if (requireKind(Node, yaml::NodeKind::Scalar, Description)) - return true; - Value = Node.scalar(); - return false; -} - -bool MIRDocumentParser::boolean(const yaml::Node &Node, bool &Value) { - std::string_view Text; - if (scalar(Node, Text, "boolean") || !parseBool(Text, Value)) - return fail(Node.range(), "expected 'true' or 'false'"); - return false; -} - -bool MIRDocumentParser::unsignedValue(const yaml::Node &Node, std::uint64_t &Value, - std::string_view Description) { - std::string_view Text; - if (scalar(Node, Text, Description) || !parseUnsigned(Text, Value)) - return fail(Node.range(), "expected " + std::string(Description)); - return false; -} - -bool MIRDocumentParser::signedValue(const yaml::Node &Node, std::int64_t &Value, - std::string_view Description) { - std::string_view Text; - if (scalar(Node, Text, Description) || !parseSigned(Text, Value)) - return fail(Node.range(), "expected " + std::string(Description)); - return false; -} - -bool MIRDocumentParser::alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero) { - std::uint64_t Bytes = 0; - if (unsignedValue(Node, Bytes, "an unsigned alignment")) - return true; - if (Bytes == 0 && AllowZero) { - Value = Alignment{}; - return false; - } - auto Parsed = Alignment::create(Bytes); - if (!Parsed) - return importError(Node.range(), Parsed.error()); - Value = *Parsed; - return false; -} - -bool MIRDocumentParser::physicalRegister(std::string_view Text, const TextRange &Range, - PhysicalRegister &Reg) { - Text = trim(Text); - if (!Text.starts_with('$') || Text.size() == 1) - return fail(Range, "physical register must use the '$name' MIR spelling"); - const PhysicalRegisterDesc *Description = Target.registers().physicalRegister(Text.substr(1)); - if (Description == nullptr) - return fail(Range, "unknown physical register '" + std::string(Text) + "'"); - Reg = Description->Reg; - return false; -} - -bool MIRDocumentParser::physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg) { - std::string_view Text; - return scalar(Node, Text, "physical register") || physicalRegister(Text, Node.range(), Reg); -} - -bool MIRDocumentParser::virtualRegister(std::string_view Text, const TextRange &Range, - const MIParserContext &Context, Register &Reg) { - const auto ID = parsePrefixedID(Text, "%"); - if (!ID) - return fail(Range, "virtual register must use the '%N' MIR spelling"); - const auto Found = - std::ranges::find_if(Context.VirtualRegisters, - [ID](const MIVirtualRegisterSlot &Slot) { return Slot.TextID == *ID; }); - if (Found == Context.VirtualRegisters.end()) - return fail(Range, "reference to undefined virtual register '%" + std::to_string(*ID) + "'"); - Reg = Found->Reg; - return false; -} - -Expected -MIRDocumentParser::resolveMetadataReference(const MachineFunction &Function, - std::uint32_t Slot) const { - if (ActiveDocument != nullptr && ActiveDocument->EmbeddedIR && - ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) { - return MachineMetadataReference::embeddedIR(Slot); - } - if (Function.metadata().node(Slot) != nullptr) - return MachineMetadataReference::machineFunction(Slot); - return makeUnexpected(ErrorCode::ParseError, - "metadata reference uses undefined slot '!" + std::to_string(Slot) + "'"); -} - -bool MIRDocumentParser::parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function, - MachineVariableDebugInfo &DebugInfo) { - const auto Resolve = [this, &Function](std::uint32_t Slot) { - return resolveMetadataReference(Function, Slot); - }; - if (const yaml::Node *VariableNode = mappingValue(Entry, "debug-info-variable")) { - std::string_view Text; - if (scalar(*VariableNode, Text, "debug variable")) - return true; - if (!Text.empty()) { - MetadataTextParser Parser(std::string(Text), Resolve); - auto Reference = Parser.parseReferenceOnly(); - if (!Reference) - return importError(VariableNode->range(), Reference.error(), "mir-debug-metadata"); - if (Reference->scope() != MachineMetadataScope::EmbeddedIR) { - return fail(VariableNode->range(), - "debug variable must reference embedded LLVM IR metadata", - "mir-debug-metadata"); - } - DebugInfo.Variable = *Reference; - } - } - if (const yaml::Node *ExpressionNode = mappingValue(Entry, "debug-info-expression")) { - std::string_view Text; - if (scalar(*ExpressionNode, Text, "debug expression")) - return true; - if (!Text.empty()) { - MetadataTextParser Parser(std::string(Text), Resolve); - auto Value = Parser.parseValue(); - if (!Value) - return importError(ExpressionNode->range(), Value.error(), "mir-debug-metadata"); - if (Value->reference() != nullptr && - Value->reference()->scope() != MachineMetadataScope::EmbeddedIR) { - return fail(ExpressionNode->range(), - "debug expression reference must use embedded LLVM IR metadata", - "mir-debug-metadata"); - } - DebugInfo.Expression = std::move(*Value); - } - } - if (const yaml::Node *LocationNode = mappingValue(Entry, "debug-info-location")) { - std::string_view Text; - if (scalar(*LocationNode, Text, "debug location")) - return true; - if (!Text.empty()) { - MetadataTextParser Parser(std::string(Text), Resolve); - auto Location = Parser.parseLocation(); - if (!Location) - return importError(LocationNode->range(), Location.error(), "mir-debug-metadata"); - if (Location->reference() != nullptr && - Location->reference()->scope() != MachineMetadataScope::EmbeddedIR) { - return fail(LocationNode->range(), - "variable debug location must reference embedded LLVM IR metadata", - "mir-debug-metadata"); - } - DebugInfo.Location = std::move(*Location); - } - } - if (!DebugInfo.Variable && !DebugInfo.Expression && !DebugInfo.Location) - return false; - auto Verification = DebugInfo.verifyStructure(); - if (!Verification) - return importError(Entry.range(), Verification.error(), "mir-debug-metadata"); - return false; -} - -MIRDocumentParseResult MIRDocumentParser::run() { - yaml::ParseResult YAMLResult = yaml::parse(std::string(Result.Source.name()), - std::string(Result.Source.text()), Options.YAML); - if (!YAMLResult.succeeded()) { - const auto Diagnostics = YAMLResult.diagnostics(); - Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end()); - return std::move(Result); - } - Stream = YAMLResult.stream(); - if (Stream == nullptr || Stream->documents().empty()) { - static_cast(fail(Result.Source.range(0, 0), "MIR stream contains no YAML documents")); - return std::move(Result); - } - - MIRDocument Staging; - ActiveDocument = &Staging; - std::size_t FirstFunction = 0; - const yaml::Node *FirstRoot = node(Stream->documents().front().Root); - if (FirstRoot != nullptr && FirstRoot->kind() == yaml::NodeKind::Scalar) { - EmbeddedIRIndexResult Embedded = indexEmbeddedIR( - std::string(Result.Source.name()) + ":embedded-ir", std::string(FirstRoot->scalar())); - if (!Embedded.succeeded()) { - const auto Diagnostics = Embedded.diagnostics(); - Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end()); - return std::move(Result); - } - Staging.EmbeddedIR = std::move(Embedded).takeIndex(); - FirstFunction = 1; - } - - if (const EmbeddedIRReferenceIndex *Embedded = Staging.embeddedIR()) { - if (const auto DataLayout = Embedded->dataLayout(); - DataLayout && !Options.DataLayout.empty() && *DataLayout != Options.DataLayout) { - static_cast(fail(Stream->documents().front().Range, - "explicit DataLayout does not match embedded LLVM IR data layout")); - return std::move(Result); - } - } - if (!effectiveDataLayout().empty()) { - auto PointerSize = llvmPointerSizeInBits(0, pointerLayoutOptions()); - if (!PointerSize) { - static_cast( - importError(Stream->documents().front().Range, PointerSize.error(), "mir-data-layout")); - return std::move(Result); - } - } - - if (FirstFunction == Stream->documents().size()) { - static_cast(fail(Stream->documents().front().Range, - "MIR stream must contain at least one machine function document")); - return std::move(Result); - } - - for (std::size_t Index = FirstFunction; Index < Stream->documents().size(); ++Index) { - const yaml::Node *Root = node(Stream->documents()[Index].Root); - if (Root == nullptr || requireKind(*Root, yaml::NodeKind::Mapping, "machine function") || - parseFunction(*Root, Staging)) { - return std::move(Result); - } - } - - auto UnitResult = Staging.Unit.verify(); - if (!UnitResult) { - static_cast( - fail(Stream->documents().back().Range, "parsed compilation unit failed verification: " + - std::string(UnitResult.error().message()))); - return std::move(Result); - } - ActiveDocument = nullptr; - Result.Document = std::move(Staging); - return std::move(Result); -} - -bool MIRDocumentParser::parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function, - MIRFunctionDocumentInfo &DocumentInfo) { - if (const yaml::Node *AlignNode = mappingValue(Root, "alignment")) { - Alignment Align; - if (alignment(*AlignNode, Align, true)) - return true; - if (Align.bytes() != 1 || AlignNode->scalar() != "0") - Function.attributes().setAlignment(Align); - } - - constexpr std::array PropertyBindings{ - std::pair{"legalized", MachineFunctionProperty::Legalized}, - std::pair{"regBankSelected", MachineFunctionProperty::RegBankSelected}, - std::pair{"selected", MachineFunctionProperty::Selected}, - std::pair{"failedISel", MachineFunctionProperty::FailedISel}, - std::pair{"tracksRegLiveness", MachineFunctionProperty::TracksLiveness}, - std::pair{"noPhis", MachineFunctionProperty::NoPHIs}, - std::pair{"isSSA", MachineFunctionProperty::IsSSA}, - std::pair{"noVRegs", MachineFunctionProperty::NoVRegs}, - std::pair{"failsVerification", MachineFunctionProperty::FailsVerification}, - }; - for (const auto &[Key, Property] : PropertyBindings) { - const yaml::Node *ValueNode = mappingValue(Root, Key); - if (ValueNode == nullptr) - continue; - bool Value = false; - if (boolean(*ValueNode, Value)) - return true; - if (Value) - Function.properties().set(Property); - else - Function.properties().clear(Property); - if (Key == std::string_view("noPhis")) - DocumentInfo.NoPHIs = Value; - else if (Key == std::string_view("isSSA")) - DocumentInfo.IsSSA = Value; - else if (Key == std::string_view("noVRegs")) - DocumentInfo.NoVRegs = Value; - } - - constexpr std::array AttributeBindings{ - std::pair{"exposesReturnsTwice", MachineFunctionAttribute::ExposesReturnsTwice}, - std::pair{"hasWinCFI", MachineFunctionAttribute::HasWinCFI}, - std::pair{"hasFakeUses", MachineFunctionAttribute::HasFakeUses}, - std::pair{"callsEHReturn", MachineFunctionAttribute::CallsEHReturn}, - std::pair{"callsUnwindInit", MachineFunctionAttribute::CallsUnwindInit}, - std::pair{"hasEHContTarget", MachineFunctionAttribute::HasEHContTarget}, - std::pair{"hasEHScopes", MachineFunctionAttribute::HasEHScopes}, - std::pair{"hasEHFunclets", MachineFunctionAttribute::HasEHFunclets}, - std::pair{"isOutlined", MachineFunctionAttribute::IsOutlined}, - std::pair{"debugInstrRef", MachineFunctionAttribute::DebugInstrRef}, - std::pair{"tracksDebugUserValues", MachineFunctionAttribute::TracksDebugUserValues}, - }; - for (const auto &[Key, Attribute] : AttributeBindings) { - const yaml::Node *ValueNode = mappingValue(Root, Key); - if (ValueNode == nullptr) - continue; - bool Value = false; - if (boolean(*ValueNode, Value)) - return true; - if (Value) - Function.attributes().set(Attribute); - else - Function.attributes().clear(Attribute); - if (Key == std::string_view("hasFakeUses")) - DocumentInfo.HasFakeUses = Value; - } - - return false; -} - -bool MIRDocumentParser::recomputeProperties(const yaml::Node &Root, MachineFunction &Function, - const MIRFunctionDocumentInfo &DocumentInfo) { - bool HasPHI = false; - bool HasFakeUse = false; - bool IsSSA = true; - bool HasTiedOperands = false; - bool AllTiedOperandsRewritten = true; - std::unordered_set Definitions; - for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; - Block = Block->next()) { - for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; - Instruction = Instruction->next()) { - const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); - if (Descriptor == nullptr) - return fail(Root.range(), "instruction has no target descriptor"); - HasPHI |= Descriptor->Flags.has(InstructionFlag::Phi); - HasFakeUse |= Descriptor->Flags.has(InstructionFlag::FakeUse); - const auto Operands = Instruction->operands(); - for (std::size_t Index = 0; Index < Operands.size(); ++Index) { - const MachineOperand &Operand = Operands[Index]; - const auto Reg = Operand.registerValue(); - if (Reg && Reg->isVirtual() && Operand.isDef() && !Definitions.insert(*Reg).second) { - IsSSA = false; - } - if (!Operand.tiedOperandIndex() || !Operand.isUse()) - continue; - HasTiedOperands = true; - const MachineOperand *Definition = Instruction->operandAt(*Operand.tiedOperandIndex()); - if (Definition == nullptr || Definition->registerValue() != Reg) - AllTiedOperandsRewritten = false; - } - } - } - const bool NoPHIs = !HasPHI; - const bool NoVRegs = - std::ranges::none_of(Function.registerInfo().virtualRegisters(), - [](const VirtualRegisterInfo &Info) { return !Info.IsTombstone; }); - - const auto ApplyComputed = [this, &Root, &Function](std::optional Explicit, bool Computed, - MachineFunctionProperty Property, - std::string_view Name) -> bool { - if (Explicit && *Explicit && !Computed) { - return fail(Root.range(), "explicit property '" + std::string(Name) + - "' conflicts with the parsed machine function"); - } - if (Explicit.value_or(Computed)) - Function.properties().set(Property); - else - Function.properties().clear(Property); - return false; - }; - if (ApplyComputed(DocumentInfo.NoPHIs, NoPHIs, MachineFunctionProperty::NoPHIs, "noPhis") || - ApplyComputed(DocumentInfo.IsSSA, IsSSA, MachineFunctionProperty::IsSSA, "isSSA") || - ApplyComputed(DocumentInfo.NoVRegs, NoVRegs, MachineFunctionProperty::NoVRegs, "noVRegs")) { - return true; - } - - if (DocumentInfo.HasFakeUses && !*DocumentInfo.HasFakeUses && HasFakeUse) { - return fail(Root.range(), - "explicit property 'hasFakeUses: false' conflicts with a fake-use instruction"); - } - if (DocumentInfo.HasFakeUses.value_or(HasFakeUse)) - Function.attributes().set(MachineFunctionAttribute::HasFakeUses); - else - Function.attributes().clear(MachineFunctionAttribute::HasFakeUses); - - if (HasTiedOperands && AllTiedOperandsRewritten) - Function.properties().set(MachineFunctionProperty::TiedOpsRewritten); - else - Function.properties().clear(MachineFunctionProperty::TiedOpsRewritten); - return false; -} - -bool MIRDocumentParser::parseRegisters(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context) { - const yaml::Node *Registers = mappingValue(Root, "registers"); - if (Registers != nullptr) { - if (requireKind(*Registers, yaml::NodeKind::Sequence, "registers")) - return true; - constexpr std::array RegisterKeys{ - std::string_view{"id"}, - std::string_view{"class"}, - std::string_view{"preferred-register"}, - std::string_view{"flags"}, - }; - std::unordered_set IDs; - for (const yaml::NodeID ElementID : Registers->sequence()) { - const yaml::Node *Element = node(ElementID); - if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "register entry") || - validateKeys(*Element, RegisterKeys, "register")) { - return true; - } - const yaml::Node *IDNode = mappingValue(*Element, "id"); - const yaml::Node *ClassNode = mappingValue(*Element, "class"); - if (IDNode == nullptr || ClassNode == nullptr) - return fail(Element->range(), "register entry requires 'id' and 'class'"); - std::uint64_t WideID = 0; - std::string_view ClassName; - if (unsignedValue(*IDNode, WideID, "a 32-bit virtual-register ID") || - WideID > std::numeric_limits::max() || - scalar(*ClassNode, ClassName, "register class or bank")) { - return true; - } - const auto TextID = static_cast(WideID); - if (!IDs.insert(TextID).second) - return fail(IDNode->range(), "duplicate virtual register ID"); - auto Created = Function.registerInfo().createVirtualRegister(); - if (!Created) - return importError(IDNode->range(), Created.error()); - Context.VirtualRegisters.push_back({TextID, *Created}); - - if (ClassName != "_") { - if (const RegisterClassDesc *Class = Target.registers().registerClass(ClassName)) { - auto SetResult = Function.registerInfo().setRegisterClass(*Created, Class->ID); - if (!SetResult) - return importError(ClassNode->range(), SetResult.error()); - } else if (const RegisterBankDesc *Bank = Target.registers().registerBank(ClassName)) { - auto SetResult = Function.registerInfo().setRegisterBank(*Created, Bank->ID); - if (!SetResult) - return importError(ClassNode->range(), SetResult.error()); - } else { - return fail(ClassNode->range(), - "unknown register class or bank '" + std::string(ClassName) + "'"); - } - } - - if (const yaml::Node *Preferred = mappingValue(*Element, "preferred-register")) { - PhysicalRegister Reg; - if (physicalRegister(*Preferred, Reg)) - return true; - auto AddResult = Function.registerInfo().addAllocationHint(*Created, Reg); - if (!AddResult) - return importError(Preferred->range(), AddResult.error()); - } - if (const yaml::Node *Flags = mappingValue(*Element, "flags")) { - if (requireKind(*Flags, yaml::NodeKind::Sequence, "virtual-register flags")) - return true; - std::uint8_t Mask = 0; - for (const yaml::NodeID FlagID : Flags->sequence()) { - const yaml::Node *FlagNode = node(FlagID); - std::string_view Name; - if (FlagNode == nullptr || scalar(*FlagNode, Name, "virtual-register flag")) - return true; - const auto Flag = Target.mirText().virtualRegisterFlag(Name); - if (!Flag) - return fail(FlagNode->range(), - "unknown virtual-register flag '" + std::string(Name) + "'"); - if ((Mask & *Flag) != 0) - return fail(FlagNode->range(), "duplicate virtual-register flag"); - Mask = static_cast(Mask | *Flag); - } - auto SetResult = Function.registerInfo().setTargetRegisterFlags(*Created, Mask); - if (!SetResult) - return importError(Flags->range(), SetResult.error()); - } - } - } - - const yaml::Node *LiveIns = mappingValue(Root, "liveins"); - if (LiveIns != nullptr) { - if (requireKind(*LiveIns, yaml::NodeKind::Sequence, "liveins")) - return true; - constexpr std::array LiveInKeys{ - std::string_view{"reg"}, - std::string_view{"virtual-reg"}, - }; - for (const yaml::NodeID ElementID : LiveIns->sequence()) { - const yaml::Node *Element = node(ElementID); - if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "live-in entry") || - validateKeys(*Element, LiveInKeys, "live-in")) { - return true; - } - const yaml::Node *RegNode = mappingValue(*Element, "reg"); - if (RegNode == nullptr) - return fail(Element->range(), "live-in entry requires 'reg'"); - PhysicalRegister Reg; - if (physicalRegister(*RegNode, Reg)) - return true; - std::optional Initial; - if (const yaml::Node *VirtualNode = mappingValue(*Element, "virtual-reg")) { - std::string_view Text; - Register Virtual; - if (scalar(*VirtualNode, Text, "virtual register") || - virtualRegister(Text, VirtualNode->range(), Context, Virtual)) { - return true; - } - Initial = Virtual; - } - auto AddResult = Function.registerInfo().addLiveIn(Reg, Initial); - if (!AddResult) - return importError(Element->range(), AddResult.error()); - } - } - - if (const yaml::Node *CalleeSaved = mappingValue(Root, "calleeSavedRegisters")) { - if (requireKind(*CalleeSaved, yaml::NodeKind::Sequence, "callee-saved registers")) - return true; - std::vector RegistersValue; - for (const yaml::NodeID RegID : CalleeSaved->sequence()) { - const yaml::Node *RegNode = node(RegID); - PhysicalRegister Reg; - if (RegNode == nullptr || physicalRegister(*RegNode, Reg)) - return true; - RegistersValue.push_back(Reg); - } - auto SetResult = Function.records().setCalleeSavedRegisters(std::move(RegistersValue)); - if (!SetResult) - return importError(CalleeSaved->range(), SetResult.error()); - } - return false; -} - -bool MIRDocumentParser::parseMetadata(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context) { - if (ActiveDocument->EmbeddedIR) { - for (const EmbeddedIRMetadataDefinition &Reference : - ActiveDocument->EmbeddedIR->metadataDefinitions()) { - std::uint32_t ID = 0; - if (!parseUnsigned32(Reference.Name, ID)) - continue; - if (std::ranges::any_of(Context.Metadata, - [ID](const MIMetadataSlot &Slot) { return Slot.TextID == ID; })) { - return fail(Root.range(), "embedded LLVM IR metadata slots are not unique"); - } - Context.Metadata.push_back( - {.TextID = ID, .Reference = MachineMetadataReference::embeddedIR(ID)}); - } - } - - const yaml::Node *Metadata = mappingValue(Root, "machineMetadataNodes"); - if (Metadata == nullptr) - return false; - if (requireKind(*Metadata, yaml::NodeKind::Sequence, "machine metadata nodes")) - return true; - - struct Definition final { - const yaml::Node *Node = nullptr; - std::uint32_t Slot = 0; - bool IsDistinct = false; - std::string_view Elements; - }; - std::vector Definitions; - std::unordered_set Slots; - - for (const yaml::NodeID DefinitionID : Metadata->sequence()) { - const yaml::Node *DefinitionNode = node(DefinitionID); - std::string_view Text; - if (DefinitionNode == nullptr || scalar(*DefinitionNode, Text, "machine metadata definition")) { - return true; - } - Text = trim(Text); - if (!Text.starts_with('!')) - return fail(DefinitionNode->range(), "machine metadata definition must start with '!N'"); - const std::size_t Equals = Text.find('='); - if (Equals == std::string_view::npos) - return fail(DefinitionNode->range(), "machine metadata definition requires '='"); - std::uint32_t Slot = 0; - if (!parseUnsigned32(trim(Text.substr(1, Equals - 1)), Slot)) - return fail(DefinitionNode->range(), "machine metadata definition has an invalid slot"); - if (!Slots.insert(Slot).second) - return fail(DefinitionNode->range(), "duplicate machine metadata slot"); - std::string_view Right = trim(Text.substr(Equals + 1)); - bool IsDistinct = false; - if (Right.starts_with("distinct")) { - Right = trim(Right.substr(std::string_view("distinct").size())); - IsDistinct = true; - } - if (!Right.starts_with("!{") || !Right.ends_with('}')) - return fail(DefinitionNode->range(), "machine metadata node must be a metadata tuple"); - Definitions.push_back({.Node = DefinitionNode, - .Slot = Slot, - .IsDistinct = IsDistinct, - .Elements = Right.substr(2, Right.size() - 3)}); - Context.Metadata.push_back( - {.TextID = Slot, .Reference = MachineMetadataReference::machineFunction(Slot)}); - } - - auto ResolveReference = [this, - &Slots](std::uint32_t Slot, - const TextRange &Range) -> Expected { - MachineMetadataReference Reference; - if (ActiveDocument->EmbeddedIR != std::nullopt && - ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) { - Reference = MachineMetadataReference::embeddedIR(Slot); - } else if (Slots.contains(Slot)) { - Reference = MachineMetadataReference::machineFunction(Slot); - } else { - return makeUnexpected(ErrorCode::ParseError, "metadata tuple references undefined slot '!" + - std::to_string(Slot) + "' at line " + - std::to_string(Range.Begin.Line)); - } - return MachineMetadataElement::reference(Reference); - }; - - for (const Definition &Definition : Definitions) { - MachineMetadataNode NewNode{ - .Slot = Definition.Slot, - .IsDistinct = Definition.IsDistinct, - .Elements = {}, - }; - std::string_view Remaining = trim(Definition.Elements); - while (!Remaining.empty()) { - std::size_t End = 0; - bool InString = false; - bool Escaped = false; - for (; End < Remaining.size(); ++End) { - const char Character = Remaining[End]; - if (InString && Escaped) { - Escaped = false; - continue; - } - if (InString && Character == '\\') { - Escaped = true; - continue; - } - if (Character == '"') { - InString = !InString; - continue; - } - if (!InString && Character == ',') - break; - } - if (InString) - return fail(Definition.Node->range(), "unterminated machine metadata string"); - std::string_view Element = trim(Remaining.substr(0, End)); - if (Element.empty()) - return fail(Definition.Node->range(), "empty machine metadata tuple element"); - - if (Element == "null") { - NewNode.Elements.push_back(MachineMetadataElement::null()); - } else if (Element.starts_with('!') && Element.size() > 1 && Element[1] >= '0' && - Element[1] <= '9') { - std::uint32_t ReferenceSlot = 0; - if (!parseUnsigned32(Element.substr(1), ReferenceSlot)) - return fail(Definition.Node->range(), "invalid metadata tuple reference"); - auto Reference = ResolveReference(ReferenceSlot, Definition.Node->range()); - if (!Reference) - return importError(Definition.Node->range(), Reference.error()); - NewNode.Elements.push_back(std::move(*Reference)); - } else if (Element.starts_with("!\"") && Element.ends_with('"')) { - std::string Value; - std::string_view Encoded = Element.substr(2, Element.size() - 3); - for (std::size_t Index = 0; Index < Encoded.size(); ++Index) { - if (Encoded[Index] == '\\' && Index + 1 < Encoded.size()) { - const char EscapedCharacter = Encoded[++Index]; - if (EscapedCharacter != '\\' && EscapedCharacter != '"') - return fail(Definition.Node->range(), "unsupported machine metadata string escape"); - Value.push_back(EscapedCharacter); - } else { - Value.push_back(Encoded[Index]); - } - } - NewNode.Elements.push_back(MachineMetadataElement::string(std::move(Value))); - } else { - return fail(Definition.Node->range(), "unsupported machine metadata tuple element"); - } - - if (End == Remaining.size()) - Remaining = {}; - else - Remaining = trim(Remaining.substr(End + 1)); - } - auto AddResult = Function.metadata().addNode(std::move(NewNode)); - if (!AddResult) - return importError(Definition.Node->range(), AddResult.error()); - } - return false; -} - -bool MIRDocumentParser::parseStackObjects(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context) { - auto ParseList = [this, &Function, &Context](const yaml::Node &List, bool Fixed) -> bool { - if (requireKind(List, yaml::NodeKind::Sequence, - Fixed ? "fixed stack objects" : "stack objects")) { - return true; - } - constexpr std::array FixedKeys{ - std::string_view{"id"}, - std::string_view{"type"}, - std::string_view{"offset"}, - std::string_view{"size"}, - std::string_view{"alignment"}, - std::string_view{"stack-id"}, - std::string_view{"isImmutable"}, - std::string_view{"isAliased"}, - std::string_view{"callee-saved-register"}, - std::string_view{"callee-saved-restored"}, - std::string_view{"debug-info-variable"}, - std::string_view{"debug-info-expression"}, - std::string_view{"debug-info-location"}, - }; - constexpr std::array StackKeys{ - std::string_view{"id"}, - std::string_view{"name"}, - std::string_view{"type"}, - std::string_view{"offset"}, - std::string_view{"size"}, - std::string_view{"alignment"}, - std::string_view{"stack-id"}, - std::string_view{"callee-saved-register"}, - std::string_view{"callee-saved-restored"}, - std::string_view{"local-offset"}, - std::string_view{"debug-info-variable"}, - std::string_view{"debug-info-expression"}, - std::string_view{"debug-info-location"}, - }; - std::unordered_set IDs; - for (const yaml::NodeID ElementID : List.sequence()) { - const yaml::Node *Element = node(ElementID); - if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "stack object") || - (Fixed ? validateKeys(*Element, FixedKeys, "fixed stack object") - : validateKeys(*Element, StackKeys, "stack object"))) { - return true; - } - const yaml::Node *IDNode = mappingValue(*Element, "id"); - if (IDNode == nullptr) - return fail(Element->range(), "stack object requires 'id'"); - std::uint64_t WideID = 0; - if (unsignedValue(*IDNode, WideID, "a 32-bit stack object ID") || - WideID > std::numeric_limits::max()) { - return true; - } - const auto TextID = static_cast(WideID); - if (!IDs.insert(TextID).second) - return fail(IDNode->range(), "duplicate stack object ID"); - - StackObjectDesc Description; - Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default; - Description.Offset = 0; - if (const yaml::Node *NameNode = mappingValue(*Element, "name")) { - std::string_view Name; - if (scalar(*NameNode, Name, "stack object name") || Name.empty()) - return fail(NameNode->range(), "stack object name cannot be empty"); - Description.AllocaName = std::string(Name); - } - if (const yaml::Node *TypeNode = mappingValue(*Element, "type")) { - std::string_view Type; - if (scalar(*TypeNode, Type, "stack object type")) - return true; - if (Type == "default") - Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default; - else if (Type == "spill-slot") - Description.Kind = Fixed ? StackObjectKind::FixedSpillSlot : StackObjectKind::SpillSlot; - else if (!Fixed && Type == "variable-sized") - Description.Kind = StackObjectKind::VariableSized; - else - return fail(TypeNode->range(), "unknown stack object type '" + std::string(Type) + "'"); - } - if (const yaml::Node *OffsetNode = mappingValue(*Element, "offset")) { - std::int64_t Offset = 0; - if (signedValue(*OffsetNode, Offset, "a signed stack object offset")) - return true; - Description.Offset = Offset; - } else if (Fixed) { - Description.Offset = 0; - } - if (Description.Kind != StackObjectKind::VariableSized) { - const yaml::Node *SizeNode = mappingValue(*Element, "size"); - if (SizeNode == nullptr) { - if (!Fixed) - return fail(Element->range(), "fixed-size stack object requires 'size'"); - Description.Size = 0; - } else { - std::uint64_t Size = 0; - if (unsignedValue(*SizeNode, Size, "an unsigned stack object size")) - return true; - Description.Size = Size; - } - } - if (const yaml::Node *AlignmentNode = mappingValue(*Element, "alignment")) { - if (alignment(*AlignmentNode, Description.Align, true)) - return true; - } - if (const yaml::Node *StackIDNode = mappingValue(*Element, "stack-id")) { - std::string_view Name; - if (scalar(*StackIDNode, Name, "stack ID")) - return true; - const auto Stack = parseStackIDName(Name); - if (!Stack) - return fail(StackIDNode->range(), "unknown stack ID '" + std::string(Name) + "'"); - Description.Stack = *Stack; - } - if (const yaml::Node *ImmutableNode = mappingValue(*Element, "isImmutable")) { - if (boolean(*ImmutableNode, Description.IsImmutable)) - return true; - } - if (const yaml::Node *AliasedNode = mappingValue(*Element, "isAliased")) { - if (boolean(*AliasedNode, Description.IsAliased)) - return true; - } - if (const yaml::Node *SavedNode = mappingValue(*Element, "callee-saved-register")) { - PhysicalRegister Reg; - if (physicalRegister(*SavedNode, Reg)) - return true; - Description.CalleeSavedRegister = Reg; - } - if (const yaml::Node *RestoredNode = mappingValue(*Element, "callee-saved-restored")) { - if (boolean(*RestoredNode, Description.CalleeSavedRestored)) - return true; - } - if (const yaml::Node *LocalNode = mappingValue(*Element, "local-offset")) { - std::int64_t Offset = 0; - if (signedValue(*LocalNode, Offset, "a signed local frame offset")) - return true; - Description.LocalOffset = Offset; - } - MachineVariableDebugInfo DebugInfo; - if (parseVariableDebugInfo(*Element, Function, DebugInfo)) - return true; - if (DebugInfo.Variable || DebugInfo.Expression || DebugInfo.Location) - Description.DebugInfo = std::move(DebugInfo); - - auto Created = Function.frameInfo().createObject(std::move(Description)); - if (!Created) - return importError(Element->range(), Created.error()); - MIFrameObjectSlot Slot{ - .TextID = TextID, - .Index = *Created, - .Name = std::nullopt, - }; - if (const StackObjectDesc *CreatedDescription = - Function.frameInfo().object(*Created)->description(); - CreatedDescription != nullptr && CreatedDescription->AllocaName) { - Slot.Name = *CreatedDescription->AllocaName; - } - (Fixed ? Context.FixedStackObjects : Context.StackObjects).push_back(std::move(Slot)); - } - return false; - }; - - if (const yaml::Node *Fixed = mappingValue(Root, "fixedStack")) { - if (ParseList(*Fixed, true)) - return true; - } - if (const yaml::Node *Stack = mappingValue(Root, "stack")) { - if (ParseList(*Stack, false)) - return true; - } - return false; -} - -bool MIRDocumentParser::parseConstants(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context) { - const yaml::Node *Constants = mappingValue(Root, "constants"); - if (Constants == nullptr) - return false; - if (requireKind(*Constants, yaml::NodeKind::Sequence, "constant pool")) - return true; - constexpr std::array Keys{ - std::string_view{"id"}, - std::string_view{"value"}, - std::string_view{"alignment"}, - std::string_view{"isTargetSpecific"}, - }; - std::vector NamedTypes; - const std::string_view DataLayout = effectiveDataLayout(); - if (ActiveDocument->EmbeddedIR) { - const auto Definitions = ActiveDocument->EmbeddedIR->typeDefinitions(); - NamedTypes.reserve(Definitions.size()); - for (const EmbeddedIRTypeDefinition &Definition : Definitions) { - NamedTypes.push_back({.Name = Definition.Name, - .Definition = Definition.Definition, - .NumericName = Definition.NumericName}); - } - } - std::unordered_set IDs; - for (const yaml::NodeID ConstantID : Constants->sequence()) { - const yaml::Node *Constant = node(ConstantID); - if (Constant == nullptr || - requireKind(*Constant, yaml::NodeKind::Mapping, "constant pool entry") || - validateKeys(*Constant, Keys, "constant pool entry")) { - return true; - } - const yaml::Node *IDNode = mappingValue(*Constant, "id"); - const yaml::Node *ValueNode = mappingValue(*Constant, "value"); - if (IDNode == nullptr || ValueNode == nullptr) - return fail(Constant->range(), "constant pool entry requires 'id' and 'value'"); - std::uint64_t WideID = 0; - if (unsignedValue(*IDNode, WideID, "a 32-bit constant pool ID") || - WideID > std::numeric_limits::max()) { - return true; - } - const auto TextID = static_cast(WideID); - if (!IDs.insert(TextID).second) - return fail(IDNode->range(), "duplicate constant pool ID"); - - bool IsTargetSpecific = false; - if (const yaml::Node *TargetNode = mappingValue(*Constant, "isTargetSpecific")) { - if (boolean(*TargetNode, IsTargetSpecific)) - return true; - } - if (IsTargetSpecific) { - std::string_view ValueText; - if (scalar(*ValueNode, ValueText, "target constant")) - return true; - auto TargetConstantValue = Target.mirText().parseTargetConstant(ValueText); - if (!TargetConstantValue) - return importError(ValueNode->range(), TargetConstantValue.error(), "mir-target-constant"); - auto Emission = TargetConstantValue->emit(); - if (!Emission) - return importError(ValueNode->range(), Emission.error(), "mir-target-constant"); - auto RequiredAlignment = TargetConstantValue->requiredAlignment(); - if (!RequiredAlignment) - return importError(ValueNode->range(), RequiredAlignment.error(), "mir-target-constant"); - - MachineConstantPoolEntryDesc Description; - Description.Size = Emission->Bytes.size(); - Description.Align = *RequiredAlignment; - Description.Value = std::move(*TargetConstantValue); - if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) { - if (alignment(*AlignmentNode, Description.Align, true)) - return true; - } - auto Added = Function.constantPool().addEntry(std::move(Description)); - if (!Added) - return importError(Constant->range(), Added.error()); - Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added}); - continue; - } - - std::string_view ValueText; - if (scalar(*ValueNode, ValueText, "LLVM constant")) - return true; - const LLVMConstantSyntaxOptions ConstantOptions{ - .DefaultPointerSizeInBits = Context.DefaultPointerSizeInBits, - .DataLayout = DataLayout, - .NamedTypes = NamedTypes, - }; - auto Data = parseLLVMConstant(trim(ValueText), ConstantOptions); - if (!Data) - return importError(ValueNode->range(), Data.error(), "mir-llvm-constant"); - auto Size = llvmConstantStorageSize(Data->Type, ConstantOptions); - if (!Size) - return importError(ValueNode->range(), Size.error(), "mir-llvm-constant"); - - MachineConstantPoolEntryDesc Description; - Description.Size = *Size; - Description.Value = std::move(*Data); - if (const yaml::Node *AlignmentNode = mappingValue(*Constant, "alignment")) { - if (alignment(*AlignmentNode, Description.Align, true)) - return true; - } else { - auto Preferred = llvmConstantPreferredAlignment( - std::get(Description.Value).Type, ConstantOptions); - if (!Preferred) - return importError(ValueNode->range(), Preferred.error(), "mir-llvm-constant"); - Description.Align = *Preferred; - } - auto Added = Function.constantPool().addEntry(std::move(Description)); - if (!Added) - return importError(Constant->range(), Added.error()); - Context.ConstantPoolItems.push_back({.TextID = TextID, .Index = *Added}); - } - return false; -} - -bool MIRDocumentParser::parseFrameReference(const yaml::Node &Node, const MIParserContext &Context, - FrameIndex &Index) { - std::string_view Text; - if (scalar(Node, Text, "frame index")) - return true; - bool Fixed = false; - std::optional ID = parsePrefixedID(Text, "%stack."); - if (!ID) { - ID = parsePrefixedID(Text, "%fixed-stack."); - Fixed = true; - } - if (!ID) - return fail(Node.range(), "frame index must use '%stack.N' or '%fixed-stack.N'"); - const auto &Slots = Fixed ? Context.FixedStackObjects : Context.StackObjects; - const auto Found = std::ranges::find_if( - Slots, [ID](const MIFrameObjectSlot &Slot) { return Slot.TextID == *ID; }); - if (Found == Slots.end()) - return fail(Node.range(), "reference to undefined frame object"); - Index = Found->Index; - return false; -} - -MachineBasicBlock *MIRDocumentParser::blockAt(MachineFunction &Function, - std::uint32_t Number) const noexcept { - for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) { - if (Block->denseNumber() == Number) - return Block; - } - return nullptr; -} - -MachineInstr *MIRDocumentParser::instructionAt(MachineFunction &Function, std::uint32_t BlockNumber, - std::uint32_t Offset) const noexcept { - MachineBasicBlock *Block = blockAt(Function, BlockNumber); - if (Block == nullptr) - return nullptr; - MachineInstr *Instruction = Block->firstInstruction(); - for (std::uint32_t Index = 0; Instruction != nullptr && Index < Offset; ++Index) - Instruction = Instruction->next(); - return Instruction; -} - -bool MIRDocumentParser::parseBlockReference(std::string_view Text, const TextRange &Range, - MachineFunction &Function, MachineBasicBlock *&Block) { - const auto Number = parsePrefixedID(Text, "%bb."); - if (!Number) - return fail(Range, "basic block reference must use the '%bb.N' MIR spelling"); - Block = blockAt(Function, *Number); - if (Block == nullptr) - return fail(Range, "reference to undefined machine basic block"); - return false; -} - -bool MIRDocumentParser::parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function, - std::vector &Points) { - if (requireKind(Node, yaml::NodeKind::Sequence, "frame save/restore points")) - return true; - constexpr std::array Keys{ - std::string_view{"point"}, - std::string_view{"registers"}, - }; - for (const yaml::NodeID PointID : Node.sequence()) { - const yaml::Node *PointNode = node(PointID); - if (PointNode == nullptr || - requireKind(*PointNode, yaml::NodeKind::Mapping, "frame save/restore point") || - validateKeys(*PointNode, Keys, "frame save/restore point")) { - return true; - } - const yaml::Node *BlockNode = mappingValue(*PointNode, "point"); - if (BlockNode == nullptr) - return fail(PointNode->range(), "frame save/restore point requires 'point'"); - std::string_view BlockText; - MachineBasicBlock *Block = nullptr; - if (scalar(*BlockNode, BlockText, "basic block reference") || - parseBlockReference(BlockText, BlockNode->range(), Function, Block)) { - return true; - } - MachineFrameSaveRestorePoint Point{.Block = Block->stableID(), .Registers = {}}; - if (const yaml::Node *RegistersNode = mappingValue(*PointNode, "registers")) { - if (requireKind(*RegistersNode, yaml::NodeKind::Sequence, "save/restore point registers")) { - return true; - } - for (const yaml::NodeID RegID : RegistersNode->sequence()) { - const yaml::Node *RegNode = node(RegID); - PhysicalRegister Reg; - if (RegNode == nullptr || physicalRegister(*RegNode, Reg)) - return true; - Point.Registers.push_back(Reg); - } - } - Points.push_back(std::move(Point)); - } - return false; -} - -bool MIRDocumentParser::parseFrameState(const yaml::Node &Root, MachineFunction &Function, - const MIParserContext &Context) { - const yaml::Node *Frame = mappingValue(Root, "frameInfo"); - if (Frame == nullptr) - return false; - if (requireKind(*Frame, yaml::NodeKind::Mapping, "frameInfo")) - return true; - constexpr std::array Keys{ - std::string_view{"isFrameAddressTaken"}, - std::string_view{"isReturnAddressTaken"}, - std::string_view{"hasStackMap"}, - std::string_view{"hasPatchPoint"}, - std::string_view{"stackSize"}, - std::string_view{"offsetAdjustment"}, - std::string_view{"maxAlignment"}, - std::string_view{"adjustsStack"}, - std::string_view{"hasCalls"}, - std::string_view{"framePointerPolicy"}, - std::string_view{"stackProtector"}, - std::string_view{"functionContext"}, - std::string_view{"maxCallFrameSize"}, - std::string_view{"cvBytesOfCalleeSavedRegisters"}, - std::string_view{"hasOpaqueSPAdjustment"}, - std::string_view{"hasVAStart"}, - std::string_view{"hasMustTailInVarArgFunc"}, - std::string_view{"hasTailCall"}, - std::string_view{"isCalleeSavedInfoValid"}, - std::string_view{"localFrameSize"}, - std::string_view{"savePoint"}, - std::string_view{"restorePoint"}, - }; - if (validateKeys(*Frame, Keys, "frameInfo")) - return true; - - MachineFrameState State = Function.frameInfo().state(); - const std::array BoolBindings{ - std::pair{"isFrameAddressTaken", &State.IsFrameAddressTaken}, - std::pair{"isReturnAddressTaken", &State.IsReturnAddressTaken}, - std::pair{"hasStackMap", &State.HasStackMap}, - std::pair{"hasPatchPoint", &State.HasPatchPoint}, - std::pair{"adjustsStack", &State.AdjustsStack}, - std::pair{"hasCalls", &State.HasCalls}, - std::pair{"hasOpaqueSPAdjustment", &State.HasOpaqueSPAdjustment}, - std::pair{"hasVAStart", &State.HasVAStart}, - std::pair{"hasMustTailInVarArgFunc", &State.HasMustTailInVarArgFunc}, - std::pair{"hasTailCall", &State.HasTailCall}, - std::pair{"isCalleeSavedInfoValid", &State.IsCalleeSavedInfoValid}, - }; - for (const auto &[Key, Destination] : BoolBindings) { - if (const yaml::Node *Value = mappingValue(*Frame, Key)) { - if (boolean(*Value, *Destination)) - return true; - } - } - - if (const yaml::Node *Value = mappingValue(*Frame, "stackSize")) { - if (unsignedValue(*Value, State.StackSize, "an unsigned stack size")) - return true; - } - if (const yaml::Node *Value = mappingValue(*Frame, "offsetAdjustment")) { - std::int64_t Wide = 0; - if (signedValue(*Value, Wide, "a 32-bit signed offset adjustment") || - Wide < std::numeric_limits::min() || - Wide > std::numeric_limits::max()) { - return fail(Value->range(), "offset adjustment is outside the 32-bit signed range"); - } - State.OffsetAdjustment = static_cast(Wide); - } - if (const yaml::Node *Value = mappingValue(*Frame, "maxAlignment")) { - std::uint64_t Bytes = 0; - if (unsignedValue(*Value, Bytes, "an unsigned maximum alignment")) - return true; - if (Bytes == 0) { - State.MaxAlignment.reset(); - } else { - auto Parsed = Alignment::create(Bytes); - if (!Parsed) - return importError(Value->range(), Parsed.error()); - State.MaxAlignment = *Parsed; - } - } - if (const yaml::Node *Value = mappingValue(*Frame, "framePointerPolicy")) { - std::string_view Name; - if (scalar(*Value, Name, "frame-pointer policy")) - return true; - const auto Policy = parseMachineFramePointerPolicy(Name); - if (!Policy) - return fail(Value->range(), "unknown frame-pointer policy '" + std::string(Name) + "'"); - State.FramePointerPolicy = *Policy; - } - for (const auto &[Key, Destination] : {std::pair{"stackProtector", &State.StackProtector}, - std::pair{"functionContext", &State.FunctionContext}}) { - if (const yaml::Node *Value = mappingValue(*Frame, Key)) { - FrameIndex Index; - if (parseFrameReference(*Value, Context, Index)) - return true; - *Destination = Index; - } - } - if (const yaml::Node *Value = mappingValue(*Frame, "maxCallFrameSize")) { - std::uint64_t Size = 0; - if (unsignedValue(*Value, Size, "a 32-bit unsigned maximum call-frame size") || - Size > std::numeric_limits::max()) { - return fail(Value->range(), "maximum call-frame size is outside the 32-bit unsigned range"); - } - State.MaxCallFrameSize = Size; - } - for (const auto &[Key, Destination] : - {std::pair{"cvBytesOfCalleeSavedRegisters", &State.CVBytesOfCalleeSavedRegisters}, - std::pair{"localFrameSize", &State.LocalFrameSize}}) { - if (const yaml::Node *Value = mappingValue(*Frame, Key)) { - std::uint64_t Wide = 0; - if (unsignedValue(*Value, Wide, "a 32-bit unsigned frame value") || - Wide > std::numeric_limits::max()) { - return fail(Value->range(), "frame value is outside the 32-bit unsigned range"); - } - *Destination = static_cast(Wide); - } - } - if (const yaml::Node *Value = mappingValue(*Frame, "savePoint")) { - if (parseSaveRestorePoints(*Value, Function, State.SavePoints)) - return true; - } - if (const yaml::Node *Value = mappingValue(*Frame, "restorePoint")) { - if (parseSaveRestorePoints(*Value, Function, State.RestorePoints)) - return true; - } - auto StateResult = Function.frameInfo().setState(std::move(State)); - if (!StateResult) - return importError(Frame->range(), StateResult.error()); - return false; -} - -bool MIRDocumentParser::reserveJumpTables(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context, - std::vector &Pending) { - const yaml::Node *JumpTable = mappingValue(Root, "jumpTable"); - if (JumpTable == nullptr) - return false; - if (requireKind(*JumpTable, yaml::NodeKind::Mapping, "jump table")) - return true; - constexpr std::array JumpTableKeys{ - std::string_view{"kind"}, - std::string_view{"entries"}, - }; - if (validateKeys(*JumpTable, JumpTableKeys, "jump table")) - return true; - const yaml::Node *KindNode = mappingValue(*JumpTable, "kind"); - if (KindNode == nullptr) - return fail(JumpTable->range(), "jump table requires 'kind'"); - std::string_view KindName; - if (scalar(*KindNode, KindName, "jump table kind")) - return true; - - JumpTableEntryEncoding Encoding; - std::uint8_t EntrySize = 8; - if (KindName == "block-address") - Encoding = JumpTableEntryEncoding::BlockAddress; - else if (KindName == "gp-rel64-block-address") - Encoding = JumpTableEntryEncoding::GPRelative64BlockAddress; - else if (KindName == "gp-rel32-block-address") { - Encoding = JumpTableEntryEncoding::GPRelative32BlockAddress; - EntrySize = 4; - } else if (KindName == "label-difference32") { - Encoding = JumpTableEntryEncoding::LabelDifference32; - EntrySize = 4; - } else if (KindName == "label-difference64") - Encoding = JumpTableEntryEncoding::LabelDifference64; - else if (KindName == "inline") { - Encoding = JumpTableEntryEncoding::Inline; - EntrySize = 4; - } else if (KindName == "custom32") { - Encoding = JumpTableEntryEncoding::Custom32; - EntrySize = 4; - } else { - return fail(KindNode->range(), "unknown jump table kind '" + std::string(KindName) + "'"); - } - - const yaml::Node *Entries = mappingValue(*JumpTable, "entries"); - if (Entries == nullptr) - return false; - if (requireKind(*Entries, yaml::NodeKind::Sequence, "jump table entries")) - return true; - constexpr std::array EntryKeys{ - std::string_view{"id"}, - std::string_view{"blocks"}, - }; - std::unordered_set IDs; - for (const yaml::NodeID EntryID : Entries->sequence()) { - const yaml::Node *Entry = node(EntryID); - if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "jump table entry") || - validateKeys(*Entry, EntryKeys, "jump table entry")) { - return true; - } - const yaml::Node *IDNode = mappingValue(*Entry, "id"); - if (IDNode == nullptr) - return fail(Entry->range(), "jump table entry requires 'id'"); - std::uint64_t WideID = 0; - if (unsignedValue(*IDNode, WideID, "a 32-bit jump table ID") || - WideID > std::numeric_limits::max()) { - return true; - } - const auto TextID = static_cast(WideID); - if (!IDs.insert(TextID).second) - return fail(IDNode->range(), "duplicate jump table ID"); - auto Reserved = Function.jumpTableInfo().reserveTable(); - if (!Reserved) - return importError(Entry->range(), Reserved.error()); - PendingJumpTable NewPending{ - .Index = *Reserved, - .TextID = TextID, - .Encoding = Encoding, - .EntrySize = EntrySize, - .Blocks = {}, - }; - if (const yaml::Node *Blocks = mappingValue(*Entry, "blocks")) { - if (requireKind(*Blocks, yaml::NodeKind::Sequence, "jump table blocks")) - return true; - for (const yaml::NodeID BlockID : Blocks->sequence()) { - const yaml::Node *BlockNode = node(BlockID); - std::string_view Text; - if (BlockNode == nullptr || scalar(*BlockNode, Text, "basic block reference")) - return true; - NewPending.Blocks.emplace_back(Text); - } - } - if (NewPending.Blocks.empty()) - return fail(Entry->range(), "jump table entry must contain at least one block"); - Context.JumpTables.push_back({.TextID = TextID, .Index = *Reserved}); - Pending.push_back(std::move(NewPending)); - } - return false; -} - -bool MIRDocumentParser::defineJumpTables(MachineFunction &Function, - std::span Pending) { - for (const PendingJumpTable &Table : Pending) { - MachineJumpTableDesc Description; - Description.Encoding = Table.Encoding; - Description.EntrySize = Table.EntrySize; - Description.Align = *Alignment::create(Table.EntrySize); - for (const std::string &Text : Table.Blocks) { - MachineBasicBlock *Block = nullptr; - if (parseBlockReference(Text, Result.Source.range(0, 0), Function, Block)) - return true; - Description.Targets.push_back(Block); - } - auto DefineResult = - Function.jumpTableInfo().defineReservedTable(Table.Index, std::move(Description)); - if (!DefineResult) - return importError(Result.Source.range(0, 0), DefineResult.error()); - } - return false; -} - -TextRange MIRDocumentParser::bodyDiagnosticRange(const yaml::Node &Body, - const TextRange &BodyRange) const noexcept { - if (Body.scalarStyle() != yaml::ScalarStyle::Literal) - return Body.range(); - - const std::size_t FirstBodyLine = Body.range().Begin.Line + 1U; - const std::size_t LastBodyLine = Body.range().End.Line; - if (FirstBodyLine > LastBodyLine || FirstBodyLine == 0) - return Body.range(); - - std::size_t Indent = 0; - bool FoundContent = false; - for (std::size_t Line = FirstBodyLine; Line <= LastBodyLine; ++Line) { - const std::string_view Text = Result.Source.line(Line); - std::size_t CurrentIndent = 0; - while (CurrentIndent < Text.size() && Text[CurrentIndent] == ' ') - ++CurrentIndent; - if (CurrentIndent == Text.size()) - continue; - Indent = CurrentIndent; - FoundContent = true; - break; - } - if (!FoundContent) - return Body.range(); - - const TextPosition Fallback = Body.range().Begin; - const auto MapPosition = [this, FirstBodyLine, LastBodyLine, Indent, - Fallback](TextPosition Position) { - const std::size_t SourceLine = FirstBodyLine + Position.Line - 1U; - if (Position.Line == 0 || SourceLine > LastBodyLine || SourceLine > Result.Source.lineCount()) { - return Fallback; - } - const std::string_view SourceLineText = Result.Source.line(SourceLine); - const std::size_t BodyColumn = Position.Column == 0 ? 0 : Position.Column - 1U; - const std::size_t SourceColumn = std::min(Indent + BodyColumn, SourceLineText.size()); - const std::size_t Offset = Result.Source.lineOffsets()[SourceLine - 1U] + SourceColumn; - return Result.Source.positionAt(Offset); - }; - return {.Begin = MapPosition(BodyRange.Begin), .End = MapPosition(BodyRange.End)}; -} - -bool MIRDocumentParser::parseBody(const yaml::Node &Root, MachineFunction &Function, - MIParserContext &Context) { - const yaml::Node *Body = mappingValue(Root, "body"); - std::string BodyText; - TextRange BodyRange = Root.range(); - if (Body != nullptr) { - std::string_view Text; - if (scalar(*Body, Text, "machine function body")) - return true; - BodyText = Text; - BodyRange = Body->range(); - } - - if (Options.DefaultPointerSizeInBits != 0) - Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits; - if (!effectiveDataLayout().empty()) { - const LLVMConstantSyntaxOptions PointerOptions = pointerLayoutOptions(); - Context.PointerSizeInBits = [PointerOptions](std::uint32_t AddressSpace) { - return llvmPointerSizeInBits(AddressSpace, PointerOptions); - }; - } - Context.SyncScopes = ActiveDocument->SyncScopes; - Context.InternSyncScope = [this](std::string_view Name) -> Expected { - if (Name.empty()) - return makeUnexpected(ErrorCode::ParseError, "synchronization scope name cannot be empty"); - const auto Existing = - std::ranges::find_if(ActiveDocument->SyncScopes, - [Name](const MISyncScopeSlot &Slot) { return Slot.Name == Name; }); - if (Existing != ActiveDocument->SyncScopes.end()) - return Existing->ID; - if (ActiveDocument->SyncScopes.size() >= - static_cast(std::numeric_limits::max())) { - return makeUnexpected(ErrorCode::Unsupported, - "MIR document contains too many synchronization scopes"); - } - const auto ID = - SyncScopeID::fromValue(static_cast(ActiveDocument->SyncScopes.size() + 1U)); - ActiveDocument->SyncScopes.push_back({.Name = std::string(Name), .ID = ID}); - return ID; - }; - MIParserOptions BodyOptions = Options.MachineInstructions; - BodyOptions.VerifyAfterParse = false; - MIParser Parser(Function, Target, - std::string(Result.Source.name()) + ":" + std::string(Function.name()), - std::move(BodyText), Context, BodyOptions); - auto Parsed = Parser.parse(); - if (!Parsed) { - if (!Parser.diagnostics().empty()) { - const TextRange Range = Body == nullptr - ? BodyRange - : bodyDiagnosticRange(*Body, Parser.diagnostics().front().Range); - return fail(Range, Parser.diagnostics().front().Message, "mir-body"); - } - return importError(BodyRange, Parsed.error(), "mir-body"); - } - return false; -} - -bool MIRDocumentParser::convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination) { - switch (Source.kind()) { - case yaml::NodeKind::Null: - Destination = MIRTargetNode::null(); - return false; - case yaml::NodeKind::Scalar: - Destination = MIRTargetNode::scalar(std::string(Source.scalar())); - return false; - case yaml::NodeKind::Sequence: { - std::vector Values; - Values.reserve(Source.sequence().size()); - for (const yaml::NodeID ElementID : Source.sequence()) { - const yaml::Node *Element = node(ElementID); - if (Element == nullptr) - return fail(Source.range(), "target MIR sequence contains an invalid node"); - MIRTargetNode Value; - if (convertTargetNode(*Element, Value)) - return true; - Values.push_back(std::move(Value)); - } - Destination = MIRTargetNode::sequence(std::move(Values)); - return false; - } - case yaml::NodeKind::Mapping: { - std::vector Entries; - Entries.reserve(Source.mapping().size()); - for (const yaml::MappingEntry &SourceEntry : Source.mapping()) { - const yaml::Node *ValueNode = node(SourceEntry.Value); - if (ValueNode == nullptr) - return fail(SourceEntry.KeyRange, "target MIR mapping contains an invalid node"); - MIRTargetNode Value; - if (convertTargetNode(*ValueNode, Value)) - return true; - Entries.push_back({SourceEntry.Key, std::move(Value)}); - } - Destination = MIRTargetNode::mapping(std::move(Entries)); - return false; - } - } - return fail(Source.range(), "target MIR node has an unknown kind"); -} - -bool MIRDocumentParser::parseTargetState(const yaml::Node &Root, MachineFunction &Function) { - const yaml::Node *StateNode = mappingValue(Root, "machineFunctionInfo"); - if (StateNode == nullptr) - return false; - MIRTargetNode TargetNode; - if (convertTargetNode(*StateNode, TargetNode)) - return true; - auto State = Target.mirText().parseFunctionState(TargetNode); - if (!State) - return importError(StateNode->range(), State.error(), "mir-target-state"); - auto SetResult = Function.setTargetState(std::move(*State)); - if (!SetResult) - return importError(StateNode->range(), SetResult.error(), "mir-target-state"); - return false; -} - -bool MIRDocumentParser::parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function) { - auto ParseLocation = [this, &Function](const yaml::Node &Entry, - MachineInstr *&Instruction) -> bool { - const yaml::Node *BlockNode = mappingValue(Entry, "bb"); - const yaml::Node *OffsetNode = mappingValue(Entry, "offset"); - if (BlockNode == nullptr || OffsetNode == nullptr) - return fail(Entry.range(), "instruction location requires 'bb' and 'offset'"); - std::uint64_t Block = 0; - std::uint64_t Offset = 0; - if (unsignedValue(*BlockNode, Block, "a 32-bit basic block number") || - unsignedValue(*OffsetNode, Offset, "a 32-bit instruction offset") || - Block > std::numeric_limits::max() || - Offset > std::numeric_limits::max()) { - return true; - } - Instruction = instructionAt(Function, static_cast(Block), - static_cast(Offset)); - if (Instruction == nullptr) - return fail(Entry.range(), "instruction location is outside the machine function body"); - return false; - }; - - if (const yaml::Node *CallSites = mappingValue(Root, "callSites")) { - if (requireKind(*CallSites, yaml::NodeKind::Sequence, "call-site records")) - return true; - constexpr std::array CallSiteKeys{ - std::string_view{"bb"}, - std::string_view{"offset"}, - std::string_view{"fwdArgRegs"}, - std::string_view{"calleeTypeIds"}, - }; - constexpr std::array ArgumentKeys{ - std::string_view{"arg"}, - std::string_view{"reg"}, - }; - for (const yaml::NodeID CallSiteID : CallSites->sequence()) { - const yaml::Node *CallSiteNode = node(CallSiteID); - if (CallSiteNode == nullptr || - requireKind(*CallSiteNode, yaml::NodeKind::Mapping, "call-site record") || - validateKeys(*CallSiteNode, CallSiteKeys, "call-site record")) { - return true; - } - MachineInstr *Call = nullptr; - if (ParseLocation(*CallSiteNode, Call)) - return true; - MachineCallSiteInfo Info{ - .CallInstruction = Call->stableID(), - .ForwardedArguments = {}, - .CalleeTypeIDs = {}, - }; - if (const yaml::Node *Arguments = mappingValue(*CallSiteNode, "fwdArgRegs")) { - if (requireKind(*Arguments, yaml::NodeKind::Sequence, "forwarded call arguments")) { - return true; - } - for (const yaml::NodeID ArgumentID : Arguments->sequence()) { - const yaml::Node *Argument = node(ArgumentID); - if (Argument == nullptr || - requireKind(*Argument, yaml::NodeKind::Mapping, "forwarded call argument") || - validateKeys(*Argument, ArgumentKeys, "forwarded call argument")) { - return true; - } - const yaml::Node *NumberNode = mappingValue(*Argument, "arg"); - const yaml::Node *RegNode = mappingValue(*Argument, "reg"); - if (NumberNode == nullptr || RegNode == nullptr) - return fail(Argument->range(), "forwarded call argument requires 'arg' and 'reg'"); - std::uint16_t Number = 0; - std::uint64_t WideNumber = 0; - PhysicalRegister Reg; - if (unsignedValue(*NumberNode, WideNumber, "a 16-bit argument number") || - WideNumber > std::numeric_limits::max() || - physicalRegister(*RegNode, Reg)) { - return true; - } - Number = static_cast(WideNumber); - Info.ForwardedArguments.push_back({Reg, Number}); - } - } - if (const yaml::Node *TypeIDs = mappingValue(*CallSiteNode, "calleeTypeIds")) { - if (requireKind(*TypeIDs, yaml::NodeKind::Sequence, "callee type IDs")) - return true; - for (const yaml::NodeID TypeID : TypeIDs->sequence()) { - const yaml::Node *TypeNode = node(TypeID); - std::uint64_t Value = 0; - if (TypeNode == nullptr || - unsignedValue(*TypeNode, Value, "an unsigned callee type ID")) { - return true; - } - Info.CalleeTypeIDs.push_back(Value); - } - } - auto AddResult = Function.records().addCallSite(std::move(Info)); - if (!AddResult) - return importError(CallSiteNode->range(), AddResult.error()); - } - } - - if (const yaml::Node *CalledGlobals = mappingValue(Root, "calledGlobals")) { - if (requireKind(*CalledGlobals, yaml::NodeKind::Sequence, "called-global records")) - return true; - constexpr std::array Keys{ - std::string_view{"bb"}, - std::string_view{"offset"}, - std::string_view{"callee"}, - std::string_view{"flags"}, - }; - for (const yaml::NodeID CalledID : CalledGlobals->sequence()) { - const yaml::Node *CalledNode = node(CalledID); - if (CalledNode == nullptr || - requireKind(*CalledNode, yaml::NodeKind::Mapping, "called-global record") || - validateKeys(*CalledNode, Keys, "called-global record")) { - return true; - } - MachineInstr *Call = nullptr; - if (ParseLocation(*CalledNode, Call)) - return true; - const yaml::Node *CalleeNode = mappingValue(*CalledNode, "callee"); - const yaml::Node *FlagsNode = mappingValue(*CalledNode, "flags"); - if (CalleeNode == nullptr || FlagsNode == nullptr) - return fail(CalledNode->range(), "called-global record requires 'callee' and 'flags'"); - std::string_view Callee; - std::uint64_t WideFlags = 0; - if (scalar(*CalleeNode, Callee, "callee name") || - unsignedValue(*FlagsNode, WideFlags, "32-bit called-global flags") || - WideFlags > std::numeric_limits::max()) { - return true; - } - if (ActiveDocument->EmbeddedIR && !ActiveDocument->EmbeddedIR->hasGlobalValue(Callee)) { - return fail(CalleeNode->range(), "called global references an undefined embedded global"); - } - auto AddResult = - Function.records().addCalledGlobal({.CallInstruction = Call->stableID(), - .Callee = std::string(Callee), - .Flags = static_cast(WideFlags)}); - if (!AddResult) - return importError(CalledNode->range(), AddResult.error()); - } - } - - if (const yaml::Node *Substitutions = mappingValue(Root, "debugValueSubstitutions")) { - if (requireKind(*Substitutions, yaml::NodeKind::Sequence, "debug value substitutions")) { - return true; - } - constexpr std::array Keys{ - std::string_view{"srcinst"}, std::string_view{"srcop"}, std::string_view{"dstinst"}, - std::string_view{"dstop"}, std::string_view{"subreg"}, - }; - for (const yaml::NodeID SubstitutionID : Substitutions->sequence()) { - const yaml::Node *Substitution = node(SubstitutionID); - if (Substitution == nullptr || - requireKind(*Substitution, yaml::NodeKind::Mapping, "debug value substitution") || - validateKeys(*Substitution, Keys, "debug value substitution")) { - return true; - } - std::array Values{}; - for (std::size_t Index = 0; Index < Keys.size(); ++Index) { - const yaml::Node *ValueNode = mappingValue(*Substitution, Keys[Index]); - if (ValueNode == nullptr) - return fail(Substitution->range(), "debug value substitution requires every fixed field"); - std::uint64_t Wide = 0; - if (unsignedValue(*ValueNode, Wide, "a 32-bit debug substitution value") || - Wide > std::numeric_limits::max()) { - return true; - } - Values[Index] = static_cast(Wide); - } - auto AddResult = Function.records().addDebugValueSubstitution( - {.Source = {Values[0], Values[1]}, - .Destination = {Values[2], Values[3]}, - .Subregister = SubRegIndex::fromValue(Values[4])}); - if (!AddResult) - return importError(Substitution->range(), AddResult.error()); - } - } - - if (const yaml::Node *PrefetchTargets = mappingValue(Root, "prefetch-targets")) { - if (requireKind(*PrefetchTargets, yaml::NodeKind::Sequence, "prefetch targets")) - return true; - for (const yaml::NodeID TargetID : PrefetchTargets->sequence()) { - const yaml::Node *TargetNode = node(TargetID); - std::string_view Text; - if (TargetNode == nullptr || scalar(*TargetNode, Text, "prefetch target")) - return true; - Text = trim(Text); - if (!Text.starts_with("bb_id")) - return fail(TargetNode->range(), "prefetch target must start with 'bb_id'"); - Text = trim(Text.substr(std::string_view("bb_id").size())); - std::array Values{}; - for (std::size_t Index = 0; Index < Values.size(); ++Index) { - const std::size_t Comma = Text.find(','); - const std::string_view Part = Index + 1 == Values.size() ? Text : Text.substr(0, Comma); - if ((Index + 1 != Values.size() && Comma == std::string_view::npos) || - !parseUnsigned32(trim(Part), Values[Index])) { - return fail(TargetNode->range(), "prefetch target has invalid numeric fields"); - } - if (Index + 1 != Values.size()) - Text = trim(Text.substr(Comma + 1)); - } - auto AddResult = Function.records().addPrefetchTarget( - {.Block = {Values[0], Values[1]}, .CallsiteIndex = Values[2]}); - if (!AddResult) - return importError(TargetNode->range(), AddResult.error()); - } - } - - if (const yaml::Node *EntryValues = mappingValue(Root, "entry_values")) { - if (requireKind(*EntryValues, yaml::NodeKind::Sequence, "entry values")) - return true; - constexpr std::array Keys{ - std::string_view{"entry-value-register"}, - std::string_view{"debug-info-variable"}, - std::string_view{"debug-info-expression"}, - std::string_view{"debug-info-location"}, - }; - for (const yaml::NodeID EntryID : EntryValues->sequence()) { - const yaml::Node *Entry = node(EntryID); - if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "entry value") || - validateKeys(*Entry, Keys, "entry value")) { - return true; - } - const yaml::Node *RegNode = mappingValue(*Entry, "entry-value-register"); - const yaml::Node *VariableNode = mappingValue(*Entry, "debug-info-variable"); - const yaml::Node *ExpressionNode = mappingValue(*Entry, "debug-info-expression"); - const yaml::Node *LocationNode = mappingValue(*Entry, "debug-info-location"); - if (RegNode == nullptr || VariableNode == nullptr || ExpressionNode == nullptr || - LocationNode == nullptr) { - return fail(Entry->range(), "entry value requires all four fixed fields"); - } - PhysicalRegister Reg; - if (physicalRegister(*RegNode, Reg)) - return true; - MachineVariableDebugInfo DebugInfo; - if (parseVariableDebugInfo(*Entry, Function, DebugInfo)) - return true; - if (!DebugInfo.Variable || !DebugInfo.Expression || !DebugInfo.Location) { - return fail(Entry->range(), "entry value debug metadata cannot be empty", - "mir-debug-metadata"); - } - auto AddResult = - Function.records().addEntryValue({.Reg = Reg, .DebugInfo = std::move(DebugInfo)}); - if (!AddResult) - return importError(Entry->range(), AddResult.error()); - } - } - return false; -} - -Expected verifyMIRDocumentReferences(const MIRDocument &Document, - const MachineFunction &Function, bool UseEmbeddedIR) { - const EmbeddedIRReferenceIndex *Embedded = UseEmbeddedIR ? Document.embeddedIR() : nullptr; - const EmbeddedIRFunction *IRFunction = nullptr; - if (Embedded != nullptr) { - IRFunction = Embedded->function(Function.name()); - if (IRFunction == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - "machine function has no matching embedded LLVM IR function"); - } - } - - const auto ValidateGlobal = [Embedded](std::string_view Name, - std::string_view Description) -> Expected { - if (Embedded == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + - " requires embedded LLVM IR to resolve global '" + - std::string(Name) + "'"); - } - if (Embedded->hasGlobalValue(Name)) - return {}; - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + - " references an undefined embedded LLVM IR global '" + - std::string(Name) + "'"); - }; - const auto ValidateBlockAddress = [Embedded](std::string_view FunctionName, - std::string_view BlockName, - std::string_view Description) -> Expected { - if (Embedded == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + - " requires embedded LLVM IR to resolve a block address"); - } - const EmbeddedIRFunction *AddressFunction = Embedded->function(FunctionName); - if (AddressFunction == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + - " references an undefined embedded LLVM IR function '" + - std::string(FunctionName) + "'"); - } - if (AddressFunction->block(BlockName) == nullptr) { - return makeUnexpected( - ErrorCode::VerificationError, - std::string(Description) + " references an undefined embedded LLVM IR block '" + - std::string(BlockName) + "' in function '" + std::string(FunctionName) + "'"); - } - return {}; - }; - const auto ValidateIRValue = [IRFunction](std::string_view Name, - std::string_view Description) -> Expected { - if (IRFunction == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + - " requires embedded LLVM IR to resolve IR value '%" + - std::string(Name) + "'"); - } - if (IRFunction->value(Name) != nullptr) - return {}; - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + - " references an undefined embedded LLVM IR value '%" + - std::string(Name) + "'"); - }; - const auto ValidateMetadata = [Embedded](MachineMetadataReference Reference, - std::initializer_list ExpectedTypes, - std::string_view Description) -> Expected { - if (!Reference.isValid()) { - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + " has an invalid metadata reference"); - } - if (Reference.scope() != MachineMetadataScope::EmbeddedIR) - return {}; - if (Embedded == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + " requires embedded LLVM IR metadata !" + - std::to_string(Reference.slot())); - } - const EmbeddedIRMetadataDefinition *Definition = - Embedded->metadata(std::to_string(Reference.slot())); - if (Definition == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + - " references undefined embedded LLVM IR metadata !" + - std::to_string(Reference.slot())); - } - if (ExpectedTypes.size() == 0 || - std::ranges::find(ExpectedTypes, Definition->DynamicType) != ExpectedTypes.end()) { - return {}; - } - std::string Expected; - for (const std::string_view Type : ExpectedTypes) { - if (!Expected.empty()) - Expected.append(" or "); - Expected.append(Type); - } - return makeUnexpected(ErrorCode::VerificationError, - std::string(Description) + " requires " + Expected + " metadata, but !" + - std::to_string(Reference.slot()) + " is " + - (Definition->DynamicType.empty() - ? std::string("an unclassified metadata node") - : Definition->DynamicType)); - }; - const auto ValidateMetadataValue = - [&ValidateMetadata](const MachineMetadataValue &Value, - std::string_view Description) -> Expected { - const MachineMetadataReference *Reference = Value.reference(); - return Reference == nullptr ? Expected{} : ValidateMetadata(*Reference, {}, Description); - }; - const auto ValidateLocation = - [&ValidateMetadata](const MachineDebugLocation &Location, - std::string_view Description) -> Expected { - if (const MachineMetadataReference *Reference = Location.reference()) - return ValidateMetadata(*Reference, {"DILocation"}, Description); - const MachineDILocation *Inline = Location.inlineLocation(); - if (Inline == nullptr) - return {}; - for (const MachineDILocationFrame &Frame : Inline->inlineChain()) { - auto Verification = - ValidateMetadata(Frame.Scope, {"DISubprogram", "DILexicalBlock", "DILexicalBlockFile"}, - "inline DILocation scope"); - if (!Verification) - return Verification; - } - if (Inline->terminalInlinedAt()) { - return ValidateMetadata(*Inline->terminalInlinedAt(), {"DILocation"}, - "inline DILocation inlinedAt"); - } - return {}; - }; - const auto ValidateVariableDebugInfo = - [&ValidateMetadata, &ValidateMetadataValue, - &ValidateLocation](const MachineVariableDebugInfo &DebugInfo) -> Expected { - if (DebugInfo.Variable) { - auto Verification = - ValidateMetadata(*DebugInfo.Variable, {"DILocalVariable"}, "debug variable"); - if (!Verification) - return Verification; - } - if (DebugInfo.Expression) { - if (const MachineMetadataReference *Reference = DebugInfo.Expression->reference(); - Reference != nullptr) { - auto Verification = ValidateMetadata(*Reference, {"DIExpression"}, "debug expression"); - if (!Verification) - return Verification; - } - auto Verification = ValidateMetadataValue(*DebugInfo.Expression, "debug expression"); - if (!Verification) - return Verification; - } - if (DebugInfo.Location) - return ValidateLocation(*DebugInfo.Location, "debug location"); - return {}; - }; - const auto ValidateGenericType = [Embedded](const auto &Self, - const GenericConstantType &Type) -> Expected { - if (Type.Kind == GenericConstantTypeKind::Named) { - if (Embedded == nullptr) { - return makeUnexpected( - ErrorCode::VerificationError, - "generic constant type requires embedded LLVM IR to resolve named type '%" + Type.Name + - "'"); - } - if (Embedded->typeDefinition(Type.Name) == nullptr) { - return makeUnexpected( - ErrorCode::VerificationError, - "generic constant type references an undefined embedded LLVM IR named type '%" + - Type.Name + "'"); - } - } - for (const GenericConstantType &Element : Type.Elements) { - auto Verification = Self(Self, Element); - if (!Verification) - return Verification; - } - return {}; - }; - const auto ValidateGenericConstant = - [&ValidateBlockAddress, &ValidateGenericType, - &ValidateGlobal](const auto &Self, const GenericConstantData &Constant) -> Expected { - auto TypeVerification = ValidateGenericType(ValidateGenericType, Constant.Type); - if (!TypeVerification) - return TypeVerification; - if (Constant.SourceElementType) { - TypeVerification = ValidateGenericType(ValidateGenericType, *Constant.SourceElementType); - if (!TypeVerification) - return TypeVerification; - } - switch (Constant.Kind) { - case GenericConstantKind::GlobalAddress: - case GenericConstantKind::DSOLocalEquivalent: - case GenericConstantKind::NoCFI: - if (auto Verification = ValidateGlobal(Constant.Symbol, "generic constant"); !Verification) - return Verification; - break; - case GenericConstantKind::BlockAddress: - if (auto Verification = ValidateBlockAddress(Constant.Symbol, Constant.Block, - "generic constant blockaddress"); - !Verification) { - return Verification; - } - break; - default: - break; - } - for (const GenericConstantData &Element : Constant.Elements) { - auto Verification = Self(Self, Element); - if (!Verification) - return Verification; - } - return {}; - }; - - for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; - Block = Block->next()) { - if (const auto Name = Block->attributes().irBlockName(); Name) { - if (IRFunction == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - "machine basic block requires embedded LLVM IR to resolve block '" + - std::string(*Name) + "'"); - } - if (IRFunction->block(*Name) == nullptr) { - return makeUnexpected( - ErrorCode::VerificationError, - "machine basic block references an undefined embedded LLVM IR block '" + - std::string(*Name) + "'"); - } - } - } - for (const StackObject &Object : Function.frameInfo().objects()) { - const StackObjectDesc *Description = Object.description(); - if (Description == nullptr || !Description->AllocaName) - continue; - if (IRFunction == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - "stack object requires embedded LLVM IR to resolve alloca '" + - *Description->AllocaName + "'"); - } - if (IRFunction->alloca(*Description->AllocaName) == nullptr) { - return makeUnexpected(ErrorCode::VerificationError, - "stack object references an undefined embedded LLVM IR alloca '" + - *Description->AllocaName + "'"); - } - } - - for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) { - const MachineConstantPoolEntryDesc *Description = Entry.description(); - if (Description == nullptr) - continue; - if (const auto *Generic = std::get_if(&Description->Value)) { - auto Verification = ValidateGenericConstant(ValidateGenericConstant, *Generic); - if (!Verification) - return Verification; - } else if (const auto *Address = std::get_if(&Description->Value)) { - auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName, - "constant-pool block address"); - if (!Verification) - return Verification; - } - } - - for (const MachineMetadataNode &Node : Function.metadata().nodes()) { - for (const MachineMetadataElement &Element : Node.Elements) { - const MachineMetadataReference *Reference = Element.reference(); - if (Reference == nullptr) - continue; - auto Verification = ValidateMetadata(*Reference, {}, "machine metadata node"); - if (!Verification) - return Verification; - } - } - - for (const StackObject &Object : Function.frameInfo().objects()) { - const StackObjectDesc *Description = Object.description(); - if (Description == nullptr || !Description->DebugInfo) - continue; - auto Verification = ValidateVariableDebugInfo(*Description->DebugInfo); - if (!Verification) - return Verification; - } - for (const MachineEntryValueInfo &Entry : Function.records().entryValues()) { - auto Verification = ValidateVariableDebugInfo(Entry.DebugInfo); - if (!Verification) - return Verification; - } - for (const MachineCalledGlobal &Called : Function.records().calledGlobals()) { - auto Verification = ValidateGlobal(Called.Callee, "called-global record"); - if (!Verification) - return Verification; - } - - for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; - Block = Block->next()) { - for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; - Instruction = Instruction->next()) { - if (Instruction->debugLocation()) { - auto Verification = - ValidateLocation(*Instruction->debugLocation(), "instruction debug location"); - if (!Verification) - return Verification; - } - const std::array InstructionMetadata{ - Instruction->heapAllocMarker(), - Instruction->pcSections(), - Instruction->mmra(), - }; - for (const auto &Reference : InstructionMetadata) { - if (!Reference) - continue; - auto Verification = ValidateMetadata(*Reference, {}, "instruction metadata attachment"); - if (!Verification) - return Verification; - } - - for (const MachineOperand &Operand : Instruction->operands()) { - if (const auto *Global = Operand.getIf(); Global != nullptr) { - auto Verification = ValidateGlobal(Global->Name, "global-symbol operand"); - if (!Verification) - return Verification; - } - if (const auto *Address = Operand.getIf(); Address != nullptr) { - auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName, - "block-address operand"); - if (!Verification) - return Verification; - } - if (const auto *Metadata = Operand.getIf(); Metadata != nullptr) { - auto Verification = ValidateMetadataValue(Metadata->Value, "metadata operand"); - if (!Verification) - return Verification; - } - } - - for (const MachineMemOperand *Memory : Instruction->memOperands()) { - const MachineMemOperandDesc &Description = Memory->description(); - const std::optional BaseName = Description.Base.symbolName(); - switch (Description.Base.kind()) { - case MemoryBase::Kind::GlobalSymbol: - case MemoryBase::Kind::CallEntryGlobal: - if (BaseName) { - auto Verification = ValidateGlobal(*BaseName, "machine memory base"); - if (!Verification) - return Verification; - } - break; - case MemoryBase::Kind::IRValue: - if (BaseName) { - auto Verification = ValidateIRValue(*BaseName, "machine memory base"); - if (!Verification) - return Verification; - } - break; - default: - break; - } - const std::array MetadataReferences{ - Description.TBAA, Description.AliasScope, - Description.NoAlias, Description.NoAliasAddressSpace, - Description.RangeMetadata, - }; - for (const auto &Reference : MetadataReferences) { - if (!Reference) - continue; - auto Verification = ValidateMetadata(*Reference, {}, "machine memory operand metadata"); - if (!Verification) - return Verification; - } - if (Description.SyncScope.value() == 0) - continue; - const auto Scope = - std::ranges::find_if(Document.syncScopes(), [&Description](const auto &Slot) { - return Slot.ID == Description.SyncScope; - }); - if (Scope == Document.syncScopes().end()) { - return makeUnexpected( - ErrorCode::VerificationError, - "machine memory operand synchronization scope has no document name"); - } - } - } - } - return {}; -} - -bool MIRDocumentParser::validateDocumentReferences(const MachineFunction &Function) { - auto Verification = verifyMIRDocumentReferences(*ActiveDocument, Function, true); - if (Verification) - return false; - return importError(Result.Source.range(0, 0), Verification.error()); -} - -bool MIRDocumentParser::parseFunction(const yaml::Node &Root, MIRDocument &Document) { - constexpr std::array Keys{ - std::string_view{"name"}, - std::string_view{"alignment"}, - std::string_view{"exposesReturnsTwice"}, - std::string_view{"legalized"}, - std::string_view{"regBankSelected"}, - std::string_view{"selected"}, - std::string_view{"failedISel"}, - std::string_view{"tracksRegLiveness"}, - std::string_view{"hasWinCFI"}, - std::string_view{"noPhis"}, - std::string_view{"isSSA"}, - std::string_view{"noVRegs"}, - std::string_view{"hasFakeUses"}, - std::string_view{"callsEHReturn"}, - std::string_view{"callsUnwindInit"}, - std::string_view{"hasEHContTarget"}, - std::string_view{"hasEHScopes"}, - std::string_view{"hasEHFunclets"}, - std::string_view{"isOutlined"}, - std::string_view{"debugInstrRef"}, - std::string_view{"failsVerification"}, - std::string_view{"tracksDebugUserValues"}, - std::string_view{"registers"}, - std::string_view{"liveins"}, - std::string_view{"calleeSavedRegisters"}, - std::string_view{"frameInfo"}, - std::string_view{"fixedStack"}, - std::string_view{"stack"}, - std::string_view{"entry_values"}, - std::string_view{"callSites"}, - std::string_view{"debugValueSubstitutions"}, - std::string_view{"constants"}, - std::string_view{"machineFunctionInfo"}, - std::string_view{"jumpTable"}, - std::string_view{"machineMetadataNodes"}, - std::string_view{"calledGlobals"}, - std::string_view{"prefetch-targets"}, - std::string_view{"body"}, - }; - if (validateKeys(Root, Keys, "machine function")) - return true; - const yaml::Node *NameNode = mappingValue(Root, "name"); - if (NameNode == nullptr) - return fail(Root.range(), "machine function requires 'name'"); - std::string_view Name; - if (scalar(*NameNode, Name, "machine function name") || Name.empty()) - return fail(NameNode->range(), "machine function name cannot be empty"); - - if (Document.EmbeddedIR) { - const EmbeddedIRFunction *IRFunction = Document.EmbeddedIR->function(Name); - if (IRFunction == nullptr) - return fail(NameNode->range(), - "machine function has no corresponding embedded LLVM IR function"); - } - - auto Created = Document.Unit.createMachineFunction(std::string(Name)); - if (!Created) - return importError(NameNode->range(), Created.error()); - MachineFunction &Function = **Created; - ActiveFunction = &Function; - - MIRFunctionDocumentInfo DocumentInfo{ - .Function = Function.stableID(), - .NoPHIs = std::nullopt, - .IsSSA = std::nullopt, - .NoVRegs = std::nullopt, - .HasFakeUses = std::nullopt, - }; - MIParserContext Context; - Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits; - - std::vector PendingJumpTables; - if (parseFunctionScalars(Root, Function, DocumentInfo) || - parseMetadata(Root, Function, Context) || parseRegisters(Root, Function, Context) || - parseStackObjects(Root, Function, Context) || parseConstants(Root, Function, Context) || - reserveJumpTables(Root, Function, Context, PendingJumpTables) || - parseBody(Root, Function, Context) || defineJumpTables(Function, PendingJumpTables) || - recomputeProperties(Root, Function, DocumentInfo) || - parseFrameState(Root, Function, Context) || parseTargetState(Root, Function) || - parseFunctionRecords(Root, Function) || validateDocumentReferences(Function)) { - return true; - } - - if (Options.VerifyMachineFunctions) { - auto Verified = MachineVerifier::verify(Function, Target); - if (!Verified) - return importError(Root.range(), Verified.error(), "mir-verifier"); - } - Document.FunctionInfo.push_back(DocumentInfo); - ActiveFunction = nullptr; - return false; -} MIRDocument::MIRDocument() = default; MIRDocument::MIRDocument(CompilationUnit Unit) : Unit(std::move(Unit)) {} diff --git a/backend/lib/MIR/MIRDocumentParser.cpp b/backend/lib/MIR/MIRDocumentParser.cpp new file mode 100644 index 0000000..defcce9 --- /dev/null +++ b/backend/lib/MIR/MIRDocumentParser.cpp @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MIRDocumentParser.h" + +#include "MIRDocumentReferenceValidation.h" +#include "MIRMetadataTextParser.h" +#include "MIRParserUtilities.h" +#include "rcc/CodeGen/MachineBasicBlock.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/MachineVerifier.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/MIR/LLVMConstantSyntax.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +using parser_detail::parseBool; +using parser_detail::parsePrefixedID; +using parser_detail::parseSigned; +using parser_detail::parseUnsigned; +using parser_detail::parseUnsigned32; +using parser_detail::trim; + +bool MIRDocumentParser::fail(const TextRange &Range, std::string Message, std::string Category) { + if (Result.Diagnostics.empty()) { + if (ActiveFunction != nullptr) + Message = "function '" + std::string(ActiveFunction->name()) + "': " + std::move(Message); + Result.Diagnostics.push_back(TextDiagnostic{ + .Severity = DiagnosticSeverity::Error, + .Category = std::move(Category), + .Message = std::move(Message), + .SourceName = std::string(Result.Source.name()), + .Range = Range, + }); + } + return true; +} + +bool MIRDocumentParser::importError(const TextRange &Range, const Error &Failure, + std::string Category) { + return fail(Range, std::string(Failure.message()), std::move(Category)); +} + +const yaml::Node *MIRDocumentParser::mappingValue(const yaml::Node &Mapping, + std::string_view Key) const noexcept { + const auto Value = Mapping.mappingValue(Key); + return Value ? node(*Value) : nullptr; +} + +std::string_view MIRDocumentParser::effectiveDataLayout() const noexcept { + if (ActiveDocument != nullptr) { + if (const EmbeddedIRReferenceIndex *Embedded = ActiveDocument->embeddedIR()) { + if (const auto DataLayout = Embedded->dataLayout()) + return *DataLayout; + } + } + return Options.DataLayout; +} + +LLVMConstantSyntaxOptions MIRDocumentParser::pointerLayoutOptions() const noexcept { + return { + .DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits, + .DataLayout = effectiveDataLayout(), + .NamedTypes = {}, + }; +} + +bool MIRDocumentParser::requireKind(const yaml::Node &Node, yaml::NodeKind Kind, + std::string_view Description) { + if (Node.kind() == Kind) + return false; + return fail(Node.range(), std::string(Description) + " has the wrong YAML node kind"); +} + +bool MIRDocumentParser::validateKeys(const yaml::Node &Mapping, + std::span Keys, + std::string_view Description) { + for (const yaml::MappingEntry &Entry : Mapping.mapping()) { + if (std::ranges::find(Keys, Entry.Key) == Keys.end()) { + return fail(Entry.KeyRange, + "unknown " + std::string(Description) + " key '" + Entry.Key + "'"); + } + } + return false; +} + +bool MIRDocumentParser::scalar(const yaml::Node &Node, std::string_view &Value, + std::string_view Description) { + if (requireKind(Node, yaml::NodeKind::Scalar, Description)) + return true; + Value = Node.scalar(); + return false; +} + +bool MIRDocumentParser::boolean(const yaml::Node &Node, bool &Value) { + std::string_view Text; + if (scalar(Node, Text, "boolean") || !parseBool(Text, Value)) + return fail(Node.range(), "expected 'true' or 'false'"); + return false; +} + +bool MIRDocumentParser::unsignedValue(const yaml::Node &Node, std::uint64_t &Value, + std::string_view Description) { + std::string_view Text; + if (scalar(Node, Text, Description) || !parseUnsigned(Text, Value)) + return fail(Node.range(), "expected " + std::string(Description)); + return false; +} + +bool MIRDocumentParser::signedValue(const yaml::Node &Node, std::int64_t &Value, + std::string_view Description) { + std::string_view Text; + if (scalar(Node, Text, Description) || !parseSigned(Text, Value)) + return fail(Node.range(), "expected " + std::string(Description)); + return false; +} + +bool MIRDocumentParser::alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero) { + std::uint64_t Bytes = 0; + if (unsignedValue(Node, Bytes, "an unsigned alignment")) + return true; + if (Bytes == 0 && AllowZero) { + Value = Alignment{}; + return false; + } + auto Parsed = Alignment::create(Bytes); + if (!Parsed) + return importError(Node.range(), Parsed.error()); + Value = *Parsed; + return false; +} + +bool MIRDocumentParser::physicalRegister(std::string_view Text, const TextRange &Range, + PhysicalRegister &Reg) { + Text = trim(Text); + if (!Text.starts_with('$') || Text.size() == 1) + return fail(Range, "physical register must use the '$name' MIR spelling"); + const PhysicalRegisterDesc *Description = Target.registers().physicalRegister(Text.substr(1)); + if (Description == nullptr) + return fail(Range, "unknown physical register '" + std::string(Text) + "'"); + Reg = Description->Reg; + return false; +} + +bool MIRDocumentParser::physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg) { + std::string_view Text; + return scalar(Node, Text, "physical register") || physicalRegister(Text, Node.range(), Reg); +} + +bool MIRDocumentParser::virtualRegister(std::string_view Text, const TextRange &Range, + const MIParserContext &Context, Register &Reg) { + const auto ID = parsePrefixedID(Text, "%"); + if (!ID) + return fail(Range, "virtual register must use the '%N' MIR spelling"); + const auto Found = + std::ranges::find_if(Context.VirtualRegisters, + [ID](const MIVirtualRegisterSlot &Slot) { return Slot.TextID == *ID; }); + if (Found == Context.VirtualRegisters.end()) + return fail(Range, "reference to undefined virtual register '%" + std::to_string(*ID) + "'"); + Reg = Found->Reg; + return false; +} + +MIRDocumentParseResult MIRDocumentParser::run() { + yaml::ParseResult YAMLResult = yaml::parse(std::string(Result.Source.name()), + std::string(Result.Source.text()), Options.YAML); + if (!YAMLResult.succeeded()) { + const auto Diagnostics = YAMLResult.diagnostics(); + Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end()); + return std::move(Result); + } + Stream = YAMLResult.stream(); + if (Stream == nullptr || Stream->documents().empty()) { + static_cast(fail(Result.Source.range(0, 0), "MIR stream contains no YAML documents")); + return std::move(Result); + } + + MIRDocument Staging; + ActiveDocument = &Staging; + std::size_t FirstFunction = 0; + const yaml::Node *FirstRoot = node(Stream->documents().front().Root); + if (FirstRoot != nullptr && FirstRoot->kind() == yaml::NodeKind::Scalar) { + EmbeddedIRIndexResult Embedded = indexEmbeddedIR( + std::string(Result.Source.name()) + ":embedded-ir", std::string(FirstRoot->scalar())); + if (!Embedded.succeeded()) { + const auto Diagnostics = Embedded.diagnostics(); + Result.Diagnostics.assign(Diagnostics.begin(), Diagnostics.end()); + return std::move(Result); + } + Staging.EmbeddedIR = std::move(Embedded).takeIndex(); + FirstFunction = 1; + } + + if (const EmbeddedIRReferenceIndex *Embedded = Staging.embeddedIR()) { + if (const auto DataLayout = Embedded->dataLayout(); + DataLayout && !Options.DataLayout.empty() && *DataLayout != Options.DataLayout) { + static_cast(fail(Stream->documents().front().Range, + "explicit DataLayout does not match embedded LLVM IR data layout")); + return std::move(Result); + } + } + if (!effectiveDataLayout().empty()) { + auto PointerSize = llvmPointerSizeInBits(0, pointerLayoutOptions()); + if (!PointerSize) { + static_cast( + importError(Stream->documents().front().Range, PointerSize.error(), "mir-data-layout")); + return std::move(Result); + } + } + + if (FirstFunction == Stream->documents().size()) { + static_cast(fail(Stream->documents().front().Range, + "MIR stream must contain at least one machine function document")); + return std::move(Result); + } + + for (std::size_t Index = FirstFunction; Index < Stream->documents().size(); ++Index) { + const yaml::Node *Root = node(Stream->documents()[Index].Root); + if (Root == nullptr || requireKind(*Root, yaml::NodeKind::Mapping, "machine function") || + parseFunction(*Root, Staging)) { + return std::move(Result); + } + } + + auto UnitResult = Staging.Unit.verify(); + if (!UnitResult) { + static_cast( + fail(Stream->documents().back().Range, "parsed compilation unit failed verification: " + + std::string(UnitResult.error().message()))); + return std::move(Result); + } + ActiveDocument = nullptr; + Result.Document = std::move(Staging); + return std::move(Result); +} + +bool MIRDocumentParser::parseFunction(const yaml::Node &Root, MIRDocument &Document) { + constexpr std::array Keys{ + std::string_view{"name"}, + std::string_view{"alignment"}, + std::string_view{"exposesReturnsTwice"}, + std::string_view{"legalized"}, + std::string_view{"regBankSelected"}, + std::string_view{"selected"}, + std::string_view{"failedISel"}, + std::string_view{"tracksRegLiveness"}, + std::string_view{"hasWinCFI"}, + std::string_view{"noPhis"}, + std::string_view{"isSSA"}, + std::string_view{"noVRegs"}, + std::string_view{"hasFakeUses"}, + std::string_view{"callsEHReturn"}, + std::string_view{"callsUnwindInit"}, + std::string_view{"hasEHContTarget"}, + std::string_view{"hasEHScopes"}, + std::string_view{"hasEHFunclets"}, + std::string_view{"isOutlined"}, + std::string_view{"debugInstrRef"}, + std::string_view{"failsVerification"}, + std::string_view{"tracksDebugUserValues"}, + std::string_view{"registers"}, + std::string_view{"liveins"}, + std::string_view{"calleeSavedRegisters"}, + std::string_view{"frameInfo"}, + std::string_view{"fixedStack"}, + std::string_view{"stack"}, + std::string_view{"entry_values"}, + std::string_view{"callSites"}, + std::string_view{"debugValueSubstitutions"}, + std::string_view{"constants"}, + std::string_view{"machineFunctionInfo"}, + std::string_view{"jumpTable"}, + std::string_view{"machineMetadataNodes"}, + std::string_view{"calledGlobals"}, + std::string_view{"prefetch-targets"}, + std::string_view{"body"}, + }; + if (validateKeys(Root, Keys, "machine function")) + return true; + const yaml::Node *NameNode = mappingValue(Root, "name"); + if (NameNode == nullptr) + return fail(Root.range(), "machine function requires 'name'"); + std::string_view Name; + if (scalar(*NameNode, Name, "machine function name") || Name.empty()) + return fail(NameNode->range(), "machine function name cannot be empty"); + + if (Document.EmbeddedIR) { + const EmbeddedIRFunction *IRFunction = Document.EmbeddedIR->function(Name); + if (IRFunction == nullptr) + return fail(NameNode->range(), + "machine function has no corresponding embedded LLVM IR function"); + } + + auto Created = Document.Unit.createMachineFunction(std::string(Name)); + if (!Created) + return importError(NameNode->range(), Created.error()); + MachineFunction &Function = **Created; + ActiveFunction = &Function; + + MIRFunctionDocumentInfo DocumentInfo{ + .Function = Function.stableID(), + .NoPHIs = std::nullopt, + .IsSSA = std::nullopt, + .NoVRegs = std::nullopt, + .HasFakeUses = std::nullopt, + }; + MIParserContext Context; + Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits; + + std::vector PendingJumpTables; + if (parseFunctionScalars(Root, Function, DocumentInfo) || + parseMetadata(Root, Function, Context) || parseRegisters(Root, Function, Context) || + parseStackObjects(Root, Function, Context) || parseConstants(Root, Function, Context) || + reserveJumpTables(Root, Function, Context, PendingJumpTables) || + parseBody(Root, Function, Context) || defineJumpTables(Function, PendingJumpTables) || + parseFrameState(Root, Function, Context) || + recomputeProperties(Root, Function, DocumentInfo) || parseTargetState(Root, Function) || + parseFunctionRecords(Root, Function) || validateDocumentReferences(Function)) { + return true; + } + + if (Options.VerifyMachineFunctions) { + auto Verified = MachineVerifier::verify(Function, Target); + if (!Verified) + return importError(Root.range(), Verified.error(), "mir-verifier"); + } + Document.FunctionInfo.push_back(DocumentInfo); + ActiveFunction = nullptr; + return false; +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRDocumentParser.h b/backend/lib/MIR/MIRDocumentParser.h new file mode 100644 index 0000000..53a31b4 --- /dev/null +++ b/backend/lib/MIR/MIRDocumentParser.h @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// NOLINTNEXTLINE(llvm-header-guard) +#ifndef RCC_LIB_MIR_MIRDOCUMENTPARSER_H +#define RCC_LIB_MIR_MIRDOCUMENTPARSER_H + +#include "rcc/CodeGen/MachineDebugInfo.h" +#include "rcc/CodeGen/MachineFrameInfo.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" +#include "rcc/CodeGen/MachineMetadata.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/MIR/LLVMConstantSyntax.h" +#include "rcc/MIR/MIRDocument.h" + +#include +#include +#include +#include +#include +#include + +namespace rcc { + +class MachineBasicBlock; +class MachineFunction; +class MachineInstr; + +namespace mir { + +struct PendingJumpTable final { + JumpTableIndex Index; + std::uint32_t TextID = 0; + JumpTableEntryEncoding Encoding = JumpTableEntryEncoding::BlockAddress; + std::uint8_t EntrySize = 8; + std::vector Blocks; +}; + +class MIRDocumentParser final { +public: + MIRDocumentParser(const TargetDescription &Target, std::string SourceName, std::string SourceText, + MIRDocumentParserOptions Options) + : Target(Target), Options(std::move(Options)), + Result(std::move(SourceName), std::move(SourceText)) {} + + [[nodiscard]] MIRDocumentParseResult run(); + +private: + [[nodiscard]] bool fail(const TextRange &Range, std::string Message, + std::string Category = "mir-yaml"); + [[nodiscard]] bool importError(const TextRange &Range, const Error &Failure, + std::string Category = "mir-yaml"); + [[nodiscard]] const yaml::Node *node(yaml::NodeID ID) const noexcept { + return Stream->node(ID); + } + [[nodiscard]] const yaml::Node *mappingValue(const yaml::Node &Mapping, + std::string_view Key) const noexcept; + [[nodiscard]] bool requireKind(const yaml::Node &Node, yaml::NodeKind Kind, + std::string_view Description); + [[nodiscard]] bool validateKeys(const yaml::Node &Mapping, std::span Keys, + std::string_view Description); + [[nodiscard]] bool scalar(const yaml::Node &Node, std::string_view &Value, + std::string_view Description); + [[nodiscard]] bool boolean(const yaml::Node &Node, bool &Value); + [[nodiscard]] bool unsignedValue(const yaml::Node &Node, std::uint64_t &Value, + std::string_view Description); + [[nodiscard]] bool signedValue(const yaml::Node &Node, std::int64_t &Value, + std::string_view Description); + [[nodiscard]] bool alignment(const yaml::Node &Node, Alignment &Value, bool AllowZero); + [[nodiscard]] bool physicalRegister(const yaml::Node &Node, PhysicalRegister &Reg); + [[nodiscard]] bool physicalRegister(std::string_view Text, const TextRange &Range, + PhysicalRegister &Reg); + [[nodiscard]] bool virtualRegister(std::string_view Text, const TextRange &Range, + const MIParserContext &Context, Register &Reg); + [[nodiscard]] Expected + resolveMetadataReference(const MachineFunction &Function, std::uint32_t Slot) const; + [[nodiscard]] bool parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function, + MachineVariableDebugInfo &DebugInfo); + [[nodiscard]] bool parseFunction(const yaml::Node &Root, MIRDocument &Document); + [[nodiscard]] bool parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function, + MIRFunctionDocumentInfo &DocumentInfo); + [[nodiscard]] bool recomputeProperties(const yaml::Node &Root, MachineFunction &Function, + const MIRFunctionDocumentInfo &DocumentInfo); + [[nodiscard]] bool parseRegisters(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] bool parseMetadata(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] bool parseStackObjects(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] bool parseConstants(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] bool parseFrameState(const yaml::Node &Root, MachineFunction &Function, + const MIParserContext &Context); + [[nodiscard]] bool reserveJumpTables(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context, + std::vector &Pending); + [[nodiscard]] bool defineJumpTables(MachineFunction &Function, + std::span Pending); + [[nodiscard]] bool parseBody(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context); + [[nodiscard]] TextRange bodyDiagnosticRange(const yaml::Node &Body, + const TextRange &BodyRange) const noexcept; + [[nodiscard]] bool parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function); + [[nodiscard]] bool parseTargetState(const yaml::Node &Root, MachineFunction &Function); + [[nodiscard]] bool convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination); + [[nodiscard]] bool validateDocumentReferences(const MachineFunction &Function); + [[nodiscard]] std::string_view effectiveDataLayout() const noexcept; + [[nodiscard]] LLVMConstantSyntaxOptions pointerLayoutOptions() const noexcept; + + [[nodiscard]] MachineBasicBlock *blockAt(MachineFunction &Function, + std::uint32_t Number) const noexcept; + [[nodiscard]] MachineInstr *instructionAt(MachineFunction &Function, std::uint32_t Block, + std::uint32_t Offset) const noexcept; + [[nodiscard]] bool parseBlockReference(std::string_view Text, const TextRange &Range, + MachineFunction &Function, MachineBasicBlock *&Block); + [[nodiscard]] bool parseFrameReference(const yaml::Node &Node, const MIParserContext &Context, + FrameIndex &Index); + [[nodiscard]] bool parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function, + std::vector &Points); + + const TargetDescription &Target; + MIRDocumentParserOptions Options; + MIRDocumentParseResult Result; + const yaml::Stream *Stream = nullptr; + MIRDocument *ActiveDocument = nullptr; + MachineFunction *ActiveFunction = nullptr; +}; + +} // namespace mir +} // namespace rcc + +#endif // RCC_LIB_MIR_MIRDOCUMENTPARSER_H diff --git a/backend/lib/MIR/MIRDocumentReferenceValidation.cpp b/backend/lib/MIR/MIRDocumentReferenceValidation.cpp new file mode 100644 index 0000000..a6dbb56 --- /dev/null +++ b/backend/lib/MIR/MIRDocumentReferenceValidation.cpp @@ -0,0 +1,410 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MIRDocumentReferenceValidation.h" + +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineConstantPool.h" +#include "rcc/CodeGen/MachineFrameInfo.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineFunctionRecords.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMetadata.h" +#include "rcc/MIR/MIRDocument.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +Expected verifyMIRDocumentReferences(const MIRDocument &Document, + const MachineFunction &Function, bool UseEmbeddedIR) { + const EmbeddedIRReferenceIndex *Embedded = UseEmbeddedIR ? Document.embeddedIR() : nullptr; + const EmbeddedIRFunction *IRFunction = nullptr; + if (Embedded != nullptr) { + IRFunction = Embedded->function(Function.name()); + if (IRFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "machine function has no matching embedded LLVM IR function"); + } + } + + const auto ValidateGlobal = [Embedded](std::string_view Name, + std::string_view Description) -> Expected { + if (Embedded == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " requires embedded LLVM IR to resolve global '" + + std::string(Name) + "'"); + } + if (Embedded->hasGlobalValue(Name)) + return {}; + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " references an undefined embedded LLVM IR global '" + + std::string(Name) + "'"); + }; + const auto ValidateBlockAddress = [Embedded](std::string_view FunctionName, + std::string_view BlockName, + std::string_view Description) -> Expected { + if (Embedded == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " requires embedded LLVM IR to resolve a block address"); + } + const EmbeddedIRFunction *AddressFunction = Embedded->function(FunctionName); + if (AddressFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " references an undefined embedded LLVM IR function '" + + std::string(FunctionName) + "'"); + } + if (AddressFunction->block(BlockName) == nullptr) { + return makeUnexpected( + ErrorCode::VerificationError, + std::string(Description) + " references an undefined embedded LLVM IR block '" + + std::string(BlockName) + "' in function '" + std::string(FunctionName) + "'"); + } + return {}; + }; + const auto ValidateIRValue = [IRFunction](std::string_view Name, + std::string_view Description) -> Expected { + if (IRFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " requires embedded LLVM IR to resolve IR value '%" + + std::string(Name) + "'"); + } + if (IRFunction->value(Name) != nullptr) + return {}; + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " references an undefined embedded LLVM IR value '%" + + std::string(Name) + "'"); + }; + const auto ValidateMetadata = [Embedded](MachineMetadataReference Reference, + std::initializer_list ExpectedTypes, + std::string_view Description) -> Expected { + if (!Reference.isValid()) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + " has an invalid metadata reference"); + } + if (Reference.scope() != MachineMetadataScope::EmbeddedIR) + return {}; + if (Embedded == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + " requires embedded LLVM IR metadata !" + + std::to_string(Reference.slot())); + } + const EmbeddedIRMetadataDefinition *Definition = + Embedded->metadata(std::to_string(Reference.slot())); + if (Definition == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + + " references undefined embedded LLVM IR metadata !" + + std::to_string(Reference.slot())); + } + if (ExpectedTypes.size() == 0 || + std::ranges::find(ExpectedTypes, Definition->DynamicType) != ExpectedTypes.end()) { + return {}; + } + std::string Expected; + for (const std::string_view Type : ExpectedTypes) { + if (!Expected.empty()) + Expected.append(" or "); + Expected.append(Type); + } + return makeUnexpected(ErrorCode::VerificationError, + std::string(Description) + " requires " + Expected + " metadata, but !" + + std::to_string(Reference.slot()) + " is " + + (Definition->DynamicType.empty() + ? std::string("an unclassified metadata node") + : Definition->DynamicType)); + }; + const auto ValidateMetadataValue = + [&ValidateMetadata](const MachineMetadataValue &Value, + std::string_view Description) -> Expected { + const MachineMetadataReference *Reference = Value.reference(); + return Reference == nullptr ? Expected{} : ValidateMetadata(*Reference, {}, Description); + }; + const auto ValidateLocation = + [&ValidateMetadata](const MachineDebugLocation &Location, + std::string_view Description) -> Expected { + if (const MachineMetadataReference *Reference = Location.reference()) + return ValidateMetadata(*Reference, {"DILocation"}, Description); + const MachineDILocation *Inline = Location.inlineLocation(); + if (Inline == nullptr) + return {}; + for (const MachineDILocationFrame &Frame : Inline->inlineChain()) { + auto Verification = + ValidateMetadata(Frame.Scope, {"DISubprogram", "DILexicalBlock", "DILexicalBlockFile"}, + "inline DILocation scope"); + if (!Verification) + return Verification; + } + if (Inline->terminalInlinedAt()) { + return ValidateMetadata(*Inline->terminalInlinedAt(), {"DILocation"}, + "inline DILocation inlinedAt"); + } + return {}; + }; + const auto ValidateVariableDebugInfo = + [&ValidateMetadata, &ValidateMetadataValue, + &ValidateLocation](const MachineVariableDebugInfo &DebugInfo) -> Expected { + if (DebugInfo.Variable) { + auto Verification = + ValidateMetadata(*DebugInfo.Variable, {"DILocalVariable"}, "debug variable"); + if (!Verification) + return Verification; + } + if (DebugInfo.Expression) { + if (const MachineMetadataReference *Reference = DebugInfo.Expression->reference(); + Reference != nullptr) { + auto Verification = ValidateMetadata(*Reference, {"DIExpression"}, "debug expression"); + if (!Verification) + return Verification; + } + auto Verification = ValidateMetadataValue(*DebugInfo.Expression, "debug expression"); + if (!Verification) + return Verification; + } + if (DebugInfo.Location) + return ValidateLocation(*DebugInfo.Location, "debug location"); + return {}; + }; + const auto ValidateGenericType = [Embedded](const auto &Self, + const GenericConstantType &Type) -> Expected { + if (Type.Kind == GenericConstantTypeKind::Named) { + if (Embedded == nullptr) { + return makeUnexpected( + ErrorCode::VerificationError, + "generic constant type requires embedded LLVM IR to resolve named type '%" + Type.Name + + "'"); + } + if (Embedded->typeDefinition(Type.Name) == nullptr) { + return makeUnexpected( + ErrorCode::VerificationError, + "generic constant type references an undefined embedded LLVM IR named type '%" + + Type.Name + "'"); + } + } + for (const GenericConstantType &Element : Type.Elements) { + auto Verification = Self(Self, Element); + if (!Verification) + return Verification; + } + return {}; + }; + const auto ValidateGenericConstant = + [&ValidateBlockAddress, &ValidateGenericType, + &ValidateGlobal](const auto &Self, const GenericConstantData &Constant) -> Expected { + auto TypeVerification = ValidateGenericType(ValidateGenericType, Constant.Type); + if (!TypeVerification) + return TypeVerification; + if (Constant.SourceElementType) { + TypeVerification = ValidateGenericType(ValidateGenericType, *Constant.SourceElementType); + if (!TypeVerification) + return TypeVerification; + } + switch (Constant.Kind) { + case GenericConstantKind::GlobalAddress: + case GenericConstantKind::DSOLocalEquivalent: + case GenericConstantKind::NoCFI: + if (auto Verification = ValidateGlobal(Constant.Symbol, "generic constant"); !Verification) + return Verification; + break; + case GenericConstantKind::BlockAddress: + if (auto Verification = ValidateBlockAddress(Constant.Symbol, Constant.Block, + "generic constant blockaddress"); + !Verification) { + return Verification; + } + break; + default: + break; + } + for (const GenericConstantData &Element : Constant.Elements) { + auto Verification = Self(Self, Element); + if (!Verification) + return Verification; + } + return {}; + }; + + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + if (const auto Name = Block->attributes().irBlockName(); Name) { + if (IRFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "machine basic block requires embedded LLVM IR to resolve block '" + + std::string(*Name) + "'"); + } + if (IRFunction->block(*Name) == nullptr) { + return makeUnexpected( + ErrorCode::VerificationError, + "machine basic block references an undefined embedded LLVM IR block '" + + std::string(*Name) + "'"); + } + } + } + for (const StackObject &Object : Function.frameInfo().objects()) { + const StackObjectDesc *Description = Object.description(); + if (Description == nullptr || !Description->AllocaName) + continue; + if (IRFunction == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "stack object requires embedded LLVM IR to resolve alloca '" + + *Description->AllocaName + "'"); + } + if (IRFunction->alloca(*Description->AllocaName) == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "stack object references an undefined embedded LLVM IR alloca '" + + *Description->AllocaName + "'"); + } + } + + for (const MachineConstantPoolEntry &Entry : Function.constantPool().entries()) { + const MachineConstantPoolEntryDesc *Description = Entry.description(); + if (Description == nullptr) + continue; + if (const auto *Generic = std::get_if(&Description->Value)) { + auto Verification = ValidateGenericConstant(ValidateGenericConstant, *Generic); + if (!Verification) + return Verification; + } else if (const auto *Address = std::get_if(&Description->Value)) { + auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName, + "constant-pool block address"); + if (!Verification) + return Verification; + } + } + + for (const MachineMetadataNode &Node : Function.metadata().nodes()) { + for (const MachineMetadataElement &Element : Node.Elements) { + const MachineMetadataReference *Reference = Element.reference(); + if (Reference == nullptr) + continue; + auto Verification = ValidateMetadata(*Reference, {}, "machine metadata node"); + if (!Verification) + return Verification; + } + } + + for (const StackObject &Object : Function.frameInfo().objects()) { + const StackObjectDesc *Description = Object.description(); + if (Description == nullptr || !Description->DebugInfo) + continue; + auto Verification = ValidateVariableDebugInfo(*Description->DebugInfo); + if (!Verification) + return Verification; + } + for (const MachineEntryValueInfo &Entry : Function.records().entryValues()) { + auto Verification = ValidateVariableDebugInfo(Entry.DebugInfo); + if (!Verification) + return Verification; + } + for (const MachineCalledGlobal &Called : Function.records().calledGlobals()) { + auto Verification = ValidateGlobal(Called.Callee, "called-global record"); + if (!Verification) + return Verification; + } + + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (Instruction->debugLocation()) { + auto Verification = + ValidateLocation(*Instruction->debugLocation(), "instruction debug location"); + if (!Verification) + return Verification; + } + const std::array InstructionMetadata{ + Instruction->heapAllocMarker(), + Instruction->pcSections(), + Instruction->mmra(), + }; + for (const auto &Reference : InstructionMetadata) { + if (!Reference) + continue; + auto Verification = ValidateMetadata(*Reference, {}, "instruction metadata attachment"); + if (!Verification) + return Verification; + } + + for (const MachineOperand &Operand : Instruction->operands()) { + if (const auto *Global = Operand.getIf(); Global != nullptr) { + auto Verification = ValidateGlobal(Global->Name, "global-symbol operand"); + if (!Verification) + return Verification; + } + if (const auto *Address = Operand.getIf(); Address != nullptr) { + auto Verification = ValidateBlockAddress(Address->FunctionName, Address->BlockName, + "block-address operand"); + if (!Verification) + return Verification; + } + if (const auto *Metadata = Operand.getIf(); Metadata != nullptr) { + auto Verification = ValidateMetadataValue(Metadata->Value, "metadata operand"); + if (!Verification) + return Verification; + } + } + + for (const MachineMemOperand *Memory : Instruction->memOperands()) { + const MachineMemOperandDesc &Description = Memory->description(); + const std::optional BaseName = Description.Base.symbolName(); + switch (Description.Base.kind()) { + case MemoryBase::Kind::GlobalSymbol: + case MemoryBase::Kind::CallEntryGlobal: + if (BaseName) { + auto Verification = ValidateGlobal(*BaseName, "machine memory base"); + if (!Verification) + return Verification; + } + break; + case MemoryBase::Kind::IRValue: + if (BaseName) { + auto Verification = ValidateIRValue(*BaseName, "machine memory base"); + if (!Verification) + return Verification; + } + break; + default: + break; + } + const std::array MetadataReferences{ + Description.TBAA, Description.AliasScope, + Description.NoAlias, Description.NoAliasAddressSpace, + Description.RangeMetadata, + }; + for (const auto &Reference : MetadataReferences) { + if (!Reference) + continue; + auto Verification = ValidateMetadata(*Reference, {}, "machine memory operand metadata"); + if (!Verification) + return Verification; + } + if (Description.SyncScope.value() == 0) + continue; + const auto Scope = + std::ranges::find_if(Document.syncScopes(), [&Description](const auto &Slot) { + return Slot.ID == Description.SyncScope; + }); + if (Scope == Document.syncScopes().end()) { + return makeUnexpected( + ErrorCode::VerificationError, + "machine memory operand synchronization scope has no document name"); + } + } + } + } + return {}; +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRFrameParser.cpp b/backend/lib/MIR/MIRFrameParser.cpp new file mode 100644 index 0000000..76ace8f --- /dev/null +++ b/backend/lib/MIR/MIRFrameParser.cpp @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MIRDocumentParser.h" +#include "MIRDocumentReferenceValidation.h" +#include "MIRMetadataTextParser.h" +#include "MIRParserUtilities.h" +#include "rcc/CodeGen/MachineBasicBlock.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/MachineVerifier.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/MIR/LLVMConstantSyntax.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +using parser_detail::parseBool; +using parser_detail::parsePrefixedID; +using parser_detail::parseSigned; +using parser_detail::parseUnsigned; +using parser_detail::parseUnsigned32; +using parser_detail::trim; + +bool MIRDocumentParser::parseStackObjects(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + auto ParseList = [this, &Function, &Context](const yaml::Node &List, bool Fixed) -> bool { + if (requireKind(List, yaml::NodeKind::Sequence, + Fixed ? "fixed stack objects" : "stack objects")) { + return true; + } + constexpr std::array FixedKeys{ + std::string_view{"id"}, + std::string_view{"type"}, + std::string_view{"offset"}, + std::string_view{"size"}, + std::string_view{"alignment"}, + std::string_view{"stack-id"}, + std::string_view{"isImmutable"}, + std::string_view{"isAliased"}, + std::string_view{"callee-saved-register"}, + std::string_view{"callee-saved-restored"}, + std::string_view{"debug-info-variable"}, + std::string_view{"debug-info-expression"}, + std::string_view{"debug-info-location"}, + }; + constexpr std::array StackKeys{ + std::string_view{"id"}, + std::string_view{"name"}, + std::string_view{"type"}, + std::string_view{"offset"}, + std::string_view{"size"}, + std::string_view{"alignment"}, + std::string_view{"stack-id"}, + std::string_view{"callee-saved-register"}, + std::string_view{"callee-saved-restored"}, + std::string_view{"local-offset"}, + std::string_view{"debug-info-variable"}, + std::string_view{"debug-info-expression"}, + std::string_view{"debug-info-location"}, + }; + std::unordered_set IDs; + for (const yaml::NodeID ElementID : List.sequence()) { + const yaml::Node *Element = node(ElementID); + if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "stack object") || + (Fixed ? validateKeys(*Element, FixedKeys, "fixed stack object") + : validateKeys(*Element, StackKeys, "stack object"))) { + return true; + } + const yaml::Node *IDNode = mappingValue(*Element, "id"); + if (IDNode == nullptr) + return fail(Element->range(), "stack object requires 'id'"); + std::uint64_t WideID = 0; + if (unsignedValue(*IDNode, WideID, "a 32-bit stack object ID") || + WideID > std::numeric_limits::max()) { + return true; + } + const auto TextID = static_cast(WideID); + if (!IDs.insert(TextID).second) + return fail(IDNode->range(), "duplicate stack object ID"); + + StackObjectDesc Description; + Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default; + Description.Offset = 0; + if (const yaml::Node *NameNode = mappingValue(*Element, "name")) { + std::string_view Name; + if (scalar(*NameNode, Name, "stack object name") || Name.empty()) + return fail(NameNode->range(), "stack object name cannot be empty"); + Description.AllocaName = std::string(Name); + } + if (const yaml::Node *TypeNode = mappingValue(*Element, "type")) { + std::string_view Type; + if (scalar(*TypeNode, Type, "stack object type")) + return true; + if (Type == "default") + Description.Kind = Fixed ? StackObjectKind::FixedABI : StackObjectKind::Default; + else if (Type == "spill-slot") + Description.Kind = Fixed ? StackObjectKind::FixedSpillSlot : StackObjectKind::SpillSlot; + else if (!Fixed && Type == "variable-sized") + Description.Kind = StackObjectKind::VariableSized; + else + return fail(TypeNode->range(), "unknown stack object type '" + std::string(Type) + "'"); + } + if (const yaml::Node *OffsetNode = mappingValue(*Element, "offset")) { + std::int64_t Offset = 0; + if (signedValue(*OffsetNode, Offset, "a signed stack object offset")) + return true; + Description.Offset = Offset; + } else if (Fixed) { + Description.Offset = 0; + } + if (Description.Kind != StackObjectKind::VariableSized) { + const yaml::Node *SizeNode = mappingValue(*Element, "size"); + if (SizeNode == nullptr) { + if (!Fixed) + return fail(Element->range(), "fixed-size stack object requires 'size'"); + Description.Size = 0; + } else { + std::uint64_t Size = 0; + if (unsignedValue(*SizeNode, Size, "an unsigned stack object size")) + return true; + Description.Size = Size; + } + } + if (const yaml::Node *AlignmentNode = mappingValue(*Element, "alignment")) { + if (alignment(*AlignmentNode, Description.Align, true)) + return true; + } + if (const yaml::Node *StackIDNode = mappingValue(*Element, "stack-id")) { + std::string_view Name; + if (scalar(*StackIDNode, Name, "stack ID")) + return true; + const auto Stack = parseStackIDName(Name); + if (!Stack) + return fail(StackIDNode->range(), "unknown stack ID '" + std::string(Name) + "'"); + Description.Stack = *Stack; + } + if (const yaml::Node *ImmutableNode = mappingValue(*Element, "isImmutable")) { + if (boolean(*ImmutableNode, Description.IsImmutable)) + return true; + } + if (const yaml::Node *AliasedNode = mappingValue(*Element, "isAliased")) { + if (boolean(*AliasedNode, Description.IsAliased)) + return true; + } + if (const yaml::Node *SavedNode = mappingValue(*Element, "callee-saved-register")) { + PhysicalRegister Reg; + if (physicalRegister(*SavedNode, Reg)) + return true; + Description.CalleeSavedRegister = Reg; + } + if (const yaml::Node *RestoredNode = mappingValue(*Element, "callee-saved-restored")) { + if (boolean(*RestoredNode, Description.CalleeSavedRestored)) + return true; + } + if (const yaml::Node *LocalNode = mappingValue(*Element, "local-offset")) { + std::int64_t Offset = 0; + if (signedValue(*LocalNode, Offset, "a signed local frame offset")) + return true; + Description.LocalOffset = Offset; + } + MachineVariableDebugInfo DebugInfo; + if (parseVariableDebugInfo(*Element, Function, DebugInfo)) + return true; + if (DebugInfo.Variable || DebugInfo.Expression || DebugInfo.Location) + Description.DebugInfo = std::move(DebugInfo); + + auto Created = Function.frameInfo().createObject(std::move(Description)); + if (!Created) + return importError(Element->range(), Created.error()); + MIFrameObjectSlot Slot{ + .TextID = TextID, + .Index = *Created, + .Name = std::nullopt, + }; + if (const StackObjectDesc *CreatedDescription = + Function.frameInfo().object(*Created)->description(); + CreatedDescription != nullptr && CreatedDescription->AllocaName) { + Slot.Name = *CreatedDescription->AllocaName; + } + (Fixed ? Context.FixedStackObjects : Context.StackObjects).push_back(std::move(Slot)); + } + return false; + }; + + if (const yaml::Node *Fixed = mappingValue(Root, "fixedStack")) { + if (ParseList(*Fixed, true)) + return true; + } + if (const yaml::Node *Stack = mappingValue(Root, "stack")) { + if (ParseList(*Stack, false)) + return true; + } + return false; +} + +bool MIRDocumentParser::parseFrameReference(const yaml::Node &Node, const MIParserContext &Context, + FrameIndex &Index) { + std::string_view Text; + if (scalar(Node, Text, "frame index")) + return true; + bool Fixed = false; + std::optional ID = parsePrefixedID(Text, "%stack."); + if (!ID) { + ID = parsePrefixedID(Text, "%fixed-stack."); + Fixed = true; + } + if (!ID) + return fail(Node.range(), "frame index must use '%stack.N' or '%fixed-stack.N'"); + const auto &Slots = Fixed ? Context.FixedStackObjects : Context.StackObjects; + const auto Found = std::ranges::find_if( + Slots, [ID](const MIFrameObjectSlot &Slot) { return Slot.TextID == *ID; }); + if (Found == Slots.end()) + return fail(Node.range(), "reference to undefined frame object"); + Index = Found->Index; + return false; +} + +MachineBasicBlock *MIRDocumentParser::blockAt(MachineFunction &Function, + std::uint32_t Number) const noexcept { + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) { + if (Block->denseNumber() == Number) + return Block; + } + return nullptr; +} + +MachineInstr *MIRDocumentParser::instructionAt(MachineFunction &Function, std::uint32_t BlockNumber, + std::uint32_t Offset) const noexcept { + MachineBasicBlock *Block = blockAt(Function, BlockNumber); + if (Block == nullptr) + return nullptr; + MachineInstr *Instruction = Block->firstInstruction(); + for (std::uint32_t Index = 0; Instruction != nullptr && Index < Offset; ++Index) + Instruction = Instruction->next(); + return Instruction; +} + +bool MIRDocumentParser::parseBlockReference(std::string_view Text, const TextRange &Range, + MachineFunction &Function, MachineBasicBlock *&Block) { + const auto Number = parsePrefixedID(Text, "%bb."); + if (!Number) + return fail(Range, "basic block reference must use the '%bb.N' MIR spelling"); + Block = blockAt(Function, *Number); + if (Block == nullptr) + return fail(Range, "reference to undefined machine basic block"); + return false; +} + +bool MIRDocumentParser::parseSaveRestorePoints(const yaml::Node &Node, MachineFunction &Function, + std::vector &Points) { + if (requireKind(Node, yaml::NodeKind::Sequence, "frame save/restore points")) + return true; + constexpr std::array Keys{ + std::string_view{"point"}, + std::string_view{"registers"}, + }; + for (const yaml::NodeID PointID : Node.sequence()) { + const yaml::Node *PointNode = node(PointID); + if (PointNode == nullptr || + requireKind(*PointNode, yaml::NodeKind::Mapping, "frame save/restore point") || + validateKeys(*PointNode, Keys, "frame save/restore point")) { + return true; + } + const yaml::Node *BlockNode = mappingValue(*PointNode, "point"); + if (BlockNode == nullptr) + return fail(PointNode->range(), "frame save/restore point requires 'point'"); + std::string_view BlockText; + MachineBasicBlock *Block = nullptr; + if (scalar(*BlockNode, BlockText, "basic block reference") || + parseBlockReference(BlockText, BlockNode->range(), Function, Block)) { + return true; + } + if (Block == nullptr) + return fail(BlockNode->range(), "reference to undefined machine basic block"); + MachineFrameSaveRestorePoint Point{.Block = Block->stableID(), .Registers = {}}; + if (const yaml::Node *RegistersNode = mappingValue(*PointNode, "registers")) { + if (requireKind(*RegistersNode, yaml::NodeKind::Sequence, "save/restore point registers")) { + return true; + } + for (const yaml::NodeID RegID : RegistersNode->sequence()) { + const yaml::Node *RegNode = node(RegID); + PhysicalRegister Reg; + if (RegNode == nullptr || physicalRegister(*RegNode, Reg)) + return true; + Point.Registers.push_back(Reg); + } + } + Points.push_back(std::move(Point)); + } + return false; +} + +bool MIRDocumentParser::parseFrameState(const yaml::Node &Root, MachineFunction &Function, + const MIParserContext &Context) { + const yaml::Node *Frame = mappingValue(Root, "frameInfo"); + if (Frame == nullptr) + return false; + if (requireKind(*Frame, yaml::NodeKind::Mapping, "frameInfo")) + return true; + constexpr std::array Keys{ + std::string_view{"isFrameAddressTaken"}, + std::string_view{"isReturnAddressTaken"}, + std::string_view{"hasStackMap"}, + std::string_view{"hasPatchPoint"}, + std::string_view{"stackSize"}, + std::string_view{"offsetAdjustment"}, + std::string_view{"maxAlignment"}, + std::string_view{"adjustsStack"}, + std::string_view{"hasCalls"}, + std::string_view{"framePointerPolicy"}, + std::string_view{"stackProtector"}, + std::string_view{"functionContext"}, + std::string_view{"maxCallFrameSize"}, + std::string_view{"cvBytesOfCalleeSavedRegisters"}, + std::string_view{"hasOpaqueSPAdjustment"}, + std::string_view{"hasVAStart"}, + std::string_view{"hasMustTailInVarArgFunc"}, + std::string_view{"hasTailCall"}, + std::string_view{"isCalleeSavedInfoValid"}, + std::string_view{"localFrameSize"}, + std::string_view{"savePoint"}, + std::string_view{"restorePoint"}, + }; + if (validateKeys(*Frame, Keys, "frameInfo")) + return true; + + MachineFrameState State = Function.frameInfo().state(); + const std::array BoolBindings{ + std::pair{"isFrameAddressTaken", &State.IsFrameAddressTaken}, + std::pair{"isReturnAddressTaken", &State.IsReturnAddressTaken}, + std::pair{"hasStackMap", &State.HasStackMap}, + std::pair{"hasPatchPoint", &State.HasPatchPoint}, + std::pair{"adjustsStack", &State.AdjustsStack}, + std::pair{"hasCalls", &State.HasCalls}, + std::pair{"hasOpaqueSPAdjustment", &State.HasOpaqueSPAdjustment}, + std::pair{"hasVAStart", &State.HasVAStart}, + std::pair{"hasMustTailInVarArgFunc", &State.HasMustTailInVarArgFunc}, + std::pair{"hasTailCall", &State.HasTailCall}, + std::pair{"isCalleeSavedInfoValid", &State.IsCalleeSavedInfoValid}, + }; + for (const auto &[Key, Destination] : BoolBindings) { + if (const yaml::Node *Value = mappingValue(*Frame, Key)) { + if (boolean(*Value, *Destination)) + return true; + } + } + + if (const yaml::Node *Value = mappingValue(*Frame, "stackSize")) { + if (unsignedValue(*Value, State.StackSize, "an unsigned stack size")) + return true; + } + if (const yaml::Node *Value = mappingValue(*Frame, "offsetAdjustment")) { + std::int64_t Wide = 0; + if (signedValue(*Value, Wide, "a 32-bit signed offset adjustment") || + Wide < std::numeric_limits::min() || + Wide > std::numeric_limits::max()) { + return fail(Value->range(), "offset adjustment is outside the 32-bit signed range"); + } + State.OffsetAdjustment = static_cast(Wide); + } + if (const yaml::Node *Value = mappingValue(*Frame, "maxAlignment")) { + std::uint64_t Bytes = 0; + if (unsignedValue(*Value, Bytes, "an unsigned maximum alignment")) + return true; + if (Bytes == 0) { + State.MaxAlignment.reset(); + } else { + auto Parsed = Alignment::create(Bytes); + if (!Parsed) + return importError(Value->range(), Parsed.error()); + State.MaxAlignment = *Parsed; + } + } + if (const yaml::Node *Value = mappingValue(*Frame, "framePointerPolicy")) { + std::string_view Name; + if (scalar(*Value, Name, "frame-pointer policy")) + return true; + const auto Policy = parseMachineFramePointerPolicy(Name); + if (!Policy) + return fail(Value->range(), "unknown frame-pointer policy '" + std::string(Name) + "'"); + State.FramePointerPolicy = *Policy; + } + for (const auto &[Key, Destination] : {std::pair{"stackProtector", &State.StackProtector}, + std::pair{"functionContext", &State.FunctionContext}}) { + if (const yaml::Node *Value = mappingValue(*Frame, Key)) { + FrameIndex Index; + if (parseFrameReference(*Value, Context, Index)) + return true; + *Destination = Index; + } + } + if (const yaml::Node *Value = mappingValue(*Frame, "maxCallFrameSize")) { + std::uint64_t Size = 0; + if (unsignedValue(*Value, Size, "a 32-bit unsigned maximum call-frame size") || + Size > std::numeric_limits::max()) { + return fail(Value->range(), "maximum call-frame size is outside the 32-bit unsigned range"); + } + State.MaxCallFrameSize = Size; + } + for (const auto &[Key, Destination] : + {std::pair{"cvBytesOfCalleeSavedRegisters", &State.CVBytesOfCalleeSavedRegisters}, + std::pair{"localFrameSize", &State.LocalFrameSize}}) { + if (const yaml::Node *Value = mappingValue(*Frame, Key)) { + std::uint64_t Wide = 0; + if (unsignedValue(*Value, Wide, "a 32-bit unsigned frame value") || + Wide > std::numeric_limits::max()) { + return fail(Value->range(), "frame value is outside the 32-bit unsigned range"); + } + *Destination = static_cast(Wide); + } + } + if (const yaml::Node *Value = mappingValue(*Frame, "savePoint")) { + if (parseSaveRestorePoints(*Value, Function, State.SavePoints)) + return true; + } + if (const yaml::Node *Value = mappingValue(*Frame, "restorePoint")) { + if (parseSaveRestorePoints(*Value, Function, State.RestorePoints)) + return true; + } + auto StateResult = Function.frameInfo().setState(std::move(State)); + if (!StateResult) + return importError(Frame->range(), StateResult.error()); + return false; +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRFunctionParser.cpp b/backend/lib/MIR/MIRFunctionParser.cpp new file mode 100644 index 0000000..07cb2b0 --- /dev/null +++ b/backend/lib/MIR/MIRFunctionParser.cpp @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MIRDocumentParser.h" +#include "MIRDocumentReferenceValidation.h" +#include "MIRMetadataTextParser.h" +#include "MIRParserUtilities.h" +#include "rcc/CodeGen/MachineBasicBlock.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/MachineVerifier.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/MIR/LLVMConstantSyntax.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +using parser_detail::parseBool; +using parser_detail::parsePrefixedID; +using parser_detail::parseSigned; +using parser_detail::parseUnsigned; +using parser_detail::parseUnsigned32; +using parser_detail::trim; + +bool MIRDocumentParser::parseFunctionScalars(const yaml::Node &Root, MachineFunction &Function, + MIRFunctionDocumentInfo &DocumentInfo) { + if (const yaml::Node *AlignNode = mappingValue(Root, "alignment")) { + Alignment Align; + if (alignment(*AlignNode, Align, true)) + return true; + if (Align.bytes() != 1 || AlignNode->scalar() != "0") + Function.attributes().setAlignment(Align); + } + + constexpr std::array PropertyBindings{ + std::pair{"legalized", MachineFunctionProperty::Legalized}, + std::pair{"regBankSelected", MachineFunctionProperty::RegBankSelected}, + std::pair{"selected", MachineFunctionProperty::Selected}, + std::pair{"failedISel", MachineFunctionProperty::FailedISel}, + std::pair{"tracksRegLiveness", MachineFunctionProperty::TracksLiveness}, + std::pair{"noPhis", MachineFunctionProperty::NoPHIs}, + std::pair{"isSSA", MachineFunctionProperty::IsSSA}, + std::pair{"noVRegs", MachineFunctionProperty::NoVRegs}, + std::pair{"failsVerification", MachineFunctionProperty::FailsVerification}, + }; + for (const auto &[Key, Property] : PropertyBindings) { + const yaml::Node *ValueNode = mappingValue(Root, Key); + if (ValueNode == nullptr) + continue; + bool Value = false; + if (boolean(*ValueNode, Value)) + return true; + if (Value) + Function.properties().set(Property); + else + Function.properties().clear(Property); + if (Key == std::string_view("noPhis")) + DocumentInfo.NoPHIs = Value; + else if (Key == std::string_view("isSSA")) + DocumentInfo.IsSSA = Value; + else if (Key == std::string_view("noVRegs")) + DocumentInfo.NoVRegs = Value; + } + + constexpr std::array AttributeBindings{ + std::pair{"exposesReturnsTwice", MachineFunctionAttribute::ExposesReturnsTwice}, + std::pair{"hasWinCFI", MachineFunctionAttribute::HasWinCFI}, + std::pair{"hasFakeUses", MachineFunctionAttribute::HasFakeUses}, + std::pair{"callsEHReturn", MachineFunctionAttribute::CallsEHReturn}, + std::pair{"callsUnwindInit", MachineFunctionAttribute::CallsUnwindInit}, + std::pair{"hasEHContTarget", MachineFunctionAttribute::HasEHContTarget}, + std::pair{"hasEHScopes", MachineFunctionAttribute::HasEHScopes}, + std::pair{"hasEHFunclets", MachineFunctionAttribute::HasEHFunclets}, + std::pair{"isOutlined", MachineFunctionAttribute::IsOutlined}, + std::pair{"debugInstrRef", MachineFunctionAttribute::DebugInstrRef}, + std::pair{"tracksDebugUserValues", MachineFunctionAttribute::TracksDebugUserValues}, + }; + for (const auto &[Key, Attribute] : AttributeBindings) { + const yaml::Node *ValueNode = mappingValue(Root, Key); + if (ValueNode == nullptr) + continue; + bool Value = false; + if (boolean(*ValueNode, Value)) + return true; + if (Value) + Function.attributes().set(Attribute); + else + Function.attributes().clear(Attribute); + if (Key == std::string_view("hasFakeUses")) + DocumentInfo.HasFakeUses = Value; + } + + return false; +} + +bool MIRDocumentParser::recomputeProperties(const yaml::Node &Root, MachineFunction &Function, + const MIRFunctionDocumentInfo &DocumentInfo) { + bool HasPHI = false; + bool HasFakeUse = false; + bool IsSSA = true; + bool HasTiedOperands = false; + bool AllTiedOperandsRewritten = true; + bool HasFrameIndex = false; + bool HasExecutablePseudo = false; + std::unordered_set Definitions; + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); + if (Descriptor == nullptr) + return fail(Root.range(), "instruction has no target descriptor"); + HasPHI |= Descriptor->Flags.has(InstructionFlag::Phi); + HasFakeUse |= Descriptor->Flags.has(InstructionFlag::FakeUse); + HasExecutablePseudo |= Descriptor->Flags.has(InstructionFlag::Pseudo) && + !Descriptor->Flags.has(InstructionFlag::Meta) && + !Descriptor->Flags.has(InstructionFlag::TerminalEmission); + const auto Operands = Instruction->operands(); + for (std::size_t Index = 0; Index < Operands.size(); ++Index) { + const MachineOperand &Operand = Operands[Index]; + HasFrameIndex |= Operand.getIf() != nullptr; + const auto Reg = Operand.registerValue(); + if (Reg && Reg->isVirtual() && Operand.isDef() && !Definitions.insert(*Reg).second) { + IsSSA = false; + } + if (!Operand.tiedOperandIndex() || !Operand.isUse()) + continue; + HasTiedOperands = true; + const MachineOperand *Definition = Instruction->operandAt(*Operand.tiedOperandIndex()); + if (Definition == nullptr || Definition->registerValue() != Reg) + AllTiedOperandsRewritten = false; + } + for (const MachineMemOperand *Memory : Instruction->memOperands()) + HasFrameIndex |= Memory->description().Base.frameIndex().has_value(); + } + } + const bool NoPHIs = !HasPHI; + const bool NoVRegs = + std::ranges::none_of(Function.registerInfo().virtualRegisters(), + [](const VirtualRegisterInfo &Info) { return !Info.IsTombstone; }); + + const auto ApplyComputed = [this, &Root, &Function](std::optional Explicit, bool Computed, + MachineFunctionProperty Property, + std::string_view Name) -> bool { + if (Explicit && *Explicit && !Computed) { + return fail(Root.range(), "explicit property '" + std::string(Name) + + "' conflicts with the parsed machine function"); + } + if (Explicit.value_or(Computed)) + Function.properties().set(Property); + else + Function.properties().clear(Property); + return false; + }; + if (ApplyComputed(DocumentInfo.NoPHIs, NoPHIs, MachineFunctionProperty::NoPHIs, "noPhis") || + ApplyComputed(DocumentInfo.IsSSA, IsSSA, MachineFunctionProperty::IsSSA, "isSSA") || + ApplyComputed(DocumentInfo.NoVRegs, NoVRegs, MachineFunctionProperty::NoVRegs, "noVRegs")) { + return true; + } + + if (DocumentInfo.HasFakeUses && !*DocumentInfo.HasFakeUses && HasFakeUse) { + return fail(Root.range(), + "explicit property 'hasFakeUses: false' conflicts with a fake-use instruction"); + } + if (DocumentInfo.HasFakeUses.value_or(HasFakeUse)) + Function.attributes().set(MachineFunctionAttribute::HasFakeUses); + else + Function.attributes().clear(MachineFunctionAttribute::HasFakeUses); + + const bool Selected = Function.properties().has(MachineFunctionProperty::Selected); + const bool StructurallyPostSSA = + Selected && NoPHIs && !Function.properties().has(MachineFunctionProperty::IsSSA); + if (AllTiedOperandsRewritten && (HasTiedOperands || StructurallyPostSSA)) + Function.properties().set(MachineFunctionProperty::TiedOpsRewritten); + else + Function.properties().clear(MachineFunctionProperty::TiedOpsRewritten); + + if (HasFrameIndex) + Function.properties().clear(MachineFunctionProperty::NoFrameIndices); + else + Function.properties().set(MachineFunctionProperty::NoFrameIndices); + + const MachineFrameState &FrameState = Function.frameInfo().state(); + const bool StructurallyFinalizedFrame = Selected && NoVRegs && !HasFrameIndex && + FrameState.IsCalleeSavedInfoValid && + FrameState.MaxAlignment.has_value(); + if (StructurallyFinalizedFrame) { + if (!Function.frameInfo().isFrozen()) { + auto Frozen = Function.frameInfo().freeze(FrameState.StackSize, *FrameState.MaxAlignment); + if (!Frozen) + return importError(Root.range(), Frozen.error(), "mir-frame-finalization"); + } + Function.properties().set(MachineFunctionProperty::FrameFinalized); + } else { + Function.properties().clear(MachineFunctionProperty::FrameFinalized); + } + + if (StructurallyFinalizedFrame && !HasExecutablePseudo) + Function.properties().set(MachineFunctionProperty::NoExecutablePseudos); + else + Function.properties().clear(MachineFunctionProperty::NoExecutablePseudos); + return false; +} + +bool MIRDocumentParser::parseRegisters(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + const yaml::Node *Registers = mappingValue(Root, "registers"); + if (Registers != nullptr) { + if (requireKind(*Registers, yaml::NodeKind::Sequence, "registers")) + return true; + constexpr std::array RegisterKeys{ + std::string_view{"id"}, + std::string_view{"class"}, + std::string_view{"preferred-register"}, + std::string_view{"flags"}, + }; + std::unordered_set IDs; + for (const yaml::NodeID ElementID : Registers->sequence()) { + const yaml::Node *Element = node(ElementID); + if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "register entry") || + validateKeys(*Element, RegisterKeys, "register")) { + return true; + } + const yaml::Node *IDNode = mappingValue(*Element, "id"); + const yaml::Node *ClassNode = mappingValue(*Element, "class"); + if (IDNode == nullptr || ClassNode == nullptr) + return fail(Element->range(), "register entry requires 'id' and 'class'"); + std::uint64_t WideID = 0; + std::string_view ClassName; + if (unsignedValue(*IDNode, WideID, "a 32-bit virtual-register ID") || + WideID > std::numeric_limits::max() || + scalar(*ClassNode, ClassName, "register class or bank")) { + return true; + } + const auto TextID = static_cast(WideID); + if (!IDs.insert(TextID).second) + return fail(IDNode->range(), "duplicate virtual register ID"); + auto Created = Function.registerInfo().createVirtualRegister(); + if (!Created) + return importError(IDNode->range(), Created.error()); + Context.VirtualRegisters.push_back({TextID, *Created}); + + if (ClassName != "_") { + if (const RegisterClassDesc *Class = Target.registers().registerClass(ClassName)) { + auto SetResult = Function.registerInfo().setRegisterClass(*Created, Class->ID); + if (!SetResult) + return importError(ClassNode->range(), SetResult.error()); + SetResult = Function.registerInfo().setRegisterBank(*Created, Class->DefaultBank); + if (!SetResult) + return importError(ClassNode->range(), SetResult.error()); + } else if (const RegisterBankDesc *Bank = Target.registers().registerBank(ClassName)) { + auto SetResult = Function.registerInfo().setRegisterBank(*Created, Bank->ID); + if (!SetResult) + return importError(ClassNode->range(), SetResult.error()); + } else { + return fail(ClassNode->range(), + "unknown register class or bank '" + std::string(ClassName) + "'"); + } + } + + if (const yaml::Node *Preferred = mappingValue(*Element, "preferred-register")) { + PhysicalRegister Reg; + if (physicalRegister(*Preferred, Reg)) + return true; + auto AddResult = Function.registerInfo().addAllocationHint(*Created, Reg); + if (!AddResult) + return importError(Preferred->range(), AddResult.error()); + } + if (const yaml::Node *Flags = mappingValue(*Element, "flags")) { + if (requireKind(*Flags, yaml::NodeKind::Sequence, "virtual-register flags")) + return true; + std::uint8_t Mask = 0; + for (const yaml::NodeID FlagID : Flags->sequence()) { + const yaml::Node *FlagNode = node(FlagID); + std::string_view Name; + if (FlagNode == nullptr || scalar(*FlagNode, Name, "virtual-register flag")) + return true; + const auto Flag = Target.mirText().virtualRegisterFlag(Name); + if (!Flag) + return fail(FlagNode->range(), + "unknown virtual-register flag '" + std::string(Name) + "'"); + if ((Mask & *Flag) != 0) + return fail(FlagNode->range(), "duplicate virtual-register flag"); + Mask = static_cast(Mask | *Flag); + } + auto SetResult = Function.registerInfo().setTargetRegisterFlags(*Created, Mask); + if (!SetResult) + return importError(Flags->range(), SetResult.error()); + } + } + } + + const yaml::Node *LiveIns = mappingValue(Root, "liveins"); + if (LiveIns != nullptr) { + if (requireKind(*LiveIns, yaml::NodeKind::Sequence, "liveins")) + return true; + constexpr std::array LiveInKeys{ + std::string_view{"reg"}, + std::string_view{"virtual-reg"}, + }; + for (const yaml::NodeID ElementID : LiveIns->sequence()) { + const yaml::Node *Element = node(ElementID); + if (Element == nullptr || requireKind(*Element, yaml::NodeKind::Mapping, "live-in entry") || + validateKeys(*Element, LiveInKeys, "live-in")) { + return true; + } + const yaml::Node *RegNode = mappingValue(*Element, "reg"); + if (RegNode == nullptr) + return fail(Element->range(), "live-in entry requires 'reg'"); + PhysicalRegister Reg; + if (physicalRegister(*RegNode, Reg)) + return true; + std::optional Initial; + if (const yaml::Node *VirtualNode = mappingValue(*Element, "virtual-reg")) { + std::string_view Text; + Register Virtual; + if (scalar(*VirtualNode, Text, "virtual register") || + virtualRegister(Text, VirtualNode->range(), Context, Virtual)) { + return true; + } + Initial = Virtual; + } + auto AddResult = Function.registerInfo().addLiveIn(Reg, Initial); + if (!AddResult) + return importError(Element->range(), AddResult.error()); + } + } + + if (const yaml::Node *CalleeSaved = mappingValue(Root, "calleeSavedRegisters")) { + if (requireKind(*CalleeSaved, yaml::NodeKind::Sequence, "callee-saved registers")) + return true; + std::vector RegistersValue; + for (const yaml::NodeID RegID : CalleeSaved->sequence()) { + const yaml::Node *RegNode = node(RegID); + PhysicalRegister Reg; + if (RegNode == nullptr || physicalRegister(*RegNode, Reg)) + return true; + RegistersValue.push_back(Reg); + } + auto SetResult = Function.records().setCalleeSavedRegisters(std::move(RegistersValue)); + if (!SetResult) + return importError(CalleeSaved->range(), SetResult.error()); + } + return false; +} + +TextRange MIRDocumentParser::bodyDiagnosticRange(const yaml::Node &Body, + const TextRange &BodyRange) const noexcept { + if (Body.scalarStyle() != yaml::ScalarStyle::Literal) + return Body.range(); + + const std::size_t FirstBodyLine = Body.range().Begin.Line + 1U; + const std::size_t LastBodyLine = Body.range().End.Line; + if (FirstBodyLine > LastBodyLine || FirstBodyLine == 0) + return Body.range(); + + std::size_t Indent = 0; + bool FoundContent = false; + for (std::size_t Line = FirstBodyLine; Line <= LastBodyLine; ++Line) { + const std::string_view Text = Result.Source.line(Line); + std::size_t CurrentIndent = 0; + while (CurrentIndent < Text.size() && Text[CurrentIndent] == ' ') + ++CurrentIndent; + if (CurrentIndent == Text.size()) + continue; + Indent = CurrentIndent; + FoundContent = true; + break; + } + if (!FoundContent) + return Body.range(); + + const TextPosition Fallback = Body.range().Begin; + const auto MapPosition = [this, FirstBodyLine, LastBodyLine, Indent, + Fallback](TextPosition Position) { + const std::size_t SourceLine = FirstBodyLine + Position.Line - 1U; + if (Position.Line == 0 || SourceLine > LastBodyLine || SourceLine > Result.Source.lineCount()) { + return Fallback; + } + const std::string_view SourceLineText = Result.Source.line(SourceLine); + const std::size_t BodyColumn = Position.Column == 0 ? 0 : Position.Column - 1U; + const std::size_t SourceColumn = std::min(Indent + BodyColumn, SourceLineText.size()); + const std::size_t Offset = Result.Source.lineOffsets()[SourceLine - 1U] + SourceColumn; + return Result.Source.positionAt(Offset); + }; + return {.Begin = MapPosition(BodyRange.Begin), .End = MapPosition(BodyRange.End)}; +} + +bool MIRDocumentParser::parseBody(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + const yaml::Node *Body = mappingValue(Root, "body"); + std::string BodyText; + TextRange BodyRange = Root.range(); + if (Body != nullptr) { + std::string_view Text; + if (scalar(*Body, Text, "machine function body")) + return true; + BodyText = Text; + BodyRange = Body->range(); + } + + if (Options.DefaultPointerSizeInBits != 0) + Context.DefaultPointerSizeInBits = Options.DefaultPointerSizeInBits; + if (!effectiveDataLayout().empty()) { + const LLVMConstantSyntaxOptions PointerOptions = pointerLayoutOptions(); + Context.PointerSizeInBits = [PointerOptions](std::uint32_t AddressSpace) { + return llvmPointerSizeInBits(AddressSpace, PointerOptions); + }; + } + Context.SyncScopes = ActiveDocument->SyncScopes; + Context.InternSyncScope = [this](std::string_view Name) -> Expected { + if (Name.empty()) + return makeUnexpected(ErrorCode::ParseError, "synchronization scope name cannot be empty"); + const auto Existing = + std::ranges::find_if(ActiveDocument->SyncScopes, + [Name](const MISyncScopeSlot &Slot) { return Slot.Name == Name; }); + if (Existing != ActiveDocument->SyncScopes.end()) + return Existing->ID; + if (ActiveDocument->SyncScopes.size() >= + static_cast(std::numeric_limits::max())) { + return makeUnexpected(ErrorCode::Unsupported, + "MIR document contains too many synchronization scopes"); + } + const auto ID = + SyncScopeID::fromValue(static_cast(ActiveDocument->SyncScopes.size() + 1U)); + ActiveDocument->SyncScopes.push_back({.Name = std::string(Name), .ID = ID}); + return ID; + }; + MIParserOptions BodyOptions = Options.MachineInstructions; + BodyOptions.VerifyAfterParse = false; + MIParser Parser(Function, Target, + std::string(Result.Source.name()) + ":" + std::string(Function.name()), + std::move(BodyText), Context, BodyOptions); + auto Parsed = Parser.parse(); + if (!Parsed) { + if (!Parser.diagnostics().empty()) { + const TextRange Range = Body == nullptr + ? BodyRange + : bodyDiagnosticRange(*Body, Parser.diagnostics().front().Range); + return fail(Range, Parser.diagnostics().front().Message, "mir-body"); + } + return importError(BodyRange, Parsed.error(), "mir-body"); + } + return false; +} + +bool MIRDocumentParser::convertTargetNode(const yaml::Node &Source, MIRTargetNode &Destination) { + switch (Source.kind()) { + case yaml::NodeKind::Null: + Destination = MIRTargetNode::null(); + return false; + case yaml::NodeKind::Scalar: + Destination = MIRTargetNode::scalar(std::string(Source.scalar())); + return false; + case yaml::NodeKind::Sequence: { + std::vector Values; + Values.reserve(Source.sequence().size()); + for (const yaml::NodeID ElementID : Source.sequence()) { + const yaml::Node *Element = node(ElementID); + if (Element == nullptr) + return fail(Source.range(), "target MIR sequence contains an invalid node"); + MIRTargetNode Value; + if (convertTargetNode(*Element, Value)) + return true; + Values.push_back(std::move(Value)); + } + Destination = MIRTargetNode::sequence(std::move(Values)); + return false; + } + case yaml::NodeKind::Mapping: { + std::vector Entries; + Entries.reserve(Source.mapping().size()); + for (const yaml::MappingEntry &SourceEntry : Source.mapping()) { + const yaml::Node *ValueNode = node(SourceEntry.Value); + if (ValueNode == nullptr) + return fail(SourceEntry.KeyRange, "target MIR mapping contains an invalid node"); + MIRTargetNode Value; + if (convertTargetNode(*ValueNode, Value)) + return true; + Entries.push_back({SourceEntry.Key, std::move(Value)}); + } + Destination = MIRTargetNode::mapping(std::move(Entries)); + return false; + } + } + return fail(Source.range(), "target MIR node has an unknown kind"); +} + +bool MIRDocumentParser::parseTargetState(const yaml::Node &Root, MachineFunction &Function) { + const yaml::Node *StateNode = mappingValue(Root, "machineFunctionInfo"); + if (StateNode == nullptr) + return false; + MIRTargetNode TargetNode; + if (convertTargetNode(*StateNode, TargetNode)) + return true; + auto State = Target.mirText().parseFunctionState(TargetNode); + if (!State) + return importError(StateNode->range(), State.error(), "mir-target-state"); + auto SetResult = Function.setTargetState(std::move(*State)); + if (!SetResult) + return importError(StateNode->range(), SetResult.error(), "mir-target-state"); + return false; +} + +bool MIRDocumentParser::validateDocumentReferences(const MachineFunction &Function) { + auto Verification = verifyMIRDocumentReferences(*ActiveDocument, Function, true); + if (Verification) + return false; + return importError(Result.Source.range(0, 0), Verification.error()); +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRFunctionRecordsParser.cpp b/backend/lib/MIR/MIRFunctionRecordsParser.cpp new file mode 100644 index 0000000..dcd486e --- /dev/null +++ b/backend/lib/MIR/MIRFunctionRecordsParser.cpp @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MIRDocumentParser.h" +#include "MIRDocumentReferenceValidation.h" +#include "MIRMetadataTextParser.h" +#include "MIRParserUtilities.h" +#include "rcc/CodeGen/MachineBasicBlock.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/MachineVerifier.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/MIR/LLVMConstantSyntax.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +using parser_detail::parseBool; +using parser_detail::parsePrefixedID; +using parser_detail::parseSigned; +using parser_detail::parseUnsigned; +using parser_detail::parseUnsigned32; +using parser_detail::trim; + +bool MIRDocumentParser::parseFunctionRecords(const yaml::Node &Root, MachineFunction &Function) { + auto ParseLocation = [this, &Function](const yaml::Node &Entry, + MachineInstr *&Instruction) -> bool { + const yaml::Node *BlockNode = mappingValue(Entry, "bb"); + const yaml::Node *OffsetNode = mappingValue(Entry, "offset"); + if (BlockNode == nullptr || OffsetNode == nullptr) + return fail(Entry.range(), "instruction location requires 'bb' and 'offset'"); + std::uint64_t Block = 0; + std::uint64_t Offset = 0; + if (unsignedValue(*BlockNode, Block, "a 32-bit basic block number") || + unsignedValue(*OffsetNode, Offset, "a 32-bit instruction offset") || + Block > std::numeric_limits::max() || + Offset > std::numeric_limits::max()) { + return true; + } + Instruction = instructionAt(Function, static_cast(Block), + static_cast(Offset)); + if (Instruction == nullptr) + return fail(Entry.range(), "instruction location is outside the machine function body"); + return false; + }; + + if (const yaml::Node *CallSites = mappingValue(Root, "callSites")) { + if (requireKind(*CallSites, yaml::NodeKind::Sequence, "call-site records")) + return true; + constexpr std::array CallSiteKeys{ + std::string_view{"bb"}, + std::string_view{"offset"}, + std::string_view{"fwdArgRegs"}, + std::string_view{"calleeTypeIds"}, + }; + constexpr std::array ArgumentKeys{ + std::string_view{"arg"}, + std::string_view{"reg"}, + }; + for (const yaml::NodeID CallSiteID : CallSites->sequence()) { + const yaml::Node *CallSiteNode = node(CallSiteID); + if (CallSiteNode == nullptr || + requireKind(*CallSiteNode, yaml::NodeKind::Mapping, "call-site record") || + validateKeys(*CallSiteNode, CallSiteKeys, "call-site record")) { + return true; + } + MachineInstr *Call = nullptr; + if (ParseLocation(*CallSiteNode, Call)) + return true; + if (Call == nullptr) + return fail(CallSiteNode->range(), + "instruction location is outside the machine function body"); + MachineCallSiteInfo Info{ + .CallInstruction = Call->stableID(), + .ForwardedArguments = {}, + .CalleeTypeIDs = {}, + }; + if (const yaml::Node *Arguments = mappingValue(*CallSiteNode, "fwdArgRegs")) { + if (requireKind(*Arguments, yaml::NodeKind::Sequence, "forwarded call arguments")) { + return true; + } + for (const yaml::NodeID ArgumentID : Arguments->sequence()) { + const yaml::Node *Argument = node(ArgumentID); + if (Argument == nullptr || + requireKind(*Argument, yaml::NodeKind::Mapping, "forwarded call argument") || + validateKeys(*Argument, ArgumentKeys, "forwarded call argument")) { + return true; + } + const yaml::Node *NumberNode = mappingValue(*Argument, "arg"); + const yaml::Node *RegNode = mappingValue(*Argument, "reg"); + if (NumberNode == nullptr || RegNode == nullptr) + return fail(Argument->range(), "forwarded call argument requires 'arg' and 'reg'"); + std::uint16_t Number = 0; + std::uint64_t WideNumber = 0; + PhysicalRegister Reg; + if (unsignedValue(*NumberNode, WideNumber, "a 16-bit argument number") || + WideNumber > std::numeric_limits::max() || + physicalRegister(*RegNode, Reg)) { + return true; + } + Number = static_cast(WideNumber); + Info.ForwardedArguments.push_back({Reg, Number}); + } + } + if (const yaml::Node *TypeIDs = mappingValue(*CallSiteNode, "calleeTypeIds")) { + if (requireKind(*TypeIDs, yaml::NodeKind::Sequence, "callee type IDs")) + return true; + for (const yaml::NodeID TypeID : TypeIDs->sequence()) { + const yaml::Node *TypeNode = node(TypeID); + std::uint64_t Value = 0; + if (TypeNode == nullptr || + unsignedValue(*TypeNode, Value, "an unsigned callee type ID")) { + return true; + } + Info.CalleeTypeIDs.push_back(Value); + } + } + auto AddResult = Function.records().addCallSite(std::move(Info)); + if (!AddResult) + return importError(CallSiteNode->range(), AddResult.error()); + } + } + + if (const yaml::Node *CalledGlobals = mappingValue(Root, "calledGlobals")) { + if (requireKind(*CalledGlobals, yaml::NodeKind::Sequence, "called-global records")) + return true; + constexpr std::array Keys{ + std::string_view{"bb"}, + std::string_view{"offset"}, + std::string_view{"callee"}, + std::string_view{"flags"}, + }; + for (const yaml::NodeID CalledID : CalledGlobals->sequence()) { + const yaml::Node *CalledNode = node(CalledID); + if (CalledNode == nullptr || + requireKind(*CalledNode, yaml::NodeKind::Mapping, "called-global record") || + validateKeys(*CalledNode, Keys, "called-global record")) { + return true; + } + MachineInstr *Call = nullptr; + if (ParseLocation(*CalledNode, Call)) + return true; + if (Call == nullptr) + return fail(CalledNode->range(), + "instruction location is outside the machine function body"); + const yaml::Node *CalleeNode = mappingValue(*CalledNode, "callee"); + const yaml::Node *FlagsNode = mappingValue(*CalledNode, "flags"); + if (CalleeNode == nullptr || FlagsNode == nullptr) + return fail(CalledNode->range(), "called-global record requires 'callee' and 'flags'"); + std::string_view Callee; + std::uint64_t WideFlags = 0; + if (scalar(*CalleeNode, Callee, "callee name") || + unsignedValue(*FlagsNode, WideFlags, "32-bit called-global flags") || + WideFlags > std::numeric_limits::max()) { + return true; + } + if (ActiveDocument->EmbeddedIR && !ActiveDocument->EmbeddedIR->hasGlobalValue(Callee)) { + return fail(CalleeNode->range(), "called global references an undefined embedded global"); + } + auto AddResult = + Function.records().addCalledGlobal({.CallInstruction = Call->stableID(), + .Callee = std::string(Callee), + .Flags = static_cast(WideFlags)}); + if (!AddResult) + return importError(CalledNode->range(), AddResult.error()); + } + } + + if (const yaml::Node *Substitutions = mappingValue(Root, "debugValueSubstitutions")) { + if (requireKind(*Substitutions, yaml::NodeKind::Sequence, "debug value substitutions")) { + return true; + } + constexpr std::array Keys{ + std::string_view{"srcinst"}, std::string_view{"srcop"}, std::string_view{"dstinst"}, + std::string_view{"dstop"}, std::string_view{"subreg"}, + }; + for (const yaml::NodeID SubstitutionID : Substitutions->sequence()) { + const yaml::Node *Substitution = node(SubstitutionID); + if (Substitution == nullptr || + requireKind(*Substitution, yaml::NodeKind::Mapping, "debug value substitution") || + validateKeys(*Substitution, Keys, "debug value substitution")) { + return true; + } + std::array Values{}; + for (std::size_t Index = 0; Index < Keys.size(); ++Index) { + const yaml::Node *ValueNode = mappingValue(*Substitution, Keys[Index]); + if (ValueNode == nullptr) + return fail(Substitution->range(), "debug value substitution requires every fixed field"); + std::uint64_t Wide = 0; + if (unsignedValue(*ValueNode, Wide, "a 32-bit debug substitution value") || + Wide > std::numeric_limits::max()) { + return true; + } + Values[Index] = static_cast(Wide); + } + auto AddResult = Function.records().addDebugValueSubstitution( + {.Source = {Values[0], Values[1]}, + .Destination = {Values[2], Values[3]}, + .Subregister = SubRegIndex::fromValue(Values[4])}); + if (!AddResult) + return importError(Substitution->range(), AddResult.error()); + } + } + + if (const yaml::Node *PrefetchTargets = mappingValue(Root, "prefetch-targets")) { + if (requireKind(*PrefetchTargets, yaml::NodeKind::Sequence, "prefetch targets")) + return true; + for (const yaml::NodeID TargetID : PrefetchTargets->sequence()) { + const yaml::Node *TargetNode = node(TargetID); + std::string_view Text; + if (TargetNode == nullptr || scalar(*TargetNode, Text, "prefetch target")) + return true; + Text = trim(Text); + if (!Text.starts_with("bb_id")) + return fail(TargetNode->range(), "prefetch target must start with 'bb_id'"); + Text = trim(Text.substr(std::string_view("bb_id").size())); + std::array Values{}; + for (std::size_t Index = 0; Index < Values.size(); ++Index) { + const std::size_t Comma = Text.find(','); + const std::string_view Part = Index + 1 == Values.size() ? Text : Text.substr(0, Comma); + if ((Index + 1 != Values.size() && Comma == std::string_view::npos) || + !parseUnsigned32(trim(Part), Values[Index])) { + return fail(TargetNode->range(), "prefetch target has invalid numeric fields"); + } + if (Index + 1 != Values.size()) + Text = trim(Text.substr(Comma + 1)); + } + auto AddResult = Function.records().addPrefetchTarget( + {.Block = {Values[0], Values[1]}, .CallsiteIndex = Values[2]}); + if (!AddResult) + return importError(TargetNode->range(), AddResult.error()); + } + } + + if (const yaml::Node *EntryValues = mappingValue(Root, "entry_values")) { + if (requireKind(*EntryValues, yaml::NodeKind::Sequence, "entry values")) + return true; + constexpr std::array Keys{ + std::string_view{"entry-value-register"}, + std::string_view{"debug-info-variable"}, + std::string_view{"debug-info-expression"}, + std::string_view{"debug-info-location"}, + }; + for (const yaml::NodeID EntryID : EntryValues->sequence()) { + const yaml::Node *Entry = node(EntryID); + if (Entry == nullptr || requireKind(*Entry, yaml::NodeKind::Mapping, "entry value") || + validateKeys(*Entry, Keys, "entry value")) { + return true; + } + const yaml::Node *RegNode = mappingValue(*Entry, "entry-value-register"); + const yaml::Node *VariableNode = mappingValue(*Entry, "debug-info-variable"); + const yaml::Node *ExpressionNode = mappingValue(*Entry, "debug-info-expression"); + const yaml::Node *LocationNode = mappingValue(*Entry, "debug-info-location"); + if (RegNode == nullptr || VariableNode == nullptr || ExpressionNode == nullptr || + LocationNode == nullptr) { + return fail(Entry->range(), "entry value requires all four fixed fields"); + } + PhysicalRegister Reg; + if (physicalRegister(*RegNode, Reg)) + return true; + MachineVariableDebugInfo DebugInfo; + if (parseVariableDebugInfo(*Entry, Function, DebugInfo)) + return true; + if (!DebugInfo.Variable || !DebugInfo.Expression || !DebugInfo.Location) { + return fail(Entry->range(), "entry value debug metadata cannot be empty", + "mir-debug-metadata"); + } + auto AddResult = + Function.records().addEntryValue({.Reg = Reg, .DebugInfo = std::move(DebugInfo)}); + if (!AddResult) + return importError(Entry->range(), AddResult.error()); + } + } + return false; +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRMetadataParser.cpp b/backend/lib/MIR/MIRMetadataParser.cpp new file mode 100644 index 0000000..6ce717b --- /dev/null +++ b/backend/lib/MIR/MIRMetadataParser.cpp @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MIRDocumentParser.h" +#include "MIRDocumentReferenceValidation.h" +#include "MIRMetadataTextParser.h" +#include "MIRParserUtilities.h" +#include "rcc/CodeGen/MachineBasicBlock.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/MachineVerifier.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/MIR/LLVMConstantSyntax.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +using parser_detail::parseBool; +using parser_detail::parsePrefixedID; +using parser_detail::parseSigned; +using parser_detail::parseUnsigned; +using parser_detail::parseUnsigned32; +using parser_detail::trim; + +Expected +MIRDocumentParser::resolveMetadataReference(const MachineFunction &Function, + std::uint32_t Slot) const { + if (ActiveDocument != nullptr && ActiveDocument->EmbeddedIR && + ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) { + return MachineMetadataReference::embeddedIR(Slot); + } + if (Function.metadata().node(Slot) != nullptr) + return MachineMetadataReference::machineFunction(Slot); + return makeUnexpected(ErrorCode::ParseError, + "metadata reference uses undefined slot '!" + std::to_string(Slot) + "'"); +} + +bool MIRDocumentParser::parseVariableDebugInfo(const yaml::Node &Entry, MachineFunction &Function, + MachineVariableDebugInfo &DebugInfo) { + const auto Resolve = [this, &Function](std::uint32_t Slot) { + return resolveMetadataReference(Function, Slot); + }; + if (const yaml::Node *VariableNode = mappingValue(Entry, "debug-info-variable")) { + std::string_view Text; + if (scalar(*VariableNode, Text, "debug variable")) + return true; + if (!Text.empty()) { + MetadataTextParser Parser(std::string(Text), Resolve); + auto Reference = Parser.parseReferenceOnly(); + if (!Reference) + return importError(VariableNode->range(), Reference.error(), "mir-debug-metadata"); + if (Reference->scope() != MachineMetadataScope::EmbeddedIR) { + return fail(VariableNode->range(), + "debug variable must reference embedded LLVM IR metadata", + "mir-debug-metadata"); + } + DebugInfo.Variable = *Reference; + } + } + if (const yaml::Node *ExpressionNode = mappingValue(Entry, "debug-info-expression")) { + std::string_view Text; + if (scalar(*ExpressionNode, Text, "debug expression")) + return true; + if (!Text.empty()) { + MetadataTextParser Parser(std::string(Text), Resolve); + auto Value = Parser.parseValue(); + if (!Value) + return importError(ExpressionNode->range(), Value.error(), "mir-debug-metadata"); + if (Value->reference() != nullptr && + Value->reference()->scope() != MachineMetadataScope::EmbeddedIR) { + return fail(ExpressionNode->range(), + "debug expression reference must use embedded LLVM IR metadata", + "mir-debug-metadata"); + } + DebugInfo.Expression = std::move(*Value); + } + } + if (const yaml::Node *LocationNode = mappingValue(Entry, "debug-info-location")) { + std::string_view Text; + if (scalar(*LocationNode, Text, "debug location")) + return true; + if (!Text.empty()) { + MetadataTextParser Parser(std::string(Text), Resolve); + auto Location = Parser.parseLocation(); + if (!Location) + return importError(LocationNode->range(), Location.error(), "mir-debug-metadata"); + if (Location->reference() != nullptr && + Location->reference()->scope() != MachineMetadataScope::EmbeddedIR) { + return fail(LocationNode->range(), + "variable debug location must reference embedded LLVM IR metadata", + "mir-debug-metadata"); + } + DebugInfo.Location = std::move(*Location); + } + } + if (!DebugInfo.Variable && !DebugInfo.Expression && !DebugInfo.Location) + return false; + auto Verification = DebugInfo.verifyStructure(); + if (!Verification) + return importError(Entry.range(), Verification.error(), "mir-debug-metadata"); + return false; +} + +bool MIRDocumentParser::parseMetadata(const yaml::Node &Root, MachineFunction &Function, + MIParserContext &Context) { + if (ActiveDocument->EmbeddedIR) { + for (const EmbeddedIRMetadataDefinition &Reference : + ActiveDocument->EmbeddedIR->metadataDefinitions()) { + std::uint32_t ID = 0; + if (!parseUnsigned32(Reference.Name, ID)) + continue; + if (std::ranges::any_of(Context.Metadata, + [ID](const MIMetadataSlot &Slot) { return Slot.TextID == ID; })) { + return fail(Root.range(), "embedded LLVM IR metadata slots are not unique"); + } + Context.Metadata.push_back( + {.TextID = ID, .Reference = MachineMetadataReference::embeddedIR(ID)}); + } + } + + const yaml::Node *Metadata = mappingValue(Root, "machineMetadataNodes"); + if (Metadata == nullptr) + return false; + if (requireKind(*Metadata, yaml::NodeKind::Sequence, "machine metadata nodes")) + return true; + + struct Definition final { + const yaml::Node *Node = nullptr; + std::uint32_t Slot = 0; + bool IsDistinct = false; + std::string_view Elements; + }; + std::vector Definitions; + std::unordered_set Slots; + + for (const yaml::NodeID DefinitionID : Metadata->sequence()) { + const yaml::Node *DefinitionNode = node(DefinitionID); + std::string_view Text; + if (DefinitionNode == nullptr || scalar(*DefinitionNode, Text, "machine metadata definition")) { + return true; + } + Text = trim(Text); + if (!Text.starts_with('!')) + return fail(DefinitionNode->range(), "machine metadata definition must start with '!N'"); + const std::size_t Equals = Text.find('='); + if (Equals == std::string_view::npos) + return fail(DefinitionNode->range(), "machine metadata definition requires '='"); + std::uint32_t Slot = 0; + if (!parseUnsigned32(trim(Text.substr(1, Equals - 1)), Slot)) + return fail(DefinitionNode->range(), "machine metadata definition has an invalid slot"); + if (!Slots.insert(Slot).second) + return fail(DefinitionNode->range(), "duplicate machine metadata slot"); + std::string_view Right = trim(Text.substr(Equals + 1)); + bool IsDistinct = false; + if (Right.starts_with("distinct")) { + Right = trim(Right.substr(std::string_view("distinct").size())); + IsDistinct = true; + } + if (!Right.starts_with("!{") || !Right.ends_with('}')) + return fail(DefinitionNode->range(), "machine metadata node must be a metadata tuple"); + Definitions.push_back({.Node = DefinitionNode, + .Slot = Slot, + .IsDistinct = IsDistinct, + .Elements = Right.substr(2, Right.size() - 3)}); + Context.Metadata.push_back( + {.TextID = Slot, .Reference = MachineMetadataReference::machineFunction(Slot)}); + } + + auto ResolveReference = [this, + &Slots](std::uint32_t Slot, + const TextRange &Range) -> Expected { + MachineMetadataReference Reference; + if (ActiveDocument->EmbeddedIR != std::nullopt && + ActiveDocument->EmbeddedIR->metadata(std::to_string(Slot)) != nullptr) { + Reference = MachineMetadataReference::embeddedIR(Slot); + } else if (Slots.contains(Slot)) { + Reference = MachineMetadataReference::machineFunction(Slot); + } else { + return makeUnexpected(ErrorCode::ParseError, "metadata tuple references undefined slot '!" + + std::to_string(Slot) + "' at line " + + std::to_string(Range.Begin.Line)); + } + return MachineMetadataElement::reference(Reference); + }; + + for (const Definition &Definition : Definitions) { + MachineMetadataNode NewNode{ + .Slot = Definition.Slot, + .IsDistinct = Definition.IsDistinct, + .Elements = {}, + }; + std::string_view Remaining = trim(Definition.Elements); + while (!Remaining.empty()) { + std::size_t End = 0; + bool InString = false; + bool Escaped = false; + for (; End < Remaining.size(); ++End) { + const char Character = Remaining[End]; + if (InString && Escaped) { + Escaped = false; + continue; + } + if (InString && Character == '\\') { + Escaped = true; + continue; + } + if (Character == '"') { + InString = !InString; + continue; + } + if (!InString && Character == ',') + break; + } + if (InString) + return fail(Definition.Node->range(), "unterminated machine metadata string"); + std::string_view Element = trim(Remaining.substr(0, End)); + if (Element.empty()) + return fail(Definition.Node->range(), "empty machine metadata tuple element"); + + if (Element == "null") { + NewNode.Elements.push_back(MachineMetadataElement::null()); + } else if (Element.starts_with('!') && Element.size() > 1 && Element[1] >= '0' && + Element[1] <= '9') { + std::uint32_t ReferenceSlot = 0; + if (!parseUnsigned32(Element.substr(1), ReferenceSlot)) + return fail(Definition.Node->range(), "invalid metadata tuple reference"); + auto Reference = ResolveReference(ReferenceSlot, Definition.Node->range()); + if (!Reference) + return importError(Definition.Node->range(), Reference.error()); + NewNode.Elements.push_back(std::move(*Reference)); + } else if (Element.starts_with("!\"") && Element.ends_with('"')) { + std::string Value; + std::string_view Encoded = Element.substr(2, Element.size() - 3); + for (std::size_t Index = 0; Index < Encoded.size(); ++Index) { + if (Encoded[Index] == '\\' && Index + 1 < Encoded.size()) { + const char EscapedCharacter = Encoded[++Index]; + if (EscapedCharacter != '\\' && EscapedCharacter != '"') + return fail(Definition.Node->range(), "unsupported machine metadata string escape"); + Value.push_back(EscapedCharacter); + } else { + Value.push_back(Encoded[Index]); + } + } + NewNode.Elements.push_back(MachineMetadataElement::string(std::move(Value))); + } else { + return fail(Definition.Node->range(), "unsupported machine metadata tuple element"); + } + + if (End == Remaining.size()) + Remaining = {}; + else + Remaining = trim(Remaining.substr(End + 1)); + } + auto AddResult = Function.metadata().addNode(std::move(NewNode)); + if (!AddResult) + return importError(Definition.Node->range(), AddResult.error()); + } + return false; +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRMetadataTextParser.cpp b/backend/lib/MIR/MIRMetadataTextParser.cpp new file mode 100644 index 0000000..51b039f --- /dev/null +++ b/backend/lib/MIR/MIRMetadataTextParser.cpp @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "MIRMetadataTextParser.h" + +#include "MIRParserUtilities.h" +#include "rcc/MIR/MIDebugSyntax.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +using parser_detail::parseSigned; +using parser_detail::parseUnsigned; +using parser_detail::parseUnsigned32; + +Expected MetadataTextParser::expect(MITokenKind Kind, std::string_view Description) { + if (consume(Kind)) + return {}; + return makeUnexpected(ErrorCode::ParseError, + "expected " + std::string(Description) + " in MIR metadata scalar"); +} + +Expected MetadataTextParser::finish() { + while (consume(MITokenKind::Newline)) { + } + if (Lexer.failed()) { + if (!Lexer.diagnostics().empty()) { + return makeUnexpected(ErrorCode::ParseError, + std::string(Lexer.diagnostics().front().Message)); + } + return makeUnexpected(ErrorCode::ParseError, "invalid MIR metadata scalar"); + } + if (!at(MITokenKind::EndOfFile)) + return makeUnexpected(ErrorCode::ParseError, "unexpected trailing MIR metadata tokens"); + return {}; +} + +Expected MetadataTextParser::parseReference() { + if (auto Exclaim = expect(MITokenKind::Exclaim, "'!' before a metadata slot"); !Exclaim) + return std::unexpected(Exclaim.error()); + if (!at(MITokenKind::IntegerLiteral)) + return makeUnexpected(ErrorCode::ParseError, "expected a 32-bit metadata slot"); + std::uint32_t Slot = 0; + if (!parseUnsigned32(token().integerValue(), Slot)) + return makeUnexpected(ErrorCode::ParseError, "metadata slot is outside the 32-bit range"); + advance(); + return Resolve(Slot); +} + +Expected MetadataTextParser::parseReferenceOnly() { + auto Reference = parseReference(); + if (!Reference) + return std::unexpected(Reference.error()); + if (auto Finished = finish(); !Finished) + return std::unexpected(Finished.error()); + return Reference; +} + +Expected MetadataTextParser::parseExpression() { + if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DIExpression")) { + return makeUnexpected(ErrorCode::ParseError, "expected an inline !DIExpression"); + } + advance(); + if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DIExpression"); !Left) + return std::unexpected(Left.error()); + std::vector Elements; + while (!at(MITokenKind::RightParen)) { + if (at(MITokenKind::Identifier)) { + const auto Opcode = parseDIExpressionOpcodeName(token().stringValue()); + if (!Opcode) { + return makeUnexpected(ErrorCode::ParseError, "unknown DIExpression opcode '" + + std::string(token().stringValue()) + "'"); + } + Elements.push_back(*Opcode); + advance(); + } else if (at(MITokenKind::IntegerLiteral)) { + std::int64_t Signed = 0; + std::uint64_t Unsigned = 0; + if (parseSigned(token().integerValue(), Signed)) + Elements.push_back(static_cast(Signed)); + else if (parseUnsigned(token().integerValue(), Unsigned)) + Elements.push_back(Unsigned); + else + return makeUnexpected(ErrorCode::ParseError, "DIExpression integer exceeds 64 bits"); + advance(); + } else { + return makeUnexpected(ErrorCode::ParseError, "expected a DIExpression opcode or integer"); + } + if (!consume(MITokenKind::Comma)) + break; + } + if (auto Right = expect(MITokenKind::RightParen, "')' after !DIExpression"); !Right) + return std::unexpected(Right.error()); + return MachineDIExpression(std::move(Elements)); +} + +Expected MetadataTextParser::parseValue() { + MachineMetadataValue Value; + if (at(MITokenKind::Exclaim)) { + auto Reference = parseReference(); + if (!Reference) + return std::unexpected(Reference.error()); + auto Created = MachineMetadataValue::reference(*Reference); + if (!Created) + return std::unexpected(Created.error()); + Value = std::move(*Created); + } else { + auto Expression = parseExpression(); + if (!Expression) + return std::unexpected(Expression.error()); + Value = MachineMetadataValue::expression(std::move(*Expression)); + } + if (auto Finished = finish(); !Finished) + return std::unexpected(Finished.error()); + return Value; +} + +Expected +MetadataTextParser::parseDILocation(std::vector &Frames, + std::optional &Terminal) { + if (!at(MITokenKind::MetadataKeyword) || !token().isMetadataKeyword("!DILocation")) { + return makeUnexpected(ErrorCode::ParseError, "expected an inline !DILocation"); + } + advance(); + if (auto Left = expect(MITokenKind::LeftParen, "'(' after !DILocation"); !Left) + return Left; + + MachineDILocationFrame Frame; + bool HasLine = false; + bool HasScope = false; + std::vector NestedFrames; + std::optional NestedTerminal; + std::unordered_set Seen; + while (!at(MITokenKind::RightParen)) { + if (!at(MITokenKind::Identifier)) + return makeUnexpected(ErrorCode::ParseError, "expected a DILocation field name"); + const std::string Field(token().stringValue()); + if (!Seen.insert(Field).second) { + return makeUnexpected(ErrorCode::ParseError, "duplicate DILocation field '" + Field + "'"); + } + advance(); + if (auto Colon = expect(MITokenKind::Colon, "':' after a DILocation field"); !Colon) + return Colon; + + if (Field == "line") { + std::uint32_t Value = 0; + if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned32(token().integerValue(), Value)) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation line must be an unsigned 32-bit integer"); + } + Frame.Line = Value; + HasLine = true; + advance(); + } else if (Field == "column") { + std::uint64_t Value = 0; + if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) || + Value > std::numeric_limits::max()) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation column must be an unsigned 16-bit integer"); + } + Frame.Column = static_cast(Value); + advance(); + } else if (Field == "scope") { + auto Scope = parseReference(); + if (!Scope) + return std::unexpected(Scope.error()); + if (Scope->scope() != MachineMetadataScope::EmbeddedIR) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation scope must reference embedded LLVM IR metadata"); + } + Frame.Scope = *Scope; + HasScope = true; + } else if (Field == "inlinedAt") { + if (at(MITokenKind::MetadataKeyword) && token().isMetadataKeyword("!DILocation")) { + if (auto Nested = parseDILocation(NestedFrames, NestedTerminal); !Nested) + return Nested; + } else { + auto Reference = parseReference(); + if (!Reference) + return std::unexpected(Reference.error()); + if (Reference->scope() != MachineMetadataScope::EmbeddedIR) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation inlinedAt must reference embedded LLVM IR metadata"); + } + NestedTerminal = *Reference; + } + } else if (Field == "isImplicitCode") { + if (!at(MITokenKind::Identifier) || + (token().stringValue() != "true" && token().stringValue() != "false")) { + return makeUnexpected(ErrorCode::ParseError, + "DILocation isImplicitCode must be true or false"); + } + Frame.IsImplicitCode = token().stringValue() == "true"; + advance(); + } else if (Field == "atomGroup") { + std::uint64_t Value = 0; + if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) || + Value > MachineDILocation::MaxAtomGroup) { + return makeUnexpected(ErrorCode::ParseError, "DILocation atomGroup exceeds 61 bits"); + } + Frame.AtomGroup = Value; + advance(); + } else if (Field == "atomRank") { + std::uint64_t Value = 0; + if (!at(MITokenKind::IntegerLiteral) || !parseUnsigned(token().integerValue(), Value) || + Value > 7) { + return makeUnexpected(ErrorCode::ParseError, "DILocation atomRank exceeds three bits"); + } + Frame.AtomRank = static_cast(Value); + advance(); + } else { + return makeUnexpected(ErrorCode::ParseError, "unknown DILocation field '" + Field + "'"); + } + if (!consume(MITokenKind::Comma)) + break; + } + if (auto Right = expect(MITokenKind::RightParen, "')' after !DILocation"); !Right) + return Right; + if (!HasLine || !HasScope) + return makeUnexpected(ErrorCode::ParseError, "DILocation requires line and scope fields"); + Frames.push_back(Frame); + Frames.insert(Frames.end(), NestedFrames.begin(), NestedFrames.end()); + Terminal = NestedTerminal; + return {}; +} + +Expected MetadataTextParser::parseLocation() { + MachineDebugLocation Location; + if (at(MITokenKind::Exclaim)) { + auto Reference = parseReference(); + if (!Reference) + return std::unexpected(Reference.error()); + auto Created = MachineDebugLocation::reference(*Reference); + if (!Created) + return std::unexpected(Created.error()); + Location = std::move(*Created); + } else { + std::vector Frames; + std::optional Terminal; + if (auto Parsed = parseDILocation(Frames, Terminal); !Parsed) + return std::unexpected(Parsed.error()); + auto Created = MachineDILocation::create(std::move(Frames), Terminal); + if (!Created) + return std::unexpected(Created.error()); + Location = MachineDebugLocation::inlineLocation(std::move(*Created)); + } + if (auto Finished = finish(); !Finished) + return std::unexpected(Finished.error()); + return Location; +} + +} // namespace rcc::mir diff --git a/backend/lib/MIR/MIRMetadataTextParser.h b/backend/lib/MIR/MIRMetadataTextParser.h new file mode 100644 index 0000000..b8ebb12 --- /dev/null +++ b/backend/lib/MIR/MIRMetadataTextParser.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// NOLINTNEXTLINE(llvm-header-guard) +#ifndef RCC_LIB_MIR_MIRMETADATATEXTPARSER_H +#define RCC_LIB_MIR_MIRMETADATATEXTPARSER_H + +#include "rcc/CodeGen/MachineDebugInfo.h" +#include "rcc/CodeGen/MachineMetadata.h" +#include "rcc/MIR/MILexer.h" +#include "rcc/Support/Error.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::mir { + +class MetadataTextParser final { +public: + using Resolver = std::function(std::uint32_t Slot)>; + + MetadataTextParser(std::string Text, Resolver Resolve) + : Lexer("MIR metadata scalar", std::move(Text)), Resolve(std::move(Resolve)) { + do { + Tokens.push_back(Lexer.next()); + } while (!Tokens.back().isErrorOrEnd()); + } + + [[nodiscard]] Expected parseReferenceOnly(); + [[nodiscard]] Expected parseValue(); + [[nodiscard]] Expected parseLocation(); + +private: + [[nodiscard]] const MIToken &token() const noexcept { + return Tokens[Position]; + } + [[nodiscard]] bool at(MITokenKind Kind) const noexcept { + return token().is(Kind); + } + void advance() noexcept { + if (Position + 1U < Tokens.size()) + ++Position; + } + [[nodiscard]] bool consume(MITokenKind Kind) noexcept { + if (!at(Kind)) + return false; + advance(); + return true; + } + [[nodiscard]] Expected expect(MITokenKind Kind, std::string_view Description); + [[nodiscard]] Expected finish(); + [[nodiscard]] Expected parseReference(); + [[nodiscard]] Expected parseExpression(); + [[nodiscard]] Expected parseDILocation(std::vector &Frames, + std::optional &Terminal); + + MILexer Lexer; + Resolver Resolve; + std::vector Tokens; + std::size_t Position = 0; +}; + +} // namespace rcc::mir + +#endif // RCC_LIB_MIR_MIRMETADATATEXTPARSER_H diff --git a/backend/lib/MIR/MIRParserUtilities.h b/backend/lib/MIR/MIRParserUtilities.h new file mode 100644 index 0000000..2f69548 --- /dev/null +++ b/backend/lib/MIR/MIRParserUtilities.h @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +// NOLINTNEXTLINE(llvm-header-guard) +#ifndef RCC_LIB_MIR_MIRPARSERUTILITIES_H +#define RCC_LIB_MIR_MIRPARSERUTILITIES_H + +#include +#include +#include +#include +#include + +namespace rcc::mir::parser_detail { + +[[nodiscard]] inline std::string_view trim(std::string_view Text) noexcept { + while (!Text.empty() && (Text.front() == ' ' || Text.front() == '\t' || Text.front() == '\r' || + Text.front() == '\n')) { + Text.remove_prefix(1); + } + while (!Text.empty() && (Text.back() == ' ' || Text.back() == '\t' || Text.back() == '\r' || + Text.back() == '\n')) { + Text.remove_suffix(1); + } + return Text; +} + +[[nodiscard]] inline bool parseUnsigned(std::string_view Text, std::uint64_t &Value) noexcept { + Text = trim(Text); + int Base = 10; + if (Text.starts_with("0x") || Text.starts_with("0X")) { + Text.remove_prefix(2); + Base = 16; + } + if (Text.empty()) + return false; + const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, Base); + return Error == std::errc{} && End == Text.data() + Text.size(); +} + +[[nodiscard]] inline bool parseSigned(std::string_view Text, std::int64_t &Value) noexcept { + Text = trim(Text); + if (Text.empty()) + return false; + const auto [End, Error] = std::from_chars(Text.data(), Text.data() + Text.size(), Value, 10); + return Error == std::errc{} && End == Text.data() + Text.size(); +} + +[[nodiscard]] inline bool parseUnsigned32(std::string_view Text, std::uint32_t &Value) noexcept { + std::uint64_t Wide = 0; + if (!parseUnsigned(Text, Wide) || Wide > std::numeric_limits::max()) + return false; + Value = static_cast(Wide); + return true; +} + +[[nodiscard]] inline bool parseBool(std::string_view Text, bool &Value) noexcept { + if (Text == "true") { + Value = true; + return true; + } + if (Text == "false") { + Value = false; + return true; + } + return false; +} + +[[nodiscard]] inline std::optional +parsePrefixedID(std::string_view Text, std::string_view Prefix) noexcept { + Text = trim(Text); + if (!Text.starts_with(Prefix)) + return std::nullopt; + Text.remove_prefix(Prefix.size()); + const std::size_t Suffix = Text.find_first_of(". \t"); + if (Suffix != std::string_view::npos) + Text = Text.substr(0, Suffix); + std::uint32_t ID = 0; + if (!parseUnsigned32(Text, ID)) + return std::nullopt; + return ID; +} + +} // namespace rcc::mir::parser_detail + +#endif // RCC_LIB_MIR_MIRPARSERUTILITIES_H diff --git a/backend/lib/Support/CMakeLists.txt b/backend/lib/Support/CMakeLists.txt index ee85084..c081106 100644 --- a/backend/lib/Support/CMakeLists.txt +++ b/backend/lib/Support/CMakeLists.txt @@ -1,9 +1,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later -set(RCCBackendSupportSources Error.cpp TextDiagnostic.cpp YAML.cpp) +set(RCCBackendSupportSources Error.cpp SHA256.cpp TextDiagnostic.cpp YAML.cpp) set( RCCBackendSupportHeaders "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/Error.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/SHA256.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/TextDiagnostic.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/YAML.h" ) @@ -37,12 +38,14 @@ install( rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/Error.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/SHA256.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TextDiagnostic.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/YAML.cpp" ${RCCBackendSupportHeaders} ) rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/Error.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/SHA256.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/TextDiagnostic.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/YAML.cpp" ) diff --git a/backend/lib/Support/Error.cpp b/backend/lib/Support/Error.cpp index da4299f..2bda044 100644 --- a/backend/lib/Support/Error.cpp +++ b/backend/lib/Support/Error.cpp @@ -38,6 +38,18 @@ std::string_view getErrorCodeName(ErrorCode Code) noexcept { return "relocation failed"; case ErrorCode::ObjectWritingFailed: return "object writing failed"; + case ErrorCode::InvalidInvocation: + return "invalid backend invocation"; + case ErrorCode::ManifestError: + return "manifest error"; + case ErrorCode::CacheMiss: + return "cache miss"; + case ErrorCode::ReproducerError: + return "reproducer error"; + case ErrorCode::ReductionFailed: + return "reduction failed"; + case ErrorCode::FuzzingFailure: + return "fuzzing failure"; case ErrorCode::IoError: return "I/O error"; case ErrorCode::InternalError: diff --git a/backend/lib/Support/SHA256.cpp b/backend/lib/Support/SHA256.cpp new file mode 100644 index 0000000..8825f89 --- /dev/null +++ b/backend/lib/Support/SHA256.cpp @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Support/SHA256.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc { +namespace { + +constexpr std::array RoundConstants{ + 0x428a2f98U, 0x71374491U, 0xb5c0fbcfU, 0xe9b5dba5U, 0x3956c25bU, 0x59f111f1U, 0x923f82a4U, + 0xab1c5ed5U, 0xd807aa98U, 0x12835b01U, 0x243185beU, 0x550c7dc3U, 0x72be5d74U, 0x80deb1feU, + 0x9bdc06a7U, 0xc19bf174U, 0xe49b69c1U, 0xefbe4786U, 0x0fc19dc6U, 0x240ca1ccU, 0x2de92c6fU, + 0x4a7484aaU, 0x5cb0a9dcU, 0x76f988daU, 0x983e5152U, 0xa831c66dU, 0xb00327c8U, 0xbf597fc7U, + 0xc6e00bf3U, 0xd5a79147U, 0x06ca6351U, 0x14292967U, 0x27b70a85U, 0x2e1b2138U, 0x4d2c6dfcU, + 0x53380d13U, 0x650a7354U, 0x766a0abbU, 0x81c2c92eU, 0x92722c85U, 0xa2bfe8a1U, 0xa81a664bU, + 0xc24b8b70U, 0xc76c51a3U, 0xd192e819U, 0xd6990624U, 0xf40e3585U, 0x106aa070U, 0x19a4c116U, + 0x1e376c08U, 0x2748774cU, 0x34b0bcb5U, 0x391c0cb3U, 0x4ed8aa4aU, 0x5b9cca4fU, 0x682e6ff3U, + 0x748f82eeU, 0x78a5636fU, 0x84c87814U, 0x8cc70208U, 0x90befffaU, 0xa4506cebU, 0xbef9a3f7U, + 0xc67178f2U, +}; + +constexpr std::array InitialState{ + 0x6a09e667U, 0xbb67ae85U, 0x3c6ef372U, 0xa54ff53aU, + 0x510e527fU, 0x9b05688cU, 0x1f83d9abU, 0x5be0cd19U, +}; + +[[nodiscard]] std::uint32_t loadBigEndian(const std::uint8_t *Input) noexcept { + return (static_cast(Input[0]) << 24U) | + (static_cast(Input[1]) << 16U) | + (static_cast(Input[2]) << 8U) | static_cast(Input[3]); +} + +void storeBigEndian(std::uint32_t Value, std::uint8_t *Output) noexcept { + Output[0] = static_cast(Value >> 24U); + Output[1] = static_cast(Value >> 16U); + Output[2] = static_cast(Value >> 8U); + Output[3] = static_cast(Value); +} + +void compress(std::array &State, const std::uint8_t *Block) noexcept { + std::array Words{}; + for (std::size_t Index = 0; Index < 16; ++Index) + Words[Index] = loadBigEndian(Block + Index * 4U); + for (std::size_t Index = 16; Index < Words.size(); ++Index) { + const std::uint32_t S0 = std::rotr(Words[Index - 15], 7) ^ std::rotr(Words[Index - 15], 18) ^ + (Words[Index - 15] >> 3U); + const std::uint32_t S1 = std::rotr(Words[Index - 2], 17) ^ std::rotr(Words[Index - 2], 19) ^ + (Words[Index - 2] >> 10U); + Words[Index] = Words[Index - 16] + S0 + Words[Index - 7] + S1; + } + + std::uint32_t A = State[0]; + std::uint32_t B = State[1]; + std::uint32_t C = State[2]; + std::uint32_t D = State[3]; + std::uint32_t E = State[4]; + std::uint32_t F = State[5]; + std::uint32_t G = State[6]; + std::uint32_t H = State[7]; + for (std::size_t Index = 0; Index < Words.size(); ++Index) { + const std::uint32_t Sum1 = std::rotr(E, 6) ^ std::rotr(E, 11) ^ std::rotr(E, 25); + const std::uint32_t Choice = (E & F) ^ (~E & G); + const std::uint32_t Temporary1 = H + Sum1 + Choice + RoundConstants[Index] + Words[Index]; + const std::uint32_t Sum0 = std::rotr(A, 2) ^ std::rotr(A, 13) ^ std::rotr(A, 22); + const std::uint32_t Majority = (A & B) ^ (A & C) ^ (B & C); + const std::uint32_t Temporary2 = Sum0 + Majority; + H = G; + G = F; + F = E; + E = D + Temporary1; + D = C; + C = B; + B = A; + A = Temporary1 + Temporary2; + } + State[0] += A; + State[1] += B; + State[2] += C; + State[3] += D; + State[4] += E; + State[5] += F; + State[6] += G; + State[7] += H; +} + +[[nodiscard]] std::string toHex(const SHA256Digest &Digest) { + constexpr std::string_view Digits = "0123456789abcdef"; + std::string Result; + Result.resize(Digest.size() * 2U); + for (std::size_t Index = 0; Index < Digest.size(); ++Index) { + Result[Index * 2U] = Digits[Digest[Index] >> 4U]; + Result[Index * 2U + 1U] = Digits[Digest[Index] & 0x0fU]; + } + return Result; +} + +} // namespace + +SHA256Digest sha256(std::span Input) { + const auto *Bytes = reinterpret_cast(Input.data()); + std::array State = InitialState; + std::size_t Offset = 0; + while (Input.size() - Offset >= 64U) { + compress(State, Bytes + Offset); + Offset += 64U; + } + + std::vector Tail; + Tail.reserve(128); + Tail.insert(Tail.end(), Bytes + Offset, Bytes + Input.size()); + Tail.push_back(0x80U); + while ((Tail.size() + 8U) % 64U != 0) + Tail.push_back(0); + const std::uint64_t BitLength = static_cast(Input.size()) * 8U; + for (unsigned Byte = 0; Byte < 8; ++Byte) { + const unsigned Shift = 56U - Byte * 8U; + Tail.push_back(static_cast(BitLength >> Shift)); + } + for (std::size_t Index = 0; Index < Tail.size(); Index += 64U) + compress(State, Tail.data() + Index); + + SHA256Digest Result{}; + for (std::size_t Index = 0; Index < State.size(); ++Index) + storeBigEndian(State[Index], Result.data() + Index * 4U); + return Result; +} + +SHA256Digest sha256(std::string_view Input) { + const auto *Bytes = reinterpret_cast(Input.data()); + return sha256(std::span(Bytes, Input.size())); +} + +std::string sha256Hex(std::span Input) { + return toHex(sha256(Input)); +} + +std::string sha256Hex(std::string_view Input) { + return toHex(sha256(Input)); +} + +} // namespace rcc diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt index 55578a2..156dfd6 100644 --- a/backend/lib/Target/RISCV/CMakeLists.txt +++ b/backend/lib/Target/RISCV/CMakeLists.txt @@ -60,6 +60,7 @@ add_library( RISCVMachineCombiner.cpp RISCVMachineMemoryFolding.cpp RISCVOptimizationPipeline.cpp + RISCVPipeline.cpp RISCVPseudoExpansion.cpp RISCVRegisterAllocation.cpp RISCVScheduling.cpp @@ -125,6 +126,7 @@ rcc_backend_register_format_files( "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineCombiner.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineMemoryFolding.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVOptimizationPipeline.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVPipeline.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVPseudoExpansion.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocation.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVScheduling.cpp" @@ -138,6 +140,7 @@ rcc_backend_register_format_files( "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVFixupKinds.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVMCCodeEmitter.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVObjectFile.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVPipeline.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVMCTargetLowering.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTarget.h" "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTargetConstant.h" @@ -157,6 +160,7 @@ rcc_backend_register_tidy_files( "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineCombiner.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineMemoryFolding.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVOptimizationPipeline.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVPipeline.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVPseudoExpansion.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocation.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/RISCVScheduling.cpp" diff --git a/backend/lib/Target/RISCV/RISCVPipeline.cpp b/backend/lib/Target/RISCV/RISCVPipeline.cpp new file mode 100644 index 0000000..8d05bba --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVPipeline.cpp @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVPipeline.h" + +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineOptimizationPipeline.h" +#include "rcc/CodeGen/MachinePipelinePasses.h" +#include "rcc/CodeGen/MachineSSAExit.h" +#include "rcc/CodeGen/RegAllocPBQP.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVCodeGen.h" + +#include +#include +#include +#include +#include +#include + +namespace rcc::riscv { +namespace { + +template +[[nodiscard]] Expected addPass(MachinePassManager &Pipeline, ArgumentT &&...Arguments) { + return Pipeline.addPass(std::make_unique(std::forward(Arguments)...)); +} + +[[nodiscard]] Expected addInitialPipeline(MachinePassManager &Pipeline) { + auto Added = addPass(Pipeline, legalizerInfo()); + if (!Added) + return std::unexpected(Added.error()); + Added = addPass(Pipeline, registerBankInfo()); + if (!Added) + return std::unexpected(Added.error()); + return addPass(Pipeline, instructionSelector()); +} + +[[nodiscard]] Expected addSSAExitPipeline(MachinePassManager &Pipeline) { + auto Added = addSelectedSSAOptimizationPipeline(Pipeline, optimizationPipelineInfo()); + if (!Added) + return std::unexpected(Added.error()); + Added = addPass(Pipeline, ssaInfo()); + if (!Added) + return std::unexpected(Added.error()); + return addPass(Pipeline, ssaInfo()); +} + +[[nodiscard]] Expected addRegisterAllocationPipeline(MachinePassManager &Pipeline) { + auto Added = addPreRegisterAllocationOptimizationPipeline(Pipeline, optimizationPipelineInfo()); + if (!Added) + return std::unexpected(Added.error()); + return addPass(Pipeline, registerAllocationInfo()); +} + +[[nodiscard]] Expected addCompleteRegistry(MachinePassManager &Pipeline) { + auto Added = addInitialPipeline(Pipeline); + if (!Added) + return std::unexpected(Added.error()); + Added = addSSAExitPipeline(Pipeline); + if (!Added) + return std::unexpected(Added.error()); + Added = addRegisterAllocationPipeline(Pipeline); + if (!Added) + return std::unexpected(Added.error()); + return addPostRegisterAllocationPipeline(Pipeline, optimizationPipelineInfo()); +} + +} // namespace + +Expected addCanonicalPipeline(MachinePassManager &Pipeline, const MachineFunction &Function) { + if (!Function.properties().has(MachineFunctionProperty::Legalized)) { + auto Added = addPass(Pipeline, legalizerInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::RegBankSelected)) { + auto Added = addPass(Pipeline, registerBankInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::Selected)) { + auto Added = addPass(Pipeline, instructionSelector()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::TiedOpsRewritten)) { + auto Added = addSelectedSSAOptimizationPipeline(Pipeline, optimizationPipelineInfo()); + if (!Added) + return std::unexpected(Added.error()); + if (!Function.properties().has(MachineFunctionProperty::NoPHIs)) { + Added = addPass(Pipeline, ssaInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + } + if (!Function.properties().has(MachineFunctionProperty::TiedOpsRewritten)) { + auto Added = addPass(Pipeline, ssaInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::NoVRegs)) { + auto Added = addRegisterAllocationPipeline(Pipeline); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::NoExecutablePseudos)) { + auto Added = addPostRegisterAllocationPipeline(Pipeline, optimizationPipelineInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + return {}; +} + +Expected> createCanonicalPipelinePass(std::string_view Name) { + if (Name.empty()) + return makeUnexpected(ErrorCode::InvalidArgument, "pipeline pass name cannot be empty"); + MachinePassManager Registry; + auto Added = addCompleteRegistry(Registry); + if (!Added) + return std::unexpected(Added.error()); + MachinePassManager::PassList Passes = std::move(Registry).takePasses(); + auto Position = std::ranges::find_if(Passes, [Name](const auto &Pass) { + return Pass != nullptr && Pass->contract().Name == Name; + }); + if (Position == Passes.end()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "unknown RV64GC machine pass '" + std::string(Name) + "'"); + } + return std::move(*Position); +} + +Expected> canonicalPipelinePassNames(const MachineFunction &Function) { + MachinePassManager Pipeline; + auto Added = addCanonicalPipeline(Pipeline, Function); + if (!Added) + return std::unexpected(Added.error()); + MachinePassManager::PassList Passes = std::move(Pipeline).takePasses(); + std::vector Result; + Result.reserve(Passes.size()); + for (const std::unique_ptr &Pass : Passes) { + if (Pass == nullptr) + return makeUnexpected(ErrorCode::InternalError, "canonical pipeline contains a null pass"); + Result.push_back(Pass->contract().Name); + } + return Result; +} + +Expected runCanonicalPipeline(MachineFunction &Function, const TargetDescription &Target, + MachinePassManagerOptions Options, + MachinePassInstrumentation *Instrumentation) { + MachinePassManager Pipeline(Options); + auto Added = addCanonicalPipeline(Pipeline, Function); + if (!Added) + return std::unexpected(Added.error()); + MachineAnalysisManager Analyses; + auto Registered = registerMachineOptimizationAnalyses(Analyses); + if (!Registered) + return std::unexpected(Registered.error()); + return Pipeline.run(Function, Target, Analyses, Instrumentation); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Tooling/ArtifactStore.cpp b/backend/lib/Tooling/ArtifactStore.cpp new file mode 100644 index 0000000..76fc0df --- /dev/null +++ b/backend/lib/Tooling/ArtifactStore.cpp @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Tooling/ArtifactStore.h" + +#include "rcc/CodeGen/CompilationUnit.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineVerifier.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/MIR/MIRDocument.h" +#include "rcc/Support/SHA256.h" +#include "rcc/Support/TextDiagnostic.h" +#include "rcc/Tooling/BackendInvocation.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::tooling { +namespace { + +constexpr std::uintmax_t MaximumPackageFileSize = std::uintmax_t{1} << 30U; +std::atomic TemporarySequence = 0; + +[[nodiscard]] bool isMIRArtifact(ArtifactKind Artifact) noexcept { + switch (Artifact) { + case ArtifactKind::PreLegalizerMIR: + case ArtifactKind::LegalizedMIR: + case ArtifactKind::RegisterBankMIR: + case ArtifactKind::SelectedMIR: + case ArtifactKind::PostSSAMIR: + case ArtifactKind::PostRegisterAllocationMIR: + case ArtifactKind::FinalMIR: + return true; + case ArtifactKind::Assembly: + case ArtifactKind::RelocatableObject: + return false; + } + return false; +} + +[[nodiscard]] bool isCacheableArtifact(ArtifactKind Artifact) noexcept { + switch (Artifact) { + case ArtifactKind::PreLegalizerMIR: + case ArtifactKind::SelectedMIR: + case ArtifactKind::PostRegisterAllocationMIR: + case ArtifactKind::FinalMIR: + case ArtifactKind::Assembly: + case ArtifactKind::RelocatableObject: + return true; + case ArtifactKind::LegalizedMIR: + case ArtifactKind::RegisterBankMIR: + case ArtifactKind::PostSSAMIR: + return false; + } + return false; +} + +[[nodiscard]] std::string_view artifactFileName(ArtifactKind Artifact) noexcept { + switch (Artifact) { + case ArtifactKind::PreLegalizerMIR: + case ArtifactKind::LegalizedMIR: + case ArtifactKind::RegisterBankMIR: + case ArtifactKind::SelectedMIR: + case ArtifactKind::PostSSAMIR: + case ArtifactKind::PostRegisterAllocationMIR: + case ArtifactKind::FinalMIR: + return "artifact.mir"; + case ArtifactKind::Assembly: + return "artifact.s"; + case ArtifactKind::RelocatableObject: + return "artifact.o"; + } + return "artifact.bin"; +} + +[[nodiscard]] std::filesystem::path cacheEntryPath(const std::filesystem::path &Root, + std::string_view Key) { + return Root / std::string(Key.substr(0, 2)) / std::string(Key); +} + +[[nodiscard]] Expected> readBytes(const std::filesystem::path &Path, + ErrorCode Code) { + std::error_code FileError; + const std::uintmax_t Size = std::filesystem::file_size(Path, FileError); + if (FileError) + return makeUnexpected(Code, "cannot inspect package file '" + Path.string() + "'"); + if (Size > MaximumPackageFileSize || + Size > static_cast(std::numeric_limits::max())) { + return makeUnexpected(Code, "package file exceeds the supported size limit"); + } + std::ifstream Input(Path, std::ios::binary); + if (!Input) + return makeUnexpected(Code, "cannot open package file '" + Path.string() + "'"); + std::vector Result(static_cast(Size)); + if (!Result.empty()) { + Input.read(reinterpret_cast(Result.data()), + static_cast(Result.size())); + } + if (!Input) + return makeUnexpected(Code, "cannot read package file '" + Path.string() + "'"); + return Result; +} + +[[nodiscard]] Expected readText(const std::filesystem::path &Path, ErrorCode Code) { + auto Bytes = readBytes(Path, Code); + if (!Bytes) + return std::unexpected(Bytes.error()); + return std::string(reinterpret_cast(Bytes->data()), Bytes->size()); +} + +[[nodiscard]] Expected writeBytes(const std::filesystem::path &Path, + std::span Bytes, ErrorCode Code) { + std::ofstream Output(Path, std::ios::binary | std::ios::trunc); + if (!Output) + return makeUnexpected(Code, "cannot create package file '" + Path.string() + "'"); + if (!Bytes.empty()) { + Output.write(reinterpret_cast(Bytes.data()), + static_cast(Bytes.size())); + } + Output.close(); + if (!Output) + return makeUnexpected(Code, "cannot write package file '" + Path.string() + "'"); + return {}; +} + +[[nodiscard]] Expected writeText(const std::filesystem::path &Path, std::string_view Text, + ErrorCode Code) { + const auto *Bytes = reinterpret_cast(Text.data()); + return writeBytes(Path, std::span(Bytes, Text.size()), Code); +} + +[[nodiscard]] std::filesystem::path temporaryPath(const std::filesystem::path &Destination) { + const std::uint64_t Sequence = TemporarySequence.fetch_add(1, std::memory_order_relaxed); + return Destination.parent_path() / + (Destination.filename().string() + ".tmp." + std::to_string(Sequence)); +} + +[[nodiscard]] Expected replaceTextAtomically(const std::filesystem::path &Path, + std::string_view Text, ErrorCode Code) { + const std::filesystem::path Temporary = temporaryPath(Path); + auto Written = writeText(Temporary, Text, Code); + if (!Written) + return Written; + std::error_code FileError; + std::filesystem::rename(Temporary, Path, FileError); + if (!FileError) + return {}; + std::error_code CleanupError; + std::filesystem::remove(Temporary, CleanupError); + return makeUnexpected(Code, "cannot atomically replace package file '" + Path.string() + "'"); +} + +[[nodiscard]] Expected +writePackageAtomically(const std::filesystem::path &Destination, std::string_view ManifestText, + std::string_view InputMIR, std::string_view ArtifactName, + std::span Artifact, bool HasArtifact, ErrorCode Code) { + if (Destination.empty()) + return makeUnexpected(Code, "package destination cannot be empty"); + std::error_code FileError; + const std::filesystem::path Parent = Destination.parent_path(); + if (!Parent.empty()) { + std::filesystem::create_directories(Parent, FileError); + if (FileError) + return makeUnexpected(Code, "cannot create package parent directory"); + } + if (std::filesystem::exists(Destination, FileError)) + return makeUnexpected(Code, "package destination already exists"); + if (FileError) + return makeUnexpected(Code, "cannot inspect package destination"); + + const std::filesystem::path Temporary = temporaryPath(Destination); + std::filesystem::create_directory(Temporary, FileError); + if (FileError) + return makeUnexpected(Code, "cannot create temporary package directory"); + const auto Cleanup = [&]() { + std::error_code CleanupError; + std::filesystem::remove_all(Temporary, CleanupError); + }; + auto Written = writeText(Temporary / "manifest.yaml", ManifestText, Code); + if (!Written) { + Cleanup(); + return Written; + } + Written = writeText(Temporary / "input.mir", InputMIR, Code); + if (!Written) { + Cleanup(); + return Written; + } + if (HasArtifact) { + Written = writeBytes(Temporary / ArtifactName, Artifact, Code); + if (!Written) { + Cleanup(); + return Written; + } + } + std::filesystem::rename(Temporary, Destination, FileError); + if (FileError) { + Cleanup(); + return makeUnexpected(Code, "cannot publish package atomically"); + } + return {}; +} + +[[nodiscard]] Expected verifyFunctions(const mir::MIRDocument &Document, + const TargetDescription &Target) { + auto UnitVerification = Document.unit().verify(); + if (!UnitVerification) + return std::unexpected(UnitVerification.error()); + 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, + "MIR document contains a null machine function"); + auto FunctionVerification = MachineVerifier::verify(*Function, Target); + if (!FunctionVerification) + return std::unexpected(FunctionVerification.error()); + } + return {}; +} + +[[nodiscard]] Error parseFailure(const mir::MIRDocumentParseResult &Parsed, ErrorCode Code) { + if (Parsed.diagnostics().empty()) + return Error(Code, "MIR package input could not be parsed"); + return Error(Code, formatTextDiagnostic(Parsed.diagnostics().front(), Parsed.source())); +} + +[[nodiscard]] Expected verifyManifestIdentity(const BackendManifest &Manifest, + ManifestKind Kind, ArtifactKind Artifact, + const BackendInvocation &Invocation, + std::string_view InputHash, + std::string_view TargetFingerprint, + std::string_view CacheKey, ErrorCode Code) { + if (Manifest.Kind != Kind || Manifest.Artifact != Artifact || Manifest.Invocation != Invocation || + Manifest.InputMIRHash != InputHash || Manifest.TargetFingerprint != TargetFingerprint || + Manifest.CacheKey != CacheKey) { + return makeUnexpected(Code, "package manifest does not match the requested backend input"); + } + return {}; +} + +} // namespace + +Expected parseAndCanonicalizeMIR(std::string SourceName, std::string SourceText, + const TargetDescription &Target, + const BackendInvocation &Invocation) { + BackendInvocation CanonicalInvocation = Invocation; + auto InvocationVerification = CanonicalInvocation.canonicalizeAndVerify(); + if (!InvocationVerification) + return std::unexpected(InvocationVerification.error()); + if (CanonicalInvocation != Invocation) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "MIR parsing requires a canonical backend invocation"); + } + mir::MIRDocumentParserOptions ParseOptions; + ParseOptions.DataLayout = Invocation.DataLayout; + ParseOptions.DefaultPointerSizeInBits = 64; + ParseOptions.VerifyMachineFunctions = true; + mir::MIRDocumentParseResult Parsed = + mir::parseMIRDocument(Target, std::move(SourceName), std::move(SourceText), ParseOptions); + if (!Parsed.succeeded()) + return std::unexpected(parseFailure(Parsed, ErrorCode::ParseError)); + if (Parsed.document() == nullptr) + return makeUnexpected(ErrorCode::InternalError, "successful MIR parse has no document"); + auto Verification = verifyFunctions(*Parsed.document(), Target); + if (!Verification) + return std::unexpected(Verification.error()); + mir::MIRDocumentPrinterOptions PrintOptions; + PrintOptions.Mode = mir::MIPrintMode::Complete; + PrintOptions.EmitEmbeddedIR = true; + PrintOptions.DataLayout = Invocation.DataLayout; + PrintOptions.DefaultPointerSizeInBits = 64; + auto Printed = mir::printMIRDocument(*Parsed.document(), Target, std::move(PrintOptions)); + if (!Printed) + return std::unexpected(Printed.error()); + return CanonicalMIR{ + .Document = std::move(*Parsed.document()), + .Text = std::move(*Printed), + }; +} + +Expected +createBackendManifest(ManifestKind Kind, ArtifactKind Artifact, const BackendInvocation &Invocation, + const TargetDescription &Target, std::string_view CanonicalInputMIR, + std::optional> ArtifactBytes, + std::vector Diagnostics) { + BackendInvocation CanonicalInvocation = Invocation; + auto InvocationVerification = CanonicalInvocation.canonicalizeAndVerify(); + if (!InvocationVerification) + return std::unexpected(InvocationVerification.error()); + if (CanonicalInvocation != Invocation) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "manifest creation requires a canonical backend invocation"); + } + auto Fingerprint = computeTargetDescriptionFingerprint(Target); + if (!Fingerprint) + return std::unexpected(Fingerprint.error()); + auto Key = computeCacheKey(CanonicalInputMIR, Invocation, *Fingerprint); + if (!Key) + return std::unexpected(Key.error()); + BackendManifest Result{ + .SchemaVersion = BackendManifestSchemaVersion, + .Kind = Kind, + .Artifact = Artifact, + .Invocation = Invocation, + .InputMIRHash = sha256Hex(CanonicalInputMIR), + .TargetFingerprint = std::move(*Fingerprint), + .CacheKey = std::move(*Key), + .ArtifactHash = std::nullopt, + .Diagnostics = std::move(Diagnostics), + }; + if (ArtifactBytes) { + const auto *Bytes = reinterpret_cast(ArtifactBytes->data()); + Result.ArtifactHash = sha256Hex( + std::span(Bytes, static_cast(ArtifactBytes->size()))); + } + auto Verification = Result.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Result; +} + +Expected storeCacheEntry(const std::filesystem::path &CacheRoot, + const BackendManifest &Manifest, std::string_view CanonicalInputMIR, + std::span Artifact) { + auto Verification = Manifest.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + if (Manifest.Kind != ManifestKind::Cache || !Manifest.ArtifactHash) { + return makeUnexpected(ErrorCode::ManifestError, + "cache entry requires a cache manifest and artifact hash"); + } + if (!isCacheableArtifact(Manifest.Artifact)) { + return makeUnexpected(ErrorCode::ManifestError, + "manifest artifact is not an allowed cache boundary"); + } + if (sha256Hex(CanonicalInputMIR) != Manifest.InputMIRHash) { + return makeUnexpected(ErrorCode::ManifestError, + "cache input MIR does not match its manifest hash"); + } + const auto *ArtifactBytes = reinterpret_cast(Artifact.data()); + if (sha256Hex(std::span(ArtifactBytes, Artifact.size())) != + *Manifest.ArtifactHash) { + return makeUnexpected(ErrorCode::ManifestError, + "cache artifact does not match its manifest hash"); + } + auto Printed = printBackendManifest(Manifest); + if (!Printed) + return std::unexpected(Printed.error()); + const std::filesystem::path Destination = cacheEntryPath(CacheRoot, Manifest.CacheKey); + return writePackageAtomically(Destination, *Printed, CanonicalInputMIR, + artifactFileName(Manifest.Artifact), Artifact, true, + ErrorCode::IoError); +} + +Expected loadCacheEntry(const std::filesystem::path &CacheRoot, ArtifactKind Artifact, + const BackendInvocation &Invocation, + const TargetDescription &Target, + std::string_view CanonicalInputMIR) { + if (!isCacheableArtifact(Artifact)) + return makeUnexpected(ErrorCode::CacheMiss, "requested artifact is not a cache boundary"); + BackendInvocation CanonicalInvocation = Invocation; + auto InvocationVerification = CanonicalInvocation.canonicalizeAndVerify(); + if (!InvocationVerification) + return std::unexpected(InvocationVerification.error()); + if (CanonicalInvocation != Invocation) + return makeUnexpected(ErrorCode::CacheMiss, "cache invocation is not canonical"); + auto Fingerprint = computeTargetDescriptionFingerprint(Target); + if (!Fingerprint) + return std::unexpected(Fingerprint.error()); + auto Key = computeCacheKey(CanonicalInputMIR, Invocation, *Fingerprint); + if (!Key) + return std::unexpected(Key.error()); + const std::filesystem::path EntryPath = cacheEntryPath(CacheRoot, *Key); + std::error_code FileError; + if (!std::filesystem::is_directory(EntryPath, FileError) || FileError) + return makeUnexpected(ErrorCode::CacheMiss, "cache entry is absent"); + auto ManifestText = readText(EntryPath / "manifest.yaml", ErrorCode::CacheMiss); + if (!ManifestText) + return std::unexpected(ManifestText.error()); + auto Manifest = + parseBackendManifest((EntryPath / "manifest.yaml").string(), std::move(*ManifestText)); + if (!Manifest) + return makeUnexpected(ErrorCode::CacheMiss, std::string(Manifest.error().message())); + const std::string InputHash = sha256Hex(CanonicalInputMIR); + auto Identity = verifyManifestIdentity(*Manifest, ManifestKind::Cache, Artifact, Invocation, + InputHash, *Fingerprint, *Key, ErrorCode::CacheMiss); + if (!Identity) + return std::unexpected(Identity.error()); + if (!Manifest->ArtifactHash) + return makeUnexpected(ErrorCode::CacheMiss, "cache manifest has no artifact hash"); + + auto StoredInput = readText(EntryPath / "input.mir", ErrorCode::CacheMiss); + if (!StoredInput) + return std::unexpected(StoredInput.error()); + if (*StoredInput != CanonicalInputMIR || sha256Hex(*StoredInput) != Manifest->InputMIRHash) + return makeUnexpected(ErrorCode::CacheMiss, "cached input MIR is inconsistent"); + auto StrictInput = parseAndCanonicalizeMIR("cache-input.mir", *StoredInput, Target, Invocation); + if (!StrictInput || StrictInput->Text != *StoredInput) + return makeUnexpected(ErrorCode::CacheMiss, "cached input MIR is not canonical and valid"); + + auto ArtifactBytes = readBytes(EntryPath / artifactFileName(Artifact), ErrorCode::CacheMiss); + if (!ArtifactBytes) + return std::unexpected(ArtifactBytes.error()); + const auto *Bytes = reinterpret_cast(ArtifactBytes->data()); + if (sha256Hex(std::span(Bytes, ArtifactBytes->size())) != + *Manifest->ArtifactHash) { + return makeUnexpected(ErrorCode::CacheMiss, "cached artifact hash is inconsistent"); + } + std::optional ParsedArtifact; + if (isMIRArtifact(Artifact)) { + std::string ArtifactText(reinterpret_cast(ArtifactBytes->data()), + ArtifactBytes->size()); + auto Parsed = parseAndCanonicalizeMIR("cache-artifact.mir", ArtifactText, Target, Invocation); + if (!Parsed || Parsed->Text != ArtifactText) + return makeUnexpected(ErrorCode::CacheMiss, "cached MIR artifact is not canonical and valid"); + ParsedArtifact = std::move(*Parsed); + } + return CacheEntry{ + .Manifest = std::move(*Manifest), + .InputMIR = std::move(*StoredInput), + .Artifact = std::move(*ArtifactBytes), + .ParsedArtifact = std::move(ParsedArtifact), + }; +} + +Expected writeReproducer(const std::filesystem::path &PackagePath, + const BackendManifest &Manifest, + std::string_view CanonicalInputMIR) { + auto Verification = Manifest.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + if (Manifest.Kind != ManifestKind::Reproducer || Manifest.ArtifactHash) { + return makeUnexpected(ErrorCode::ReproducerError, + "reproducer requires a reproducer manifest without an artifact hash"); + } + if (sha256Hex(CanonicalInputMIR) != Manifest.InputMIRHash) { + return makeUnexpected(ErrorCode::ReproducerError, + "reproducer input MIR does not match its manifest hash"); + } + auto Printed = printBackendManifest(Manifest); + if (!Printed) + return std::unexpected(Printed.error()); + return writePackageAtomically(PackagePath, *Printed, CanonicalInputMIR, {}, {}, false, + ErrorCode::ReproducerError); +} + +Expected updateReproducerDiagnostics(const std::filesystem::path &PackagePath, + std::vector Diagnostics) { + const std::filesystem::path ManifestPath = PackagePath / "manifest.yaml"; + auto ManifestText = readText(ManifestPath, ErrorCode::ReproducerError); + if (!ManifestText) + return std::unexpected(ManifestText.error()); + auto Manifest = parseBackendManifest(ManifestPath.string(), std::move(*ManifestText)); + if (!Manifest) { + return makeUnexpected(ErrorCode::ReproducerError, + "cannot update an invalid reproducer manifest: " + + std::string(Manifest.error().message())); + } + if (Manifest->Kind != ManifestKind::Reproducer || Manifest->ArtifactHash) { + return makeUnexpected(ErrorCode::ReproducerError, + "diagnostics can only update a reproducer package"); + } + auto Input = readText(PackagePath / "input.mir", ErrorCode::ReproducerError); + if (!Input) + return std::unexpected(Input.error()); + if (sha256Hex(*Input) != Manifest->InputMIRHash) { + return makeUnexpected(ErrorCode::ReproducerError, + "reproducer input changed before diagnostic update"); + } + Manifest->Diagnostics = std::move(Diagnostics); + auto Printed = printBackendManifest(*Manifest); + if (!Printed) + return makeUnexpected(ErrorCode::ReproducerError, std::string(Printed.error().message())); + return replaceTextAtomically(ManifestPath, *Printed, ErrorCode::ReproducerError); +} + +Expected loadReproducer(const std::filesystem::path &PackagePath, + const TargetDescription &Target) { + auto ManifestText = readText(PackagePath / "manifest.yaml", ErrorCode::ReproducerError); + if (!ManifestText) + return std::unexpected(ManifestText.error()); + auto Manifest = + parseBackendManifest((PackagePath / "manifest.yaml").string(), std::move(*ManifestText)); + if (!Manifest) + return makeUnexpected(ErrorCode::ReproducerError, std::string(Manifest.error().message())); + if (Manifest->Kind != ManifestKind::Reproducer || Manifest->ArtifactHash) { + return makeUnexpected(ErrorCode::ReproducerError, + "package does not contain a valid reproducer manifest"); + } + auto Fingerprint = computeTargetDescriptionFingerprint(Target); + if (!Fingerprint) + return std::unexpected(Fingerprint.error()); + if (Manifest->TargetFingerprint != *Fingerprint) { + return makeUnexpected(ErrorCode::ReproducerError, + "reproducer target fingerprint does not match this backend"); + } + auto InputText = readText(PackagePath / "input.mir", ErrorCode::ReproducerError); + if (!InputText) + return std::unexpected(InputText.error()); + auto Input = parseAndCanonicalizeMIR((PackagePath / "input.mir").string(), *InputText, Target, + Manifest->Invocation); + if (!Input) + return makeUnexpected(ErrorCode::ReproducerError, std::string(Input.error().message())); + if (Input->Text != *InputText || sha256Hex(Input->Text) != Manifest->InputMIRHash) { + return makeUnexpected(ErrorCode::ReproducerError, + "reproducer input MIR is not canonical or has the wrong hash"); + } + auto Key = computeCacheKey(Input->Text, Manifest->Invocation, *Fingerprint); + if (!Key) + return std::unexpected(Key.error()); + if (*Key != Manifest->CacheKey) { + return makeUnexpected(ErrorCode::ReproducerError, + "reproducer cache key is inconsistent with its explicit inputs"); + } + return ReproducerPackage{ + .Manifest = std::move(*Manifest), + .Input = std::move(*Input), + }; +} + +} // namespace rcc::tooling diff --git a/backend/lib/Tooling/BackendDriver.cpp b/backend/lib/Tooling/BackendDriver.cpp new file mode 100644 index 0000000..85a2cf9 --- /dev/null +++ b/backend/lib/Tooling/BackendDriver.cpp @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Tooling/BackendDriver.h" + +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/MIR/MIRDocument.h" +#include "rcc/Target/RISCV/RISCVAssemblyPrinter.h" +#include "rcc/Target/RISCV/RISCVObjectFile.h" + +#include +#include +#include +#include +#include +#include + +namespace rcc::tooling { +namespace { + +[[nodiscard]] bool isMIR(BackendOutputKind Output) noexcept { + return Output <= BackendOutputKind::FinalMIR; +} + +[[nodiscard]] BackendStage outputStage(BackendOutputKind Output) noexcept { + switch (Output) { + case BackendOutputKind::PreLegalizerMIR: + return BackendStage::PreLegalizerMIR; + case BackendOutputKind::LegalizedMIR: + return BackendStage::LegalizedMIR; + case BackendOutputKind::RegisterBankMIR: + return BackendStage::RegisterBankMIR; + case BackendOutputKind::SelectedMIR: + return BackendStage::SelectedMIR; + case BackendOutputKind::PostSSAMIR: + return BackendStage::PostSSAMIR; + case BackendOutputKind::PostRegisterAllocationMIR: + return BackendStage::PostRegisterAllocationMIR; + case BackendOutputKind::FinalMIR: + case BackendOutputKind::Assembly: + case BackendOutputKind::RelocatableObject: + return BackendStage::FinalMIR; + } + return BackendStage::FinalMIR; +} + +[[nodiscard]] std::uint8_t ordinal(BackendStage Stage) noexcept { + return static_cast(Stage); +} + +[[nodiscard]] Expected> printMIR(const mir::MIRDocument &Document, + const TargetDescription &Target, + const BackendInvocation &Invocation) { + mir::MIRDocumentPrinterOptions Options; + Options.Mode = mir::MIPrintMode::Complete; + Options.EmitEmbeddedIR = true; + Options.DataLayout = Invocation.DataLayout; + Options.DefaultPointerSizeInBits = 64; + auto Printed = mir::printMIRDocument(Document, Target, std::move(Options)); + if (!Printed) + return std::unexpected(Printed.error()); + return std::vector(Printed->begin(), Printed->end()); +} + +} // namespace + +std::string_view BackendRunResult::text() const noexcept { + if (Kind == BackendOutputKind::RelocatableObject || Output.empty()) + return {}; + return {reinterpret_cast(Output.data()), Output.size()}; +} + +Expected runBackend(CanonicalMIR Input, const TargetDescription &Target, + const BackendInvocation &Invocation, BackendOutputKind Output, + PipelineControls Controls, + MachinePassInstrumentation *Instrumentation) { + auto InvocationVerification = verifyRV64GCInvocation(Invocation); + if (!InvocationVerification) + return std::unexpected(InvocationVerification.error()); + if (Input.Document.unit().functionCount() == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "backend input must contain at least one machine function"); + } + + BackendStage InitialStage = BackendStage::FinalMIR; + for (std::size_t Index = 0; Index < Input.Document.unit().functionCount(); ++Index) { + const MachineFunction *Function = Input.Document.unit().functionAt(Index); + if (Function == nullptr) + return makeUnexpected(ErrorCode::InternalError, "backend input contains a null function"); + const BackendStage Stage = stageForFunction(*Function); + if (Index == 0 || ordinal(Stage) < ordinal(InitialStage)) + InitialStage = Stage; + } + const BackendStage RequiredStage = outputStage(Output); + if (isMIR(Output) && ordinal(RequiredStage) < ordinal(InitialStage)) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "backend cannot reconstruct an earlier MIR stage from a later snapshot"); + } + + if (isMIR(Output)) { + if (Controls.StopAtStage && *Controls.StopAtStage != RequiredStage) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "output stage conflicts with the explicit pipeline stop stage"); + } + Controls.StopAtStage = RequiredStage; + } + + MachinePassInstrumentation LocalInstrumentation; + MachinePassInstrumentation *ActiveInstrumentation = + Instrumentation == nullptr ? &LocalInstrumentation : Instrumentation; + auto Pipeline = runRV64GCPipeline(Input.Document, Target, Invocation, std::move(Controls), + ActiveInstrumentation); + if (!Pipeline) + return std::unexpected(Pipeline.error()); + + std::vector Bytes; + if (isMIR(Output)) { + auto Printed = printMIR(Input.Document, Target, Invocation); + if (!Printed) + return std::unexpected(Printed.error()); + Bytes = std::move(*Printed); + } else if (Output == BackendOutputKind::Assembly) { + auto Assembly = riscv::emitAssembly(Input.Document.unit(), Target); + if (!Assembly) + return std::unexpected(Assembly.error()); + Bytes.assign(Assembly->begin(), Assembly->end()); + } else { + auto Object = riscv::emitObject(Input.Document.unit(), Target); + if (!Object) + return std::unexpected(Object.error()); + Bytes = std::move(*Object); + } + + return BackendRunResult{ + .Kind = Output, + .Output = std::move(Bytes), + .Pipeline = std::move(*Pipeline), + .Actions = ActiveInstrumentation->actionEvents(), + }; +} + +std::string_view spelling(BackendOutputKind Output) noexcept { + switch (Output) { + case BackendOutputKind::PreLegalizerMIR: + return "prelegalizer-mir"; + case BackendOutputKind::LegalizedMIR: + return "legalized-mir"; + case BackendOutputKind::RegisterBankMIR: + return "regbank-mir"; + case BackendOutputKind::SelectedMIR: + return "selected-mir"; + case BackendOutputKind::PostSSAMIR: + return "post-ssa-mir"; + case BackendOutputKind::PostRegisterAllocationMIR: + return "post-ra-mir"; + case BackendOutputKind::FinalMIR: + return "final-mir"; + case BackendOutputKind::Assembly: + return "assembly"; + case BackendOutputKind::RelocatableObject: + return "object"; + } + return "invalid"; +} + +Expected parseBackendOutputKind(std::string_view Value) { + if (Value == "prelegalizer-mir") + return BackendOutputKind::PreLegalizerMIR; + if (Value == "legalized-mir") + return BackendOutputKind::LegalizedMIR; + if (Value == "regbank-mir") + return BackendOutputKind::RegisterBankMIR; + if (Value == "selected-mir") + return BackendOutputKind::SelectedMIR; + if (Value == "post-ssa-mir") + return BackendOutputKind::PostSSAMIR; + if (Value == "post-ra-mir") + return BackendOutputKind::PostRegisterAllocationMIR; + if (Value == "final-mir") + return BackendOutputKind::FinalMIR; + if (Value == "assembly" || Value == "asm") + return BackendOutputKind::Assembly; + if (Value == "object" || Value == "obj") + return BackendOutputKind::RelocatableObject; + return makeUnexpected(ErrorCode::InvalidInvocation, "unknown backend output kind"); +} + +ArtifactKind manifestArtifactKind(BackendOutputKind Output) noexcept { + return static_cast(static_cast(Output)); +} + +Expected cacheArtifactKind(BackendOutputKind Output) { + switch (Output) { + case BackendOutputKind::PreLegalizerMIR: + return ArtifactKind::PreLegalizerMIR; + case BackendOutputKind::SelectedMIR: + return ArtifactKind::SelectedMIR; + case BackendOutputKind::PostRegisterAllocationMIR: + return ArtifactKind::PostRegisterAllocationMIR; + case BackendOutputKind::FinalMIR: + return ArtifactKind::FinalMIR; + case BackendOutputKind::Assembly: + return ArtifactKind::Assembly; + case BackendOutputKind::RelocatableObject: + return ArtifactKind::RelocatableObject; + case BackendOutputKind::LegalizedMIR: + case BackendOutputKind::RegisterBankMIR: + case BackendOutputKind::PostSSAMIR: + return makeUnexpected(ErrorCode::InvalidInvocation, + "the selected intermediate stage is not a cache boundary"); + } + return makeUnexpected(ErrorCode::InvalidInvocation, "invalid backend output kind"); +} + +} // namespace rcc::tooling diff --git a/backend/lib/Tooling/BackendInvocation.cpp b/backend/lib/Tooling/BackendInvocation.cpp new file mode 100644 index 0000000..677cd2d --- /dev/null +++ b/backend/lib/Tooling/BackendInvocation.cpp @@ -0,0 +1,500 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Tooling/BackendInvocation.h" + +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/RegisterMask.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetMIRTextInfo.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/Support/SHA256.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::tooling { +namespace { + +void appendField(std::string &Output, std::string_view Name, std::string_view Value) { + Output.append(Name); + Output.push_back('='); + Output.append(std::to_string(Value.size())); + Output.push_back(':'); + Output.append(Value); + Output.push_back('\n'); +} + +template +void appendInteger(std::string &Output, std::string_view Name, IntegerT Value) { + appendField(Output, Name, std::to_string(Value)); +} + +void appendBoolean(std::string &Output, std::string_view Name, bool Value) { + appendField(Output, Name, Value ? "true" : "false"); +} + +[[nodiscard]] bool isValidName(std::string_view Value) noexcept { + return !Value.empty() && std::ranges::all_of(Value, [](unsigned char Character) { + return std::isalnum(Character) != 0 || Character == '-' || Character == '_' || Character == '.'; + }); +} + +[[nodiscard]] Expected canonicalizeOptions(std::vector &Options, + std::string_view Category) { + std::ranges::sort(Options, {}, &BackendOption::Name); + for (std::size_t Index = 0; Index < Options.size(); ++Index) { + if (!isValidName(Options[Index].Name)) { + return makeUnexpected(ErrorCode::InvalidInvocation, + std::string(Category) + " option has an invalid name"); + } + if (Index != 0 && Options[Index - 1].Name == Options[Index].Name) { + return makeUnexpected(ErrorCode::InvalidInvocation, + std::string(Category) + " option names must be unique"); + } + } + return {}; +} + +[[nodiscard]] Expected canonicalizeFeatures(std::vector &Features) { + for (std::string &Feature : Features) { + if (Feature.empty()) + return makeUnexpected(ErrorCode::InvalidInvocation, "target feature cannot be empty"); + if (Feature.front() != '+' && Feature.front() != '-') + Feature.insert(Feature.begin(), '+'); + if (!isValidName(std::string_view(Feature).substr(1))) { + return makeUnexpected(ErrorCode::InvalidInvocation, "target feature has an invalid spelling"); + } + } + std::ranges::sort(Features, [](const std::string &Left, const std::string &Right) { + return std::string_view(Left).substr(1) < std::string_view(Right).substr(1); + }); + for (std::size_t Index = 1; Index < Features.size(); ++Index) { + if (std::string_view(Features[Index - 1]).substr(1) == + std::string_view(Features[Index]).substr(1)) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "target feature names must be unique and non-conflicting"); + } + } + return {}; +} + +[[nodiscard]] bool isSHA256(std::string_view Value) noexcept { + return Value.size() == 64 && std::ranges::all_of(Value, [](unsigned char Character) { + return std::isxdigit(Character) != 0 && + (std::isdigit(Character) != 0 || std::islower(Character) != 0); + }); +} + +void appendOperandDescriptor(std::string &Output, const OperandDescriptor &Operand) { + appendInteger(Output, "operand.kinds", Operand.Kinds.rawValue()); + appendInteger(Output, "operand.role", static_cast(Operand.RegisterRole)); + appendInteger(Output, "operand.reg-class", Operand.RegClass ? Operand.RegClass->value() : 0U); + appendInteger(Output, "operand.reg-bank", Operand.RegBank ? Operand.RegBank->value() : 0U); + appendInteger(Output, "operand.immediate-predicate", + Operand.ImmediatePredicate ? Operand.ImmediatePredicate->value() : 0U); + appendInteger(Output, "operand.memory-role", static_cast(Operand.MemoryRole)); + appendInteger(Output, "operand.tie", + Operand.DefaultTiedOperand ? *Operand.DefaultTiedOperand + 1U : 0U); + appendBoolean(Output, "operand.early-clobber", Operand.EarlyClobber); + appendBoolean(Output, "operand.optional", Operand.Optional); + appendBoolean(Output, "operand.variadic-group", Operand.VariadicGroup); + appendBoolean(Output, "operand.allow-subreg", Operand.AllowSubReg); + appendBoolean(Output, "operand.allow-any-target-flags", Operand.AllowAnyTargetFlags); + appendInteger(Output, "operand.named", Operand.Name ? Operand.Name->value() : 0U); + appendInteger(Output, "operand.target-type", + Operand.TargetOperandType ? Operand.TargetOperandType->value() : 0U); + appendInteger(Output, "operand.target-flags", Operand.AllowedTargetFlags); + appendInteger(Output, "operand.type-kinds", + Operand.TypeConstraint ? Operand.TypeConstraint->Kinds.rawValue() : 0U); + appendInteger(Output, "operand.type-equality", + Operand.TypeConstraint && Operand.TypeConstraint->EqualityGroup + ? static_cast(*Operand.TypeConstraint->EqualityGroup) + 1U + : 0U); +} + +void appendInstructionSet(std::string &Output, const InstructionSet &Instructions) { + appendInteger(Output, "instructions.count", Instructions.descriptors().size()); + for (const InstructionDescriptor &Descriptor : Instructions.descriptors()) { + appendInteger(Output, "instruction.opcode", Descriptor.InstructionOpcode.value()); + appendField(Output, "instruction.name", Descriptor.Name); + appendInteger(Output, "instruction.explicit-defs", Descriptor.NumExplicitDefs); + appendBoolean(Output, "instruction.variadic", Descriptor.Variadic); + appendInteger(Output, "instruction.minimum-variadic", Descriptor.MinimumVariadicRepetitions); + appendBoolean(Output, "instruction.extra-implicit", Descriptor.AllowExtraImplicitOperands); + appendInteger(Output, "instruction.flags", Descriptor.Flags.rawValue()); + appendInteger(Output, "instruction.scheduling-class", Descriptor.SchedulingClass.value()); + appendInteger(Output, "instruction.encoding-category", Descriptor.EncodingCategory.value()); + appendInteger(Output, "instruction.assembly-format", Descriptor.AssemblyFormat.value()); + appendBoolean(Output, "instruction.memory-operands", Descriptor.RequiresMemoryOperands); + appendInteger(Output, "instruction.pseudo-owner", static_cast(Descriptor.Owner)); + appendInteger(Output, "instruction.deadline", static_cast(Descriptor.Deadline)); + appendBoolean(Output, "instruction.explicit-verifier", + Descriptor.VerifyExplicitOperands != nullptr); + appendBoolean(Output, "instruction.semantic-verifier", + Descriptor.VerifyInstructionSemantics != nullptr); + appendInteger(Output, "instruction.operands", Descriptor.Operands.size()); + for (const OperandDescriptor &Operand : Descriptor.Operands) + appendOperandDescriptor(Output, Operand); + appendInteger(Output, "instruction.implicit-defs", Descriptor.ImplicitDefs.size()); + for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitDefs) { + appendInteger(Output, "implicit.register", Implicit.Reg.number()); + appendBoolean(Output, "implicit.early-clobber", Implicit.EarlyClobber); + appendBoolean(Output, "implicit.renamable", Implicit.Renamable); + } + appendInteger(Output, "instruction.implicit-uses", Descriptor.ImplicitUses.size()); + for (const ImplicitRegisterDescriptor &Implicit : Descriptor.ImplicitUses) { + appendInteger(Output, "implicit.register", Implicit.Reg.number()); + appendBoolean(Output, "implicit.early-clobber", Implicit.EarlyClobber); + appendBoolean(Output, "implicit.renamable", Implicit.Renamable); + } + } +} + +void appendRegisterInfo(std::string &Output, const TargetRegisterInfo &Registers, + const TargetMIRTextInfo &MIRText) { + appendInteger(Output, "register-units.count", Registers.registerUnits().size()); + for (const RegisterUnitDesc &Unit : Registers.registerUnits()) { + appendInteger(Output, "register-unit.id", Unit.ID.value()); + appendField(Output, "register-unit.name", Unit.Name); + } + appendInteger(Output, "registers.count", Registers.physicalRegisters().size()); + for (const PhysicalRegisterDesc &Register : Registers.physicalRegisters()) { + appendInteger(Output, "register.id", Register.Reg.number()); + appendField(Output, "register.name", Register.Name); + appendInteger(Output, "register.encoding", Register.Encoding); + appendBoolean(Output, "register.constant", Register.IsConstant); + appendBoolean(Output, "register.artificial", Register.IsArtificial); + appendBoolean(Output, "register.reserved", Register.IsReserved); + for (const RegisterUnitLane &Unit : Register.Units) { + appendInteger(Output, "register.unit", Unit.Unit.value()); + appendInteger(Output, "register.lanes", Unit.Lanes.rawValue()); + } + for (const std::string &Name : Register.AlternativeNames) + appendField(Output, "register.alternative-name", Name); + appendInteger( + Output, "register.dwarf", + MIRText.dwarfRegister(Register.Reg).value_or(std::numeric_limits::max())); + } + appendInteger(Output, "subregister-indices.count", Registers.subRegIndices().size()); + for (const SubRegIndexDesc &Index : Registers.subRegIndices()) { + appendInteger(Output, "subregister-index.id", Index.ID.value()); + appendField(Output, "subregister-index.name", Index.Name); + appendInteger(Output, "subregister-index.lanes", Index.Lanes.rawValue()); + appendInteger(Output, "subregister-index.size", Index.SizeInBits); + appendInteger(Output, "subregister-index.offset", Index.OffsetInBits); + } + appendInteger(Output, "register-classes.count", Registers.registerClasses().size()); + for (const RegisterClassDesc &Class : Registers.registerClasses()) { + appendInteger(Output, "register-class.id", Class.ID.value()); + appendField(Output, "register-class.name", Class.Name); + appendInteger(Output, "register-class.size", Class.SizeInBits); + appendInteger(Output, "register-class.spill-size", Class.SpillSizeInBits); + appendInteger(Output, "register-class.spill-alignment", Class.SpillAlignmentInBits); + appendInteger(Output, "register-class.lanes", Class.Lanes.rawValue()); + appendInteger(Output, "register-class.bank", Class.DefaultBank.value()); + appendBoolean(Output, "register-class.allocatable", Class.Allocatable); + appendInteger(Output, "register-class.copy-cost", Class.CopyCost); + appendBoolean(Output, "register-class.covered-by-subregs", Class.CoveredBySubRegs); + for (PhysicalRegister Register : Class.Registers) + appendInteger(Output, "register-class.register", Register.number()); + for (PhysicalRegister Register : Class.AllocationOrder) + appendInteger(Output, "register-class.allocation-order", Register.number()); + for (SubRegIndex Index : Class.SupportedSubRegs) + appendInteger(Output, "register-class.subregister", Index.value()); + for (RegisterClassID SuperClass : Class.SuperClasses) + appendInteger(Output, "register-class.superclass", SuperClass.value()); + } + appendInteger(Output, "register-banks.count", Registers.registerBanks().size()); + for (const RegisterBankDesc &Bank : Registers.registerBanks()) { + appendInteger(Output, "register-bank.id", Bank.ID.value()); + appendField(Output, "register-bank.name", Bank.Name); + appendInteger(Output, "register-bank.maximum-size", Bank.MaximumSizeInBits); + for (RegisterClassID Class : Bank.RegisterClasses) + appendInteger(Output, "register-bank.class", Class.value()); + } +} + +void appendMIRTextInfo(std::string &Output, const TargetMIRTextInfo &MIRText) { + appendInteger(Output, "mir.direct-flag-mask", MIRText.directOperandFlagMask()); + for (const MIRRegisterMaskDesc &Mask : MIRText.registerMasks()) { + appendField(Output, "mir.register-mask", Mask.Name); + if (Mask.Mask != nullptr) { + for (std::uint64_t Word : Mask.Mask->words()) + appendInteger(Output, "mir.register-mask-word", Word); + } + } + for (const MIRTargetIndexDesc &Index : MIRText.targetIndices()) { + appendField(Output, "mir.target-index-name", Index.Name); + appendInteger(Output, "mir.target-index", Index.Index.value()); + } + for (const MIROperandFlagDesc &Flag : MIRText.directOperandFlags()) { + appendField(Output, "mir.direct-flag-name", Flag.Name); + appendInteger(Output, "mir.direct-flag", Flag.Value); + } + for (const MIROperandFlagDesc &Flag : MIRText.bitmaskOperandFlags()) { + appendField(Output, "mir.bitmask-flag-name", Flag.Name); + appendInteger(Output, "mir.bitmask-flag", Flag.Value); + } + for (const MIRMachineMemOperandFlagDesc &Flag : MIRText.machineMemOperandFlags()) { + appendField(Output, "mir.mmo-flag-name", Flag.Name); + appendInteger(Output, "mir.mmo-flag", Flag.Value); + } + for (const MIRVirtualRegisterFlagDesc &Flag : MIRText.virtualRegisterFlags()) { + appendField(Output, "mir.vreg-flag-name", Flag.Name); + appendInteger(Output, "mir.vreg-flag", Flag.Value); + } + for (const MIRIntrinsicDesc &Intrinsic : MIRText.intrinsics()) { + appendField(Output, "mir.intrinsic-name", Intrinsic.Name); + appendInteger(Output, "mir.intrinsic", Intrinsic.ID.value()); + } + for (StackID ID : MIRText.supportedStackIDs()) + appendInteger(Output, "mir.stack-id", ID.value()); + appendInteger(Output, "mir.function-state-type", + MIRText.functionStateType() ? MIRText.functionStateType()->value() : 0U); +} + +} // namespace + +std::string_view spelling(RelocationModel Value) noexcept { + switch (Value) { + case RelocationModel::Static: + return "static"; + case RelocationModel::PositionIndependent: + return "pic"; + } + return "invalid"; +} + +std::string_view spelling(CodeModel Value) noexcept { + switch (Value) { + case CodeModel::Small: + return "small"; + case CodeModel::Medium: + return "medium"; + case CodeModel::Large: + return "large"; + } + return "invalid"; +} + +std::string_view spelling(OptimizationLevel Value) noexcept { + switch (Value) { + case OptimizationLevel::O0: + return "O0"; + case OptimizationLevel::O1: + return "O1"; + case OptimizationLevel::O2: + return "O2"; + case OptimizationLevel::O3: + return "O3"; + case OptimizationLevel::Os: + return "Os"; + case OptimizationLevel::Oz: + return "Oz"; + } + return "invalid"; +} + +std::string_view spelling(LayoutOwnership Value) noexcept { + switch (Value) { + case LayoutOwnership::NativeObject: + return "native-object"; + case LayoutOwnership::CompilerFinalLayout: + return "compiler-final-layout"; + case LayoutOwnership::ExternalRelaxation: + return "external-relaxation"; + } + return "invalid"; +} + +Expected parseRelocationModel(std::string_view Value) { + if (Value == "static") + return RelocationModel::Static; + if (Value == "pic") + return RelocationModel::PositionIndependent; + return makeUnexpected(ErrorCode::ManifestError, "unknown relocation model"); +} + +Expected parseCodeModel(std::string_view Value) { + if (Value == "small") + return CodeModel::Small; + if (Value == "medium") + return CodeModel::Medium; + if (Value == "large") + return CodeModel::Large; + return makeUnexpected(ErrorCode::ManifestError, "unknown code model"); +} + +Expected parseOptimizationLevel(std::string_view Value) { + if (Value == "O0") + return OptimizationLevel::O0; + if (Value == "O1") + return OptimizationLevel::O1; + if (Value == "O2") + return OptimizationLevel::O2; + if (Value == "O3") + return OptimizationLevel::O3; + if (Value == "Os") + return OptimizationLevel::Os; + if (Value == "Oz") + return OptimizationLevel::Oz; + return makeUnexpected(ErrorCode::ManifestError, "unknown optimization level"); +} + +Expected parseLayoutOwnership(std::string_view Value) { + if (Value == "native-object") + return LayoutOwnership::NativeObject; + if (Value == "compiler-final-layout") + return LayoutOwnership::CompilerFinalLayout; + if (Value == "external-relaxation") + return LayoutOwnership::ExternalRelaxation; + return makeUnexpected(ErrorCode::ManifestError, "unknown layout ownership"); +} + +Expected BackendInvocation::canonicalizeAndVerify() { + if (SchemaVersion != BackendInvocationSchemaVersion) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "backend invocation schema version is unsupported"); + } + if (Triple.empty() || CPU.empty() || TuneCPU.empty() || ABI.empty() || DataLayout.empty() || + MIRReferenceRevision.empty() || GenericOpcodeVersion.empty() || + TargetABILoweringVersion.empty() || BuildFingerprint.empty()) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "backend invocation is missing a required target or version fact"); + } + if (Determinism.ThreadCount == 0) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "backend invocation thread count must be nonzero"); + } + auto FeatureResult = canonicalizeFeatures(Features); + if (!FeatureResult) + return FeatureResult; + auto TargetOptionResult = canonicalizeOptions(TargetOptions, "target"); + if (!TargetOptionResult) + return TargetOptionResult; + auto AssemblerOptionResult = canonicalizeOptions(AssemblerOptions, "assembler"); + if (!AssemblerOptionResult) + return AssemblerOptionResult; + if (PassPipeline.empty() || + std::ranges::any_of(PassPipeline, [](const std::string &Pass) { return Pass.empty(); })) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "backend invocation pass pipeline cannot be empty"); + } + if (Layout == LayoutOwnership::ExternalRelaxation && + (AssemblerFlavor.empty() || AssemblerVersion.empty())) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "external relaxation requires explicit assembler flavor and version"); + } + if (AssemblerFlavor.empty() != AssemblerVersion.empty()) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "assembler flavor and version must be specified together"); + } + std::ranges::sort(EnabledActions); + if (std::ranges::any_of(EnabledActions, + [](const std::string &Action) { return !isValidName(Action); }) || + std::ranges::adjacent_find(EnabledActions) != EnabledActions.end()) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "enabled action names must be valid and unique"); + } + return {}; +} + +std::string BackendInvocation::canonicalText() const { + std::string Output; + appendInteger(Output, "schema", SchemaVersion); + appendField(Output, "triple", Triple); + appendField(Output, "cpu", CPU); + appendField(Output, "tune-cpu", TuneCPU); + for (const std::string &Feature : Features) + appendField(Output, "feature", Feature); + appendField(Output, "abi", ABI); + appendField(Output, "data-layout", DataLayout); + appendField(Output, "relocation-model", spelling(Relocation)); + appendField(Output, "code-model", spelling(Code)); + for (const BackendOption &Option : TargetOptions) { + appendField(Output, "target-option-name", Option.Name); + appendField(Output, "target-option-value", Option.Value); + } + appendField(Output, "layout-ownership", spelling(Layout)); + appendField(Output, "assembler-flavor", AssemblerFlavor); + appendField(Output, "assembler-version", AssemblerVersion); + for (const BackendOption &Option : AssemblerOptions) { + appendField(Output, "assembler-option-name", Option.Name); + appendField(Output, "assembler-option-value", Option.Value); + } + appendField(Output, "optimization-level", spelling(Optimization)); + for (const std::string &Pass : PassPipeline) + appendField(Output, "pass", Pass); + appendInteger(Output, "random-seed", Determinism.RandomSeed); + appendInteger(Output, "thread-count", Determinism.ThreadCount); + appendBoolean(Output, "stable-scheduling", Determinism.StableScheduling); + appendField(Output, "action-fuel", ActionFuel ? std::to_string(*ActionFuel) : "unlimited"); + for (const std::string &Action : EnabledActions) + appendField(Output, "enabled-action", Action); + appendField(Output, "mir-reference-revision", MIRReferenceRevision); + appendField(Output, "generic-opcode-version", GenericOpcodeVersion); + appendField(Output, "target-abi-lowering-version", TargetABILoweringVersion); + appendField(Output, "build-fingerprint", BuildFingerprint); + return Output; +} + +BackendInvocation defaultRV64GCInvocation() { + BackendInvocation Result; + Result.Triple = "riscv64-unknown-linux-gnu"; + Result.CPU = "generic-rv64gc"; + Result.TuneCPU = "generic-rv64gc"; + Result.Features = {"+i", "+m", "+a", "+f", "+d", "+c", "+zicsr", "+zifencei"}; + Result.ABI = "lp64d"; + Result.DataLayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"; + Result.TargetOptions = { + {"function-sections", "false"}, + {"omit-frame-pointer", "true"}, + {"stack-realignment", "false"}, + }; + Result.AssemblerFlavor = "rcc-integrated-gnu"; + Result.AssemblerVersion = "rcc-mc-v1"; + Result.AssemblerOptions = {{"relax", "true"}}; + auto Verified = Result.canonicalizeAndVerify(); + (void)Verified; + return Result; +} + +Expected computeTargetDescriptionFingerprint(const TargetDescription &Target) { + std::string Canonical; + appendField(Canonical, "fingerprint-schema", "rcc-target-fingerprint-v1"); + appendInstructionSet(Canonical, Target.instructions()); + appendRegisterInfo(Canonical, Target.registers(), Target.mirText()); + appendMIRTextInfo(Canonical, Target.mirText()); + return sha256Hex(Canonical); +} + +Expected computeCacheKey(std::string_view CanonicalMIR, + const BackendInvocation &Invocation, + std::string_view TargetFingerprint) { + BackendInvocation CanonicalInvocation = Invocation; + auto Verified = CanonicalInvocation.canonicalizeAndVerify(); + if (!Verified) + return std::unexpected(Verified.error()); + if (!isSHA256(TargetFingerprint)) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "target description fingerprint must be a lowercase SHA-256 digest"); + } + std::string Input; + appendField(Input, "cache-key-schema", "rcc-backend-cache-key-v1"); + appendField(Input, "input-mir-semantic-hash", sha256Hex(CanonicalMIR)); + appendField(Input, "target-description-fingerprint", TargetFingerprint); + appendField(Input, "invocation", CanonicalInvocation.canonicalText()); + return sha256Hex(Input); +} + +} // namespace rcc::tooling diff --git a/backend/lib/Tooling/BackendManifest.cpp b/backend/lib/Tooling/BackendManifest.cpp new file mode 100644 index 0000000..04c410a --- /dev/null +++ b/backend/lib/Tooling/BackendManifest.cpp @@ -0,0 +1,733 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Tooling/BackendManifest.h" + +#include "rcc/Support/YAML.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::tooling { +namespace { + +[[nodiscard]] bool isDigest(std::string_view Value) noexcept { + return Value.size() == 64 && std::ranges::all_of(Value, [](unsigned char Character) { + return (Character >= '0' && Character <= '9') || (Character >= 'a' && Character <= 'f'); + }); +} + +[[nodiscard]] std::string quoted(std::string_view Value) { + constexpr std::string_view Hex = "0123456789abcdef"; + std::string Result{"\""}; + for (char RawCharacter : Value) { + const auto Character = static_cast(RawCharacter); + switch (Character) { + case '\\': + Result.append("\\\\"); + break; + case '"': + Result.append("\\\""); + break; + case '\n': + Result.append("\\n"); + break; + case '\r': + Result.append("\\r"); + break; + case '\t': + Result.append("\\t"); + break; + default: + if (Character < 0x20U || Character == 0x7fU) { + Result.append("\\u00"); + Result.push_back(Hex[Character >> 4U]); + Result.push_back(Hex[Character & 0x0fU]); + } else { + Result.push_back(static_cast(Character)); + } + break; + } + } + Result.push_back('"'); + return Result; +} + +void appendScalar(std::string &Output, std::string_view Indent, std::string_view Name, + std::string_view Value) { + Output.append(Indent); + Output.append(Name); + Output.append(": "); + Output.append(quoted(Value)); + Output.push_back('\n'); +} + +template +void appendInteger(std::string &Output, std::string_view Indent, std::string_view Name, + IntegerT Value) { + Output.append(Indent); + Output.append(Name); + Output.append(": "); + Output.append(std::to_string(Value)); + Output.push_back('\n'); +} + +void appendBoolean(std::string &Output, std::string_view Indent, std::string_view Name, + bool Value) { + Output.append(Indent); + Output.append(Name); + Output.append(Value ? ": true\n" : ": false\n"); +} + +void appendStringSequence(std::string &Output, std::string_view Indent, std::string_view Name, + std::span Values) { + Output.append(Indent); + Output.append(Name); + if (Values.empty()) { + Output.append(": []\n"); + return; + } + Output.append(":\n"); + for (const std::string &Value : Values) { + Output.append(Indent); + Output.append(" - "); + Output.append(quoted(Value)); + Output.push_back('\n'); + } +} + +void appendOptions(std::string &Output, std::string_view Indent, std::string_view Name, + std::span Options) { + Output.append(Indent); + Output.append(Name); + if (Options.empty()) { + Output.append(": []\n"); + return; + } + Output.append(":\n"); + for (const BackendOption &Option : Options) { + Output.append(Indent); + Output.append(" - name: "); + Output.append(quoted(Option.Name)); + Output.push_back('\n'); + appendScalar(Output, std::string(Indent) + " ", "value", Option.Value); + } +} + +void appendInvocation(std::string &Output, const BackendInvocation &Invocation) { + appendInteger(Output, " ", "schema", Invocation.SchemaVersion); + appendScalar(Output, " ", "triple", Invocation.Triple); + appendScalar(Output, " ", "cpu", Invocation.CPU); + appendScalar(Output, " ", "tune_cpu", Invocation.TuneCPU); + appendStringSequence(Output, " ", "features", Invocation.Features); + appendScalar(Output, " ", "abi", Invocation.ABI); + appendScalar(Output, " ", "data_layout", Invocation.DataLayout); + appendScalar(Output, " ", "relocation_model", spelling(Invocation.Relocation)); + appendScalar(Output, " ", "code_model", spelling(Invocation.Code)); + appendOptions(Output, " ", "target_options", Invocation.TargetOptions); + appendScalar(Output, " ", "layout_ownership", spelling(Invocation.Layout)); + appendScalar(Output, " ", "assembler_flavor", Invocation.AssemblerFlavor); + appendScalar(Output, " ", "assembler_version", Invocation.AssemblerVersion); + appendOptions(Output, " ", "assembler_options", Invocation.AssemblerOptions); + appendScalar(Output, " ", "optimization_level", spelling(Invocation.Optimization)); + appendStringSequence(Output, " ", "pass_pipeline", Invocation.PassPipeline); + appendInteger(Output, " ", "random_seed", Invocation.Determinism.RandomSeed); + appendInteger(Output, " ", "thread_count", Invocation.Determinism.ThreadCount); + appendBoolean(Output, " ", "stable_scheduling", Invocation.Determinism.StableScheduling); + if (Invocation.ActionFuel) { + appendInteger(Output, " ", "action_fuel", *Invocation.ActionFuel); + } else { + Output.append(" action_fuel: null\n"); + } + appendStringSequence(Output, " ", "enabled_actions", Invocation.EnabledActions); + appendScalar(Output, " ", "mir_reference_revision", Invocation.MIRReferenceRevision); + appendScalar(Output, " ", "generic_opcode_version", Invocation.GenericOpcodeVersion); + appendScalar(Output, " ", "target_abi_lowering_version", Invocation.TargetABILoweringVersion); + appendScalar(Output, " ", "build_fingerprint", Invocation.BuildFingerprint); +} + +void appendOptionalInteger(std::string &Output, std::string_view Indent, std::string_view Name, + std::optional Value) { + if (Value) { + appendInteger(Output, Indent, Name, *Value); + } else { + Output.append(Indent); + Output.append(Name); + Output.append(": null\n"); + } +} + +class ManifestParser final { +public: + ManifestParser(std::string SourceName, std::string SourceText) + : Parsed(yaml::parse(std::move(SourceName), std::move(SourceText))) {} + + [[nodiscard]] Expected run() { + if (!Parsed.succeeded()) { + const std::span Diagnostics = Parsed.diagnostics(); + return makeUnexpected(ErrorCode::ManifestError, Diagnostics.empty() + ? "manifest YAML parsing failed" + : Diagnostics.front().Message); + } + Stream = Parsed.stream(); + if (Stream == nullptr || Stream->documents().size() != 1) { + return makeUnexpected(ErrorCode::ManifestError, + "manifest must contain exactly one YAML document"); + } + const yaml::Node *Root = Stream->node(Stream->documents().front().Root); + auto RootMapping = requireMapping(Root, "manifest root"); + if (!RootMapping) + return std::unexpected(RootMapping.error()); + auto RootKeys = + rejectUnknown(**RootMapping, + {"schema", "kind", "artifact", "input_mir_hash", "target_fingerprint", + "cache_key", "artifact_hash", "invocation", "diagnostics"}, + "manifest root"); + if (!RootKeys) + return std::unexpected(RootKeys.error()); + + BackendManifest Result; + auto Schema = requiredUnsigned(**RootMapping, "schema", "manifest root"); + auto Kind = requiredScalar(**RootMapping, "kind", "manifest root"); + auto Artifact = requiredScalar(**RootMapping, "artifact", "manifest root"); + auto InputHash = requiredScalar(**RootMapping, "input_mir_hash", "manifest root"); + auto TargetHash = requiredScalar(**RootMapping, "target_fingerprint", "manifest root"); + auto CacheKey = requiredScalar(**RootMapping, "cache_key", "manifest root"); + const yaml::Node *Invocation = mappingValue(**RootMapping, "invocation"); + const yaml::Node *Diagnostics = mappingValue(**RootMapping, "diagnostics"); + if (!Schema || !Kind || !Artifact || !InputHash || !TargetHash || !CacheKey) + return firstError(Schema, Kind, Artifact, InputHash, TargetHash, CacheKey); + Result.SchemaVersion = *Schema; + auto ParsedKind = parseManifestKind(*Kind); + if (!ParsedKind) + return std::unexpected(ParsedKind.error()); + Result.Kind = *ParsedKind; + auto ParsedArtifact = parseArtifactKind(*Artifact); + if (!ParsedArtifact) + return std::unexpected(ParsedArtifact.error()); + Result.Artifact = *ParsedArtifact; + Result.InputMIRHash = std::move(*InputHash); + Result.TargetFingerprint = std::move(*TargetHash); + Result.CacheKey = std::move(*CacheKey); + if (const yaml::Node *ArtifactHash = mappingValue(**RootMapping, "artifact_hash"); + ArtifactHash != nullptr && ArtifactHash->kind() != yaml::NodeKind::Null) { + auto Value = scalar(ArtifactHash, "manifest artifact_hash"); + if (!Value) + return std::unexpected(Value.error()); + Result.ArtifactHash = std::move(*Value); + } + auto ParsedInvocation = parseInvocation(Invocation); + if (!ParsedInvocation) + return std::unexpected(ParsedInvocation.error()); + Result.Invocation = std::move(*ParsedInvocation); + auto ParsedDiagnostics = parseDiagnostics(Diagnostics); + if (!ParsedDiagnostics) + return std::unexpected(ParsedDiagnostics.error()); + Result.Diagnostics = std::move(*ParsedDiagnostics); + auto Verification = Result.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Result; + } + +private: + template + [[nodiscard]] Expected firstError(const Results &...Values) const { + std::optional Failure; + const auto Capture = [&Failure](const auto &Value) { + if (!Value && !Failure) + Failure = Value.error(); + }; + (Capture(Values), ...); + if (!Failure) { + return makeUnexpected(ErrorCode::InternalError, + "manifest parser requested a missing field error without a failure"); + } + return std::unexpected(*Failure); + } + + [[nodiscard]] Expected requireMapping(const yaml::Node *Node, + std::string_view Context) const { + if (Node == nullptr || Node->kind() != yaml::NodeKind::Mapping) { + return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + " must be a mapping"); + } + return Node; + } + + [[nodiscard]] Expected rejectUnknown(const yaml::Node &Node, + std::initializer_list Allowed, + std::string_view Context) const { + std::unordered_set Seen; + for (const yaml::MappingEntry &Entry : Node.mapping()) { + if (std::ranges::find(Allowed, Entry.Key) == Allowed.end()) { + return makeUnexpected(ErrorCode::ManifestError, + std::string(Context) + " contains unknown key '" + Entry.Key + "'"); + } + if (!Seen.insert(Entry.Key).second) { + return makeUnexpected(ErrorCode::ManifestError, + std::string(Context) + " contains duplicate key '" + Entry.Key + "'"); + } + } + return {}; + } + + [[nodiscard]] const yaml::Node *mappingValue(const yaml::Node &Node, + std::string_view Name) const noexcept { + const std::optional ID = Node.mappingValue(Name); + return ID && Stream != nullptr ? Stream->node(*ID) : nullptr; + } + + [[nodiscard]] Expected scalar(const yaml::Node *Node, + std::string_view Context) const { + if (Node == nullptr || Node->kind() != yaml::NodeKind::Scalar) { + return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + " must be a scalar"); + } + return std::string(Node->scalar()); + } + + [[nodiscard]] Expected requiredScalar(const yaml::Node &Node, std::string_view Name, + std::string_view Context) const { + const yaml::Node *Value = mappingValue(Node, Name); + if (Value == nullptr) { + return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + + " is missing required key '" + + std::string(Name) + "'"); + } + return scalar(Value, std::string(Context) + "." + std::string(Name)); + } + + template + [[nodiscard]] Expected unsignedValue(const yaml::Node *Node, + std::string_view Context) const { + auto Text = scalar(Node, Context); + if (!Text) + return std::unexpected(Text.error()); + IntegerT Result = 0; + const char *Begin = Text->data(); + const char *End = Begin + Text->size(); + const auto [Position, ErrorValue] = std::from_chars(Begin, End, Result); + if (ErrorValue != std::errc{} || Position != End) { + return makeUnexpected(ErrorCode::ManifestError, + std::string(Context) + " must be an unsigned integer"); + } + return Result; + } + + template + [[nodiscard]] Expected requiredUnsigned(const yaml::Node &Node, std::string_view Name, + std::string_view Context) const { + const yaml::Node *Value = mappingValue(Node, Name); + if (Value == nullptr) { + return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + + " is missing required key '" + + std::string(Name) + "'"); + } + return unsignedValue(Value, std::string(Context) + "." + std::string(Name)); + } + + [[nodiscard]] Expected requiredBoolean(const yaml::Node &Node, std::string_view Name, + std::string_view Context) const { + auto Value = requiredScalar(Node, Name, Context); + if (!Value) + return std::unexpected(Value.error()); + if (*Value == "true") + return true; + if (*Value == "false") + return false; + return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + "." + std::string(Name) + + " must be true or false"); + } + + [[nodiscard]] Expected> stringSequence(const yaml::Node *Node, + std::string_view Context) const { + if (Node == nullptr || Node->kind() != yaml::NodeKind::Sequence) { + return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + " must be a sequence"); + } + std::vector Result; + Result.reserve(Node->sequence().size()); + for (yaml::NodeID ID : Node->sequence()) { + auto Value = scalar(Stream->node(ID), Context); + if (!Value) + return std::unexpected(Value.error()); + Result.push_back(std::move(*Value)); + } + return Result; + } + + [[nodiscard]] Expected> parseOptions(const yaml::Node *Node, + std::string_view Context) const { + if (Node == nullptr || Node->kind() != yaml::NodeKind::Sequence) { + return makeUnexpected(ErrorCode::ManifestError, std::string(Context) + " must be a sequence"); + } + std::vector Result; + Result.reserve(Node->sequence().size()); + for (yaml::NodeID ID : Node->sequence()) { + auto Entry = requireMapping(Stream->node(ID), Context); + if (!Entry) + return std::unexpected(Entry.error()); + auto Keys = rejectUnknown(**Entry, {"name", "value"}, Context); + if (!Keys) + return std::unexpected(Keys.error()); + auto Name = requiredScalar(**Entry, "name", Context); + auto Value = requiredScalar(**Entry, "value", Context); + if (!Name) + return std::unexpected(Name.error()); + if (!Value) + return std::unexpected(Value.error()); + Result.push_back({std::move(*Name), std::move(*Value)}); + } + return Result; + } + + [[nodiscard]] Expected parseInvocation(const yaml::Node *Node) const { + auto Mapping = requireMapping(Node, "manifest invocation"); + if (!Mapping) + return std::unexpected(Mapping.error()); + auto Keys = rejectUnknown(**Mapping, + {"schema", + "triple", + "cpu", + "tune_cpu", + "features", + "abi", + "data_layout", + "relocation_model", + "code_model", + "target_options", + "layout_ownership", + "assembler_flavor", + "assembler_version", + "assembler_options", + "optimization_level", + "pass_pipeline", + "random_seed", + "thread_count", + "stable_scheduling", + "action_fuel", + "enabled_actions", + "mir_reference_revision", + "generic_opcode_version", + "target_abi_lowering_version", + "build_fingerprint"}, + "manifest invocation"); + if (!Keys) + return std::unexpected(Keys.error()); + + BackendInvocation Result; + auto Schema = requiredUnsigned(**Mapping, "schema", "manifest invocation"); + auto Triple = requiredScalar(**Mapping, "triple", "manifest invocation"); + auto CPU = requiredScalar(**Mapping, "cpu", "manifest invocation"); + auto TuneCPU = requiredScalar(**Mapping, "tune_cpu", "manifest invocation"); + auto Features = stringSequence(mappingValue(**Mapping, "features"), "invocation.features"); + auto ABI = requiredScalar(**Mapping, "abi", "manifest invocation"); + auto DataLayout = requiredScalar(**Mapping, "data_layout", "manifest invocation"); + auto Relocation = requiredScalar(**Mapping, "relocation_model", "manifest invocation"); + auto Code = requiredScalar(**Mapping, "code_model", "manifest invocation"); + auto TargetOptions = + parseOptions(mappingValue(**Mapping, "target_options"), "invocation.target_options"); + auto Layout = requiredScalar(**Mapping, "layout_ownership", "manifest invocation"); + auto AssemblerFlavor = requiredScalar(**Mapping, "assembler_flavor", "manifest invocation"); + auto AssemblerVersion = requiredScalar(**Mapping, "assembler_version", "manifest invocation"); + auto AssemblerOptions = + parseOptions(mappingValue(**Mapping, "assembler_options"), "invocation.assembler_options"); + auto Optimization = requiredScalar(**Mapping, "optimization_level", "manifest invocation"); + auto Pipeline = + stringSequence(mappingValue(**Mapping, "pass_pipeline"), "invocation.pass_pipeline"); + auto RandomSeed = + requiredUnsigned(**Mapping, "random_seed", "manifest invocation"); + auto ThreadCount = + requiredUnsigned(**Mapping, "thread_count", "manifest invocation"); + auto Stable = requiredBoolean(**Mapping, "stable_scheduling", "manifest invocation"); + auto Actions = + stringSequence(mappingValue(**Mapping, "enabled_actions"), "invocation.enabled_actions"); + auto MIRRevision = requiredScalar(**Mapping, "mir_reference_revision", "manifest invocation"); + auto GenericVersion = + requiredScalar(**Mapping, "generic_opcode_version", "manifest invocation"); + auto ABIVersion = + requiredScalar(**Mapping, "target_abi_lowering_version", "manifest invocation"); + auto Build = requiredScalar(**Mapping, "build_fingerprint", "manifest invocation"); + + if (!Schema || !Triple || !CPU || !TuneCPU || !Features || !ABI || !DataLayout || !Relocation || + !Code || !TargetOptions || !Layout || !AssemblerFlavor || !AssemblerVersion || + !AssemblerOptions || !Optimization || !Pipeline || !RandomSeed || !ThreadCount || !Stable || + !Actions || !MIRRevision || !GenericVersion || !ABIVersion || !Build) { + return makeUnexpected(ErrorCode::ManifestError, + "manifest invocation is missing or has an invalid required field"); + } + Result.SchemaVersion = *Schema; + Result.Triple = std::move(*Triple); + Result.CPU = std::move(*CPU); + Result.TuneCPU = std::move(*TuneCPU); + Result.Features = std::move(*Features); + Result.ABI = std::move(*ABI); + Result.DataLayout = std::move(*DataLayout); + auto ParsedRelocation = parseRelocationModel(*Relocation); + auto ParsedCode = parseCodeModel(*Code); + auto ParsedLayout = parseLayoutOwnership(*Layout); + auto ParsedOptimization = parseOptimizationLevel(*Optimization); + if (!ParsedRelocation || !ParsedCode || !ParsedLayout || !ParsedOptimization) { + return makeUnexpected(ErrorCode::ManifestError, + "manifest invocation contains an unknown enum value"); + } + Result.Relocation = *ParsedRelocation; + Result.Code = *ParsedCode; + Result.TargetOptions = std::move(*TargetOptions); + Result.Layout = *ParsedLayout; + Result.AssemblerFlavor = std::move(*AssemblerFlavor); + Result.AssemblerVersion = std::move(*AssemblerVersion); + Result.AssemblerOptions = std::move(*AssemblerOptions); + Result.Optimization = *ParsedOptimization; + Result.PassPipeline = std::move(*Pipeline); + Result.Determinism = { + .RandomSeed = *RandomSeed, + .ThreadCount = *ThreadCount, + .StableScheduling = *Stable, + }; + const yaml::Node *Fuel = mappingValue(**Mapping, "action_fuel"); + if (Fuel == nullptr) { + return makeUnexpected(ErrorCode::ManifestError, + "manifest invocation is missing required key 'action_fuel'"); + } + if (Fuel->kind() != yaml::NodeKind::Null) { + auto Value = unsignedValue(Fuel, "invocation.action_fuel"); + if (!Value) + return std::unexpected(Value.error()); + Result.ActionFuel = *Value; + } + Result.EnabledActions = std::move(*Actions); + Result.MIRReferenceRevision = std::move(*MIRRevision); + Result.GenericOpcodeVersion = std::move(*GenericVersion); + Result.TargetABILoweringVersion = std::move(*ABIVersion); + Result.BuildFingerprint = std::move(*Build); + auto Verified = Result.canonicalizeAndVerify(); + if (!Verified) + return std::unexpected(Verified.error()); + return Result; + } + + [[nodiscard]] Expected> + parseDiagnostics(const yaml::Node *Node) const { + if (Node == nullptr || Node->kind() != yaml::NodeKind::Sequence) { + return makeUnexpected(ErrorCode::ManifestError, "manifest diagnostics must be a sequence"); + } + std::vector Result; + for (yaml::NodeID ID : Node->sequence()) { + auto Mapping = requireMapping(Stream->node(ID), "manifest diagnostic"); + if (!Mapping) + return std::unexpected(Mapping.error()); + auto Keys = + rejectUnknown(**Mapping, + {"category", "stage", "pass", "function", "block", "instruction", "operand", + "source_location", "target_constraint", "rule", "message"}, + "manifest diagnostic"); + if (!Keys) + return std::unexpected(Keys.error()); + StructuredDiagnostic Diagnostic; + auto Category = requiredScalar(**Mapping, "category", "manifest diagnostic"); + auto Stage = requiredScalar(**Mapping, "stage", "manifest diagnostic"); + auto Pass = requiredScalar(**Mapping, "pass", "manifest diagnostic"); + auto Function = requiredScalar(**Mapping, "function", "manifest diagnostic"); + auto Source = requiredScalar(**Mapping, "source_location", "manifest diagnostic"); + auto Constraint = requiredScalar(**Mapping, "target_constraint", "manifest diagnostic"); + auto Rule = requiredScalar(**Mapping, "rule", "manifest diagnostic"); + auto Message = requiredScalar(**Mapping, "message", "manifest diagnostic"); + if (!Category || !Stage || !Pass || !Function || !Source || !Constraint || !Rule || + !Message) { + return makeUnexpected(ErrorCode::ManifestError, + "manifest diagnostic is missing a required string field"); + } + Diagnostic.Category = std::move(*Category); + Diagnostic.Stage = std::move(*Stage); + Diagnostic.Pass = std::move(*Pass); + Diagnostic.Function = std::move(*Function); + Diagnostic.SourceLocation = std::move(*Source); + Diagnostic.TargetConstraint = std::move(*Constraint); + Diagnostic.Rule = std::move(*Rule); + Diagnostic.Message = std::move(*Message); + auto OptionalValue = [&](std::string_view Name, auto &Destination) -> Expected { + const yaml::Node *Value = mappingValue(**Mapping, Name); + if (Value == nullptr) { + return makeUnexpected(ErrorCode::ManifestError, + "manifest diagnostic is missing key '" + std::string(Name) + "'"); + } + if (Value->kind() == yaml::NodeKind::Null) + return {}; + using ValueT = typename std::remove_reference_t::value_type; + auto ParsedValue = unsignedValue(Value, Name); + if (!ParsedValue) + return std::unexpected(ParsedValue.error()); + Destination = *ParsedValue; + return {}; + }; + auto Block = OptionalValue("block", Diagnostic.Block); + auto Instruction = OptionalValue("instruction", Diagnostic.Instruction); + auto Operand = OptionalValue("operand", Diagnostic.Operand); + if (!Block) + return std::unexpected(Block.error()); + if (!Instruction) + return std::unexpected(Instruction.error()); + if (!Operand) + return std::unexpected(Operand.error()); + Result.push_back(std::move(Diagnostic)); + } + return Result; + } + + [[nodiscard]] Expected parseManifestKind(std::string_view Value) const { + if (Value == "cache") + return ManifestKind::Cache; + if (Value == "reproducer") + return ManifestKind::Reproducer; + return makeUnexpected(ErrorCode::ManifestError, "unknown manifest kind"); + } + + [[nodiscard]] Expected parseArtifactKind(std::string_view Value) const { + if (Value == "prelegalizer-mir") + return ArtifactKind::PreLegalizerMIR; + if (Value == "legalized-mir") + return ArtifactKind::LegalizedMIR; + if (Value == "regbank-mir") + return ArtifactKind::RegisterBankMIR; + if (Value == "selected-mir") + return ArtifactKind::SelectedMIR; + if (Value == "post-ssa-mir") + return ArtifactKind::PostSSAMIR; + if (Value == "post-ra-mir") + return ArtifactKind::PostRegisterAllocationMIR; + if (Value == "final-mir") + return ArtifactKind::FinalMIR; + if (Value == "assembly") + return ArtifactKind::Assembly; + if (Value == "object") + return ArtifactKind::RelocatableObject; + return makeUnexpected(ErrorCode::ManifestError, "unknown artifact kind"); + } + + yaml::ParseResult Parsed; + const yaml::Stream *Stream = nullptr; +}; + +} // namespace + +std::string_view spelling(ManifestKind Value) noexcept { + switch (Value) { + case ManifestKind::Cache: + return "cache"; + case ManifestKind::Reproducer: + return "reproducer"; + } + return "invalid"; +} + +std::string_view spelling(ArtifactKind Value) noexcept { + switch (Value) { + case ArtifactKind::PreLegalizerMIR: + return "prelegalizer-mir"; + case ArtifactKind::LegalizedMIR: + return "legalized-mir"; + case ArtifactKind::RegisterBankMIR: + return "regbank-mir"; + case ArtifactKind::SelectedMIR: + return "selected-mir"; + case ArtifactKind::PostSSAMIR: + return "post-ssa-mir"; + case ArtifactKind::PostRegisterAllocationMIR: + return "post-ra-mir"; + case ArtifactKind::FinalMIR: + return "final-mir"; + case ArtifactKind::Assembly: + return "assembly"; + case ArtifactKind::RelocatableObject: + return "object"; + } + return "invalid"; +} + +Expected BackendManifest::verify() const { + if (SchemaVersion != BackendManifestSchemaVersion) { + return makeUnexpected(ErrorCode::ManifestError, "manifest schema version is unsupported"); + } + BackendInvocation Canonical = Invocation; + auto InvocationVerification = Canonical.canonicalizeAndVerify(); + if (!InvocationVerification) + return std::unexpected(InvocationVerification.error()); + if (Canonical != Invocation) { + return makeUnexpected(ErrorCode::ManifestError, "manifest invocation is not in canonical form"); + } + if (!isDigest(InputMIRHash) || !isDigest(TargetFingerprint) || !isDigest(CacheKey) || + (ArtifactHash && !isDigest(*ArtifactHash))) { + return makeUnexpected(ErrorCode::ManifestError, + "manifest hashes must be lowercase SHA-256 digests"); + } + for (const StructuredDiagnostic &Diagnostic : Diagnostics) { + if (Diagnostic.Category.empty() || Diagnostic.Message.empty()) { + return makeUnexpected(ErrorCode::ManifestError, + "manifest diagnostic category and message cannot be empty"); + } + } + return {}; +} + +Expected printBackendManifest(const BackendManifest &Manifest) { + auto Verification = Manifest.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + std::string Output; + Output.append("---\n"); + appendInteger(Output, "", "schema", Manifest.SchemaVersion); + appendScalar(Output, "", "kind", spelling(Manifest.Kind)); + appendScalar(Output, "", "artifact", spelling(Manifest.Artifact)); + appendScalar(Output, "", "input_mir_hash", Manifest.InputMIRHash); + appendScalar(Output, "", "target_fingerprint", Manifest.TargetFingerprint); + appendScalar(Output, "", "cache_key", Manifest.CacheKey); + if (Manifest.ArtifactHash) { + appendScalar(Output, "", "artifact_hash", *Manifest.ArtifactHash); + } else { + Output.append("artifact_hash: null\n"); + } + Output.append("invocation:\n"); + appendInvocation(Output, Manifest.Invocation); + if (Manifest.Diagnostics.empty()) { + Output.append("diagnostics: []\n"); + return Output; + } + Output.append("diagnostics:\n"); + for (const StructuredDiagnostic &Diagnostic : Manifest.Diagnostics) { + Output.append(" - category: "); + Output.append(quoted(Diagnostic.Category)); + Output.push_back('\n'); + appendScalar(Output, " ", "stage", Diagnostic.Stage); + appendScalar(Output, " ", "pass", Diagnostic.Pass); + appendScalar(Output, " ", "function", Diagnostic.Function); + appendOptionalInteger(Output, " ", "block", Diagnostic.Block); + appendOptionalInteger(Output, " ", "instruction", Diagnostic.Instruction); + if (Diagnostic.Operand) { + appendInteger(Output, " ", "operand", *Diagnostic.Operand); + } else { + Output.append(" operand: null\n"); + } + appendScalar(Output, " ", "source_location", Diagnostic.SourceLocation); + appendScalar(Output, " ", "target_constraint", Diagnostic.TargetConstraint); + appendScalar(Output, " ", "rule", Diagnostic.Rule); + appendScalar(Output, " ", "message", Diagnostic.Message); + } + return Output; +} + +Expected parseBackendManifest(std::string SourceName, std::string SourceText) { + return ManifestParser(std::move(SourceName), std::move(SourceText)).run(); +} + +} // namespace rcc::tooling diff --git a/backend/lib/Tooling/CMakeLists.txt b/backend/lib/Tooling/CMakeLists.txt new file mode 100644 index 0000000..bf20e37 --- /dev/null +++ b/backend/lib/Tooling/CMakeLists.txt @@ -0,0 +1,76 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +set( + RCCBackendToolingSources + BackendInvocation.cpp + BackendManifest.cpp + BackendDriver.cpp + ArtifactStore.cpp + MIRFuzzer.cpp + MIRReducer.cpp + PipelineDriver.cpp +) +set( + RCCBackendToolingHeaders + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/BackendInvocation.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/BackendManifest.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/BackendDriver.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/ArtifactStore.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/MIRFuzzer.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/MIRReducer.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Tooling/PipelineDriver.h" +) + +add_library(RCCBackendTooling ${RCCBackendToolingSources}) +add_library(RCCBackend::Tooling ALIAS RCCBackendTooling) + +rcc_backend_configure_target(RCCBackendTooling) + +target_include_directories( + RCCBackendTooling + PUBLIC + "$" + "$" +) +target_link_libraries( + RCCBackendTooling + PUBLIC + RCCBackend::MIR + RCCBackend::RISCV + RCCBackend::Support +) + +set_target_properties( + RCCBackendTooling + PROPERTIES + EXPORT_NAME Tooling + OUTPUT_NAME RCCBackendTooling +) + +install( + TARGETS RCCBackendTooling + EXPORT RCCBackendTargets + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) + +rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/BackendInvocation.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/BackendManifest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/BackendDriver.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ArtifactStore.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRFuzzer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRReducer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/PipelineDriver.cpp" + ${RCCBackendToolingHeaders} +) +rcc_backend_register_tidy_files( + "${CMAKE_CURRENT_SOURCE_DIR}/BackendInvocation.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/BackendManifest.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/BackendDriver.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/ArtifactStore.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRFuzzer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/MIRReducer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/PipelineDriver.cpp" +) diff --git a/backend/lib/Tooling/MIRFuzzer.cpp b/backend/lib/Tooling/MIRFuzzer.cpp new file mode 100644 index 0000000..e8a1a7a --- /dev/null +++ b/backend/lib/Tooling/MIRFuzzer.cpp @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Tooling/MIRFuzzer.h" + +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/MachineVerifier.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/MIR/MIRDocument.h" +#include "rcc/Target/RISCV/RISCVPipeline.h" +#include "rcc/Tooling/ArtifactStore.h" +#include "rcc/Tooling/BackendDriver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::tooling { +namespace { + +class DeterministicRandom final { +public: + explicit DeterministicRandom(std::uint64_t Seed) noexcept + : State(Seed == 0 ? 0x243f6a8885a308d3ULL : Seed) {} + + [[nodiscard]] std::uint64_t next() noexcept { + State ^= State >> 12U; + State ^= State << 25U; + State ^= State >> 27U; + return State * 0x2545f4914f6cdd1dULL; + } + + [[nodiscard]] std::size_t index(std::size_t Bound) noexcept { + return Bound == 0 ? 0 : static_cast(next() % Bound); + } + +private: + std::uint64_t State; +}; + +[[nodiscard]] std::vector defaultLayers() { + return { + MIRFuzzLayer::Parser, MIRFuzzLayer::BuilderStructural, MIRFuzzLayer::PropertyStage, + MIRFuzzLayer::Mutation, MIRFuzzLayer::Pipeline, MIRFuzzLayer::RestrictedSemantics, + }; +} + +[[nodiscard]] Error fuzzFailure(MIRFuzzLayer Layer, std::size_t Iteration, const Error &Failure) { + return Error(ErrorCode::FuzzingFailure, + std::string(spelling(Layer)) + " fuzz case " + std::to_string(Iteration) + + " violated an invariant: " + std::string(Failure.message())); +} + +[[nodiscard]] std::string byteMutation(std::string_view Input, DeterministicRandom &Random, + std::size_t MaximumSize) { + std::string Result(Input); + switch (Random.index(5)) { + case 0: + if (!Result.empty()) + Result[Random.index(Result.size())] = static_cast(Random.next() & 0xffU); + break; + case 1: + if (!Result.empty()) + Result.erase(Random.index(Result.size()), 1); + break; + case 2: + if (Result.size() < MaximumSize) + Result.insert(Result.begin() + static_cast(Random.index(Result.size() + 1U)), + static_cast(Random.next() & 0xffU)); + break; + case 3: + if (!Result.empty()) + Result.resize(Random.index(Result.size())); + break; + case 4: + if (!Result.empty() && Result.size() < MaximumSize) { + const std::size_t Begin = Random.index(Result.size()); + const std::size_t Length = std::min(Random.index(32U) + 1U, Result.size() - Begin); + const std::string Fragment = Result.substr(Begin, Length); + const std::size_t Available = MaximumSize - Result.size(); + Result.append(Fragment.substr(0, Available)); + } + break; + default: + break; + } + return Result; +} + +[[nodiscard]] std::string structuralTextMutation(std::string_view Input, + DeterministicRandom &Random) { + std::string Result(Input); + std::vector LineStarts{0}; + for (std::size_t Position = 0; Position < Result.size(); ++Position) { + if (Result[Position] == '\n' && Position + 1U < Result.size()) + LineStarts.push_back(Position + 1U); + } + if (LineStarts.empty()) + return Result; + const std::size_t LineIndex = Random.index(LineStarts.size()); + const std::size_t Begin = LineStarts[LineIndex]; + const std::size_t End = Result.find('\n', Begin); + const std::size_t LineEnd = End == std::string::npos ? Result.size() : End + 1U; + switch (Random.index(4)) { + case 0: + Result.erase(Begin, LineEnd - Begin); + break; + case 1: + Result.insert(LineEnd, Result.substr(Begin, LineEnd - Begin)); + break; + case 2: { + const std::size_t Digit = Result.find_first_of("0123456789", Begin); + if (Digit < LineEnd) + Result[Digit] = static_cast('0' + Random.index(10)); + break; + } + case 3: { + const std::size_t Percent = Result.find('%', Begin); + if (Percent < LineEnd && Percent + 1U < Result.size()) + Result[Percent + 1U] = static_cast('0' + Random.index(10)); + break; + } + default: + break; + } + return Result; +} + +[[nodiscard]] Expected verifyCanonicalRoundTrip(std::string Text, + const TargetDescription &Target, + const BackendInvocation &Invocation) { + auto First = parseAndCanonicalizeMIR("fuzz-round-trip.mir", std::move(Text), Target, Invocation); + if (!First) + return std::unexpected(First.error()); + auto Second = parseAndCanonicalizeMIR("fuzz-round-trip-2.mir", First->Text, Target, Invocation); + if (!Second) + return std::unexpected(Second.error()); + if (First->Text != Second->Text) { + return makeUnexpected(ErrorCode::FuzzingFailure, + "successful MIR round-trip is not structurally deterministic"); + } + return {}; +} + +[[nodiscard]] Expected parserCase(std::string_view Canonical, DeterministicRandom &Random, + const TargetDescription &Target, + const BackendInvocation &Invocation, + std::size_t MaximumSize) { + std::string Mutated = byteMutation(Canonical, Random, MaximumSize); + auto Parsed = parseAndCanonicalizeMIR("parser-fuzz.mir", Mutated, Target, Invocation); + if (!Parsed) + return false; + auto RoundTrip = verifyCanonicalRoundTrip(std::move(Parsed->Text), Target, Invocation); + if (!RoundTrip) + return std::unexpected(RoundTrip.error()); + return true; +} + +[[nodiscard]] Expected builderCase(std::string_view Canonical, + const TargetDescription &Target, + const BackendInvocation &Invocation) { + auto Parsed = + parseAndCanonicalizeMIR("builder-fuzz.mir", std::string(Canonical), Target, Invocation); + if (!Parsed) + return std::unexpected(Parsed.error()); + MachineFunction *Function = Parsed->Document.unit().functionAt(0); + if (Function == nullptr || stageForFunction(*Function) != BackendStage::PreLegalizerMIR) + return false; + MachineBasicBlock *Block = Function->firstBlock(); + if (Block == nullptr) + return false; + auto Type = LLT::makeInteger(64); + if (!Type) + return std::unexpected(Type.error()); + auto Register = Function->registerInfo().createVirtualRegister(*Type); + if (!Register) + return std::unexpected(Register.error()); + MachineIRBuilder Builder(*Function, Target); + Expected InsertPoint = Block->firstInstruction() == nullptr + ? Builder.setInsertPoint(*Block) + : Builder.setInsertPointBefore(*Block->firstInstruction()); + if (!InsertPoint) + return std::unexpected(InsertPoint.error()); + auto Pending = Builder.buildInstr(opcode(GenericOpcode::G_IMPLICIT_DEF)); + if (!Pending) + return std::unexpected(Pending.error()); + auto Added = Pending->addDef(*Register); + if (!Added) + return std::unexpected(Added.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + auto Verified = MachineVerifier::verify(*Function, Target); + if (!Verified) + return std::unexpected(Verified.error()); + + mir::MIRDocumentPrinterOptions PrintOptions; + PrintOptions.Mode = mir::MIPrintMode::Complete; + PrintOptions.EmitEmbeddedIR = true; + PrintOptions.DataLayout = Invocation.DataLayout; + PrintOptions.DefaultPointerSizeInBits = 64; + auto Printed = mir::printMIRDocument(Parsed->Document, Target, std::move(PrintOptions)); + if (!Printed) + return std::unexpected(Printed.error()); + auto RoundTrip = verifyCanonicalRoundTrip(std::move(*Printed), Target, Invocation); + if (!RoundTrip) + return std::unexpected(RoundTrip.error()); + return true; +} + +[[nodiscard]] BackendOutputKind outputForStage(BackendStage Stage) noexcept { + return static_cast(static_cast(Stage)); +} + +[[nodiscard]] Expected propertyCase(std::string_view Canonical, DeterministicRandom &Random, + const TargetDescription &Target, + const BackendInvocation &Invocation) { + auto Parsed = + parseAndCanonicalizeMIR("property-fuzz.mir", std::string(Canonical), Target, Invocation); + if (!Parsed) + return std::unexpected(Parsed.error()); + const auto Stage = static_cast(Random.index(7U)); + const MachineFunction *Function = Parsed->Document.unit().functionAt(0); + if (Function == nullptr || + static_cast(Stage) < static_cast(stageForFunction(*Function))) { + return false; + } + auto Run = runBackend(std::move(*Parsed), Target, Invocation, outputForStage(Stage)); + if (!Run) + return std::unexpected(Run.error()); + auto RoundTrip = verifyCanonicalRoundTrip(std::string(Run->text()), Target, Invocation); + if (!RoundTrip) + return std::unexpected(RoundTrip.error()); + return true; +} + +[[nodiscard]] Expected mutationCase(std::string_view Canonical, DeterministicRandom &Random, + const TargetDescription &Target, + const BackendInvocation &Invocation) { + std::string Mutated = structuralTextMutation(Canonical, Random); + auto Parsed = + parseAndCanonicalizeMIR("mutation-fuzz.mir", std::move(Mutated), Target, Invocation); + if (!Parsed) + return false; + auto RoundTrip = verifyCanonicalRoundTrip(std::move(Parsed->Text), Target, Invocation); + if (!RoundTrip) + return std::unexpected(RoundTrip.error()); + return true; +} + +[[nodiscard]] Expected pipelineCase(std::string_view Canonical, DeterministicRandom &Random, + const TargetDescription &Target, + BackendInvocation Invocation) { + auto Parsed = + parseAndCanonicalizeMIR("pipeline-fuzz.mir", std::string(Canonical), Target, Invocation); + if (!Parsed) + return std::unexpected(Parsed.error()); + const MachineFunction *Function = Parsed->Document.unit().functionAt(0); + if (Function == nullptr) + return std::unexpected(Error(ErrorCode::InternalError, "pipeline fuzz input has no function")); + auto Names = riscv::canonicalPipelinePassNames(*Function); + if (!Names) + return std::unexpected(Names.error()); + PipelineControls Controls; + if (!Names->empty()) + Controls.StopAfter = (*Names)[Random.index(Names->size())]; + Controls.VerifyEach = true; + Controls.DetectUnreportedChanges = true; + Invocation.Determinism.ThreadCount = std::array{1, 2, 4}[Random.index(3U)]; + const std::string Stop = Controls.StopAfter ? *Controls.StopAfter : "end"; + auto Run = + runBackend(std::move(*Parsed), Target, Invocation, BackendOutputKind::FinalMIR, Controls); + if (!Run) + return std::unexpected(Run.error()); + auto RoundTrip = verifyCanonicalRoundTrip(std::string(Run->text()), Target, Invocation); + if (!RoundTrip) { + return std::unexpected( + Error(RoundTrip.error().code(), + "pipeline stop-after '" + Stop + "': " + std::string(RoundTrip.error().message()))); + } + return true; +} + +using RegisterValues = std::unordered_map; + +[[nodiscard]] Expected registerAt(const MachineInstr &Instruction, std::size_t Index) { + const MachineOperand *Operand = Instruction.operandAt(Index); + if (Operand == nullptr) + return makeUnexpected(ErrorCode::Unsupported, "semantic subset requires register operands"); + const std::optional Reg = Operand->registerValue(); + if (!Reg) + return makeUnexpected(ErrorCode::Unsupported, "semantic subset requires register operands"); + return *Reg; +} + +[[nodiscard]] Expected immediateAt(const MachineInstr &Instruction, + std::size_t Index) { + const MachineOperand *Operand = Instruction.operandAt(Index); + if (Operand == nullptr) + return makeUnexpected(ErrorCode::Unsupported, "semantic subset requires an immediate operand"); + if (const auto *Immediate = Operand->getIf()) + return static_cast(*Immediate); + if (const auto *Immediate = Operand->getIf()) { + if (Immediate->Value.bitWidth() > 64 || Immediate->Value.words().empty()) + return makeUnexpected(ErrorCode::Unsupported, + "semantic subset only supports integer immediates up to 64 bits"); + return Immediate->Value.words().front(); + } + return makeUnexpected(ErrorCode::Unsupported, "semantic subset requires an integer immediate"); +} + +[[nodiscard]] std::uint64_t readRegister(Register Reg, const RegisterValues &Values, + PhysicalRegister Zero) noexcept { + if (Reg.physicalRegister() == Zero) + return 0; + const auto Position = Values.find(Reg.opaqueValue()); + return Position == Values.end() ? 0 : Position->second; +} + +[[nodiscard]] Expected interpretStraightLine(const MachineFunction &Function, + const TargetDescription &Target) { + if (Function.blockCount() != 1 || Function.firstBlock() == nullptr || + !Function.firstBlock()->successors().empty()) { + return makeUnexpected(ErrorCode::Unsupported, + "semantic subset requires one straight-line basic block"); + } + const PhysicalRegisterDesc *ZeroDescription = Target.registers().physicalRegister("x0"); + const PhysicalRegisterDesc *ReturnDescription = Target.registers().physicalRegister("x10"); + if (ZeroDescription == nullptr || ReturnDescription == nullptr) + return makeUnexpected(ErrorCode::Unsupported, + "semantic subset requires RV64 integer registers"); + const PhysicalRegister Zero = ZeroDescription->Reg; + const Register Return = Register::fromPhysical(ReturnDescription->Reg); + RegisterValues Values; + + for (const MachineInstr *Instruction = Function.firstBlock()->firstInstruction(); + Instruction != nullptr; Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); + if (Descriptor == nullptr) + return makeUnexpected(ErrorCode::Unsupported, + "semantic subset encountered an unknown opcode"); + const std::string_view Name = Descriptor->Name; + if (Name == "PseudoRET" || Name == "JALR_RET") + return readRegister(Return, Values, Zero); + + if (Name == "G_CONSTANT") { + auto Destination = registerAt(*Instruction, 0); + auto Immediate = immediateAt(*Instruction, 1); + if (!Destination || !Immediate) + return makeUnexpected(ErrorCode::Unsupported, "unsupported generic constant form"); + Values[Destination->opaqueValue()] = *Immediate; + continue; + } + if (Name == "COPY") { + auto Destination = registerAt(*Instruction, 0); + auto Source = registerAt(*Instruction, 1); + if (!Destination || !Source) + return makeUnexpected(ErrorCode::Unsupported, "unsupported copy form"); + Values[Destination->opaqueValue()] = readRegister(*Source, Values, Zero); + continue; + } + if (Name == "LUI") { + auto Destination = registerAt(*Instruction, 0); + auto Immediate = immediateAt(*Instruction, 1); + if (!Destination || !Immediate) + return makeUnexpected(ErrorCode::Unsupported, "unsupported LUI form"); + Values[Destination->opaqueValue()] = *Immediate << 12U; + continue; + } + + const bool ImmediateForm = Name == "ADDI" || Name == "XORI" || Name == "ORI" || + Name == "ANDI" || Name == "SLLI" || Name == "SRLI" || Name == "SRAI"; + const bool RegisterForm = + Name == "G_ADD" || Name == "G_SUB" || Name == "G_MUL" || Name == "G_AND" || + Name == "G_OR" || Name == "G_XOR" || Name == "G_SHL" || Name == "G_LSHR" || + Name == "G_ASHR" || Name == "ADD" || Name == "SUB" || Name == "MUL" || Name == "AND" || + Name == "OR" || Name == "XOR" || Name == "SLL" || Name == "SRL" || Name == "SRA"; + if (!ImmediateForm && !RegisterForm && !Descriptor->Flags.has(InstructionFlag::ProducesCode)) { + continue; + } + if (!ImmediateForm && !RegisterForm) { + return makeUnexpected(ErrorCode::Unsupported, + "semantic subset does not model opcode '" + std::string(Name) + "'"); + } + auto Destination = registerAt(*Instruction, 0); + auto LeftRegister = registerAt(*Instruction, 1); + if (!Destination || !LeftRegister) + return makeUnexpected(ErrorCode::Unsupported, "unsupported binary instruction form"); + const std::uint64_t Left = readRegister(*LeftRegister, Values, Zero); + std::uint64_t Right = 0; + if (ImmediateForm) { + auto Immediate = immediateAt(*Instruction, 2); + if (!Immediate) + return makeUnexpected(ErrorCode::Unsupported, "unsupported immediate instruction form"); + Right = *Immediate; + } else { + auto RightRegister = registerAt(*Instruction, 2); + if (!RightRegister) + return makeUnexpected(ErrorCode::Unsupported, "unsupported register instruction form"); + Right = readRegister(*RightRegister, Values, Zero); + } + std::uint64_t Value = 0; + if (Name == "G_ADD" || Name == "ADD" || Name == "ADDI") + Value = Left + Right; + else if (Name == "G_SUB" || Name == "SUB") + Value = Left - Right; + else if (Name == "G_MUL" || Name == "MUL") + Value = Left * Right; + else if (Name == "G_AND" || Name == "AND" || Name == "ANDI") + Value = Left & Right; + else if (Name == "G_OR" || Name == "OR" || Name == "ORI") + Value = Left | Right; + else if (Name == "G_XOR" || Name == "XOR" || Name == "XORI") + Value = Left ^ Right; + else if (Name == "G_SHL" || Name == "SLL" || Name == "SLLI") + Value = Left << (Right & 63U); + else if (Name == "G_LSHR" || Name == "SRL" || Name == "SRLI") + Value = Left >> (Right & 63U); + else + Value = static_cast(std::bit_cast(Left) >> (Right & 63U)); + Values[Destination->opaqueValue()] = Value; + } + return makeUnexpected(ErrorCode::Unsupported, "semantic subset did not encounter a return"); +} + +[[nodiscard]] Expected semanticCase(std::string_view Canonical, + const TargetDescription &Target, + const BackendInvocation &Invocation) { + auto Original = + parseAndCanonicalizeMIR("semantic-input.mir", std::string(Canonical), Target, Invocation); + if (!Original) + return std::unexpected(Original.error()); + const MachineFunction *InputFunction = Original->Document.unit().functionAt(0); + if (InputFunction == nullptr) + return std::unexpected(Error(ErrorCode::InternalError, "semantic fuzz input has no function")); + auto ExpectedValue = interpretStraightLine(*InputFunction, Target); + if (!ExpectedValue && ExpectedValue.error().code() == ErrorCode::Unsupported) + return false; + if (!ExpectedValue) + return std::unexpected(ExpectedValue.error()); + + auto CompiledInput = + parseAndCanonicalizeMIR("semantic-pipeline.mir", std::string(Canonical), Target, Invocation); + if (!CompiledInput) + return std::unexpected(CompiledInput.error()); + auto Compiled = + runBackend(std::move(*CompiledInput), Target, Invocation, BackendOutputKind::FinalMIR); + if (!Compiled) + return std::unexpected(Compiled.error()); + auto Final = parseAndCanonicalizeMIR("semantic-final.mir", std::string(Compiled->text()), Target, + Invocation); + if (!Final) + return std::unexpected(Final.error()); + const MachineFunction *FinalFunction = Final->Document.unit().functionAt(0); + if (FinalFunction == nullptr) + return std::unexpected(Error(ErrorCode::InternalError, "semantic final MIR has no function")); + auto ActualValue = interpretStraightLine(*FinalFunction, Target); + if (!ActualValue && ActualValue.error().code() == ErrorCode::Unsupported) + return false; + if (!ActualValue) + return std::unexpected(ActualValue.error()); + if (*ExpectedValue != *ActualValue) { + return makeUnexpected(ErrorCode::FuzzingFailure, + "restricted semantic comparison changed the integer return value from " + + std::to_string(*ExpectedValue) + " to " + + std::to_string(*ActualValue)); + } + return true; +} + +} // namespace + +std::size_t MIRFuzzResult::executed() const noexcept { + std::size_t Result = 0; + for (const MIRFuzzLayerResult &Layer : Layers) + Result += Layer.Executed; + return Result; +} + +std::size_t MIRFuzzResult::accepted() const noexcept { + std::size_t Result = 0; + for (const MIRFuzzLayerResult &Layer : Layers) + Result += Layer.Accepted; + return Result; +} + +std::size_t MIRFuzzResult::rejected() const noexcept { + std::size_t Result = 0; + for (const MIRFuzzLayerResult &Layer : Layers) + Result += Layer.Rejected; + return Result; +} + +Expected fuzzMIR(std::string SourceName, std::string SourceText, + BackendInvocation Invocation, const TargetDescription &Target, + MIRFuzzOptions Options) { + if (Options.Iterations == 0 || Options.MaximumInputSize == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "MIR fuzzing requires nonzero iteration and input-size limits"); + } + if (std::ranges::any_of(Options.Layers, + [](MIRFuzzLayer Layer) { return spelling(Layer) == "invalid"; })) { + return makeUnexpected(ErrorCode::InvalidArgument, "MIR fuzz layer is not recognized"); + } + auto InvocationVerification = Invocation.canonicalizeAndVerify(); + if (!InvocationVerification) + return std::unexpected(InvocationVerification.error()); + auto Input = + parseAndCanonicalizeMIR(std::move(SourceName), std::move(SourceText), Target, Invocation); + if (!Input) + return std::unexpected(Input.error()); + if (Input->Text.size() > Options.MaximumInputSize) + return makeUnexpected(ErrorCode::InvalidArgument, "MIR fuzz seed exceeds the input-size limit"); + if (Options.Layers.empty()) + Options.Layers = defaultLayers(); + std::ranges::sort(Options.Layers); + if (std::ranges::adjacent_find(Options.Layers) != Options.Layers.end()) { + return makeUnexpected(ErrorCode::InvalidArgument, "MIR fuzz layers must be unique"); + } + + MIRFuzzResult Result{.Seed = Options.Seed, .Layers = {}}; + DeterministicRandom Random(Options.Seed); + for (MIRFuzzLayer Layer : Options.Layers) { + MIRFuzzLayerResult LayerResult{.Layer = Layer}; + const std::size_t Iterations = + Layer == MIRFuzzLayer::RestrictedSemantics ? 1U : Options.Iterations; + for (std::size_t Iteration = 0; Iteration < Iterations; ++Iteration) { + ++LayerResult.Executed; + Expected Case = false; + switch (Layer) { + case MIRFuzzLayer::Parser: + Case = parserCase(Input->Text, Random, Target, Invocation, Options.MaximumInputSize); + break; + case MIRFuzzLayer::BuilderStructural: + Case = builderCase(Input->Text, Target, Invocation); + break; + case MIRFuzzLayer::PropertyStage: + Case = propertyCase(Input->Text, Random, Target, Invocation); + break; + case MIRFuzzLayer::Mutation: + Case = mutationCase(Input->Text, Random, Target, Invocation); + break; + case MIRFuzzLayer::Pipeline: + Case = pipelineCase(Input->Text, Random, Target, Invocation); + break; + case MIRFuzzLayer::RestrictedSemantics: + Case = semanticCase(Input->Text, Target, Invocation); + break; + } + if (!Case) + return std::unexpected(fuzzFailure(Layer, Iteration, Case.error())); + if (*Case) + ++LayerResult.Accepted; + else + ++LayerResult.Rejected; + } + Result.Layers.push_back(LayerResult); + } + return Result; +} + +std::string_view spelling(MIRFuzzLayer Layer) noexcept { + switch (Layer) { + case MIRFuzzLayer::Parser: + return "parser"; + case MIRFuzzLayer::BuilderStructural: + return "builder"; + case MIRFuzzLayer::PropertyStage: + return "property-stage"; + case MIRFuzzLayer::Mutation: + return "mutation"; + case MIRFuzzLayer::Pipeline: + return "pipeline"; + case MIRFuzzLayer::RestrictedSemantics: + return "semantics"; + } + return "invalid"; +} + +Expected parseMIRFuzzLayer(std::string_view Value) { + if (Value == "parser") + return MIRFuzzLayer::Parser; + if (Value == "builder") + return MIRFuzzLayer::BuilderStructural; + if (Value == "property-stage") + return MIRFuzzLayer::PropertyStage; + if (Value == "mutation") + return MIRFuzzLayer::Mutation; + if (Value == "pipeline") + return MIRFuzzLayer::Pipeline; + if (Value == "semantics") + return MIRFuzzLayer::RestrictedSemantics; + return makeUnexpected(ErrorCode::InvalidInvocation, "unknown MIR fuzz layer"); +} + +} // namespace rcc::tooling diff --git a/backend/lib/Tooling/MIRReducer.cpp b/backend/lib/Tooling/MIRReducer.cpp new file mode 100644 index 0000000..a247584 --- /dev/null +++ b/backend/lib/Tooling/MIRReducer.cpp @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Tooling/MIRReducer.h" + +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Tooling/ArtifactStore.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::tooling { +namespace { + +struct Line final { + std::size_t Begin = 0; + std::size_t End = 0; + std::size_t ContentEnd = 0; + std::size_t Indent = 0; +}; + +struct Candidate final { + std::string MIR; + BackendInvocation Invocation; + std::string Description; +}; + +[[nodiscard]] std::vector defaultGranularities() { + return { + ReductionGranularity::MachineFunction, ReductionGranularity::BasicBlock, + ReductionGranularity::CFGEdge, ReductionGranularity::RegisterSubgraph, + ReductionGranularity::Instruction, ReductionGranularity::Operand, + ReductionGranularity::FrameObject, ReductionGranularity::ConstantPool, + ReductionGranularity::JumpTable, ReductionGranularity::TargetFeature, + ReductionGranularity::PassPipeline, + }; +} + +[[nodiscard]] std::vector lines(std::string_view Text) { + std::vector Result; + std::size_t Begin = 0; + while (Begin < Text.size()) { + const std::size_t Newline = Text.find('\n', Begin); + const std::size_t End = Newline == std::string_view::npos ? Text.size() : Newline + 1U; + std::size_t ContentEnd = Newline == std::string_view::npos ? Text.size() : Newline; + if (ContentEnd > Begin && Text[ContentEnd - 1U] == '\r') + --ContentEnd; + std::size_t Indent = 0; + while (Begin + Indent < ContentEnd && Text[Begin + Indent] == ' ') + ++Indent; + Result.push_back({Begin, End, ContentEnd, Indent}); + Begin = End; + } + if (Text.empty()) + Result.push_back({}); + return Result; +} + +[[nodiscard]] std::string_view lineText(std::string_view Text, const Line &Value) { + return Text.substr(Value.Begin, Value.ContentEnd - Value.Begin); +} + +[[nodiscard]] std::string_view trimmedLine(std::string_view Text, const Line &Value) { + return Text.substr(Value.Begin + Value.Indent, Value.ContentEnd - Value.Begin - Value.Indent); +} + +[[nodiscard]] std::string withoutRange(std::string_view Text, std::size_t Begin, std::size_t End) { + std::string Result; + Result.reserve(Text.size() - (End - Begin)); + Result.append(Text.substr(0, Begin)); + Result.append(Text.substr(End)); + return Result; +} + +void addCandidate(std::vector &Result, std::set &Seen, + std::string CandidateMIR, const BackendInvocation &Invocation, + std::string Description, std::string_view OriginalMIR) { + if (CandidateMIR == OriginalMIR || !Seen.insert(CandidateMIR).second) + return; + Result.push_back({std::move(CandidateMIR), Invocation, std::move(Description)}); +} + +[[nodiscard]] bool isBodyStart(std::string_view Text, const Line &Value) { + return Value.Indent == 0 && trimmedLine(Text, Value) == "body: |"; +} + +[[nodiscard]] bool isBlockLabel(std::string_view Text, const Line &Value) { + const std::string_view Trimmed = trimmedLine(Text, Value); + return Value.Indent == 2 && Trimmed.starts_with("bb.") && Trimmed.ends_with(':'); +} + +[[nodiscard]] bool isInstructionLine(std::string_view Text, const Line &Value, bool InBody) { + if (!InBody || Value.Indent < 4) + return false; + const std::string_view Trimmed = trimmedLine(Text, Value); + return !Trimmed.empty() && !Trimmed.starts_with("liveins:") && + !Trimmed.starts_with("successors:") && !Trimmed.starts_with("align ") && + !Trimmed.starts_with(';'); +} + +[[nodiscard]] std::vector functionCandidates(std::string_view MIR, + const BackendInvocation &Invocation) { + const std::vector Lines = lines(MIR); + std::vector Starts; + for (std::size_t Index = 0; Index < Lines.size(); ++Index) { + if (lineText(MIR, Lines[Index]) == "---") + Starts.push_back(Index); + } + std::vector Result; + std::set Seen; + if (Starts.size() <= 1) + return Result; + for (std::size_t Position = 0; Position < Starts.size(); ++Position) { + const std::size_t Begin = Lines[Starts[Position]].Begin; + const std::size_t End = + Position + 1U < Starts.size() ? Lines[Starts[Position + 1U]].Begin : MIR.size(); + addCandidate(Result, Seen, withoutRange(MIR, Begin, End), Invocation, + "remove YAML document " + std::to_string(Position), MIR); + } + return Result; +} + +[[nodiscard]] std::vector blockCandidates(std::string_view MIR, + const BackendInvocation &Invocation) { + const std::vector Lines = lines(MIR); + std::vector Result; + std::set Seen; + bool InBody = false; + for (std::size_t Index = 0; Index < Lines.size(); ++Index) { + if (isBodyStart(MIR, Lines[Index])) { + InBody = true; + continue; + } + if (InBody && Lines[Index].Indent == 0) { + InBody = isBodyStart(MIR, Lines[Index]); + continue; + } + if (!InBody || !isBlockLabel(MIR, Lines[Index])) + continue; + std::size_t EndIndex = Index + 1U; + while (EndIndex < Lines.size() && Lines[EndIndex].Indent != 0 && + !isBlockLabel(MIR, Lines[EndIndex])) { + ++EndIndex; + } + const std::size_t End = EndIndex < Lines.size() ? Lines[EndIndex].Begin : MIR.size(); + addCandidate(Result, Seen, withoutRange(MIR, Lines[Index].Begin, End), Invocation, + "remove machine basic block", MIR); + } + return Result; +} + +[[nodiscard]] std::vector instructionCandidates(std::string_view MIR, + const BackendInvocation &Invocation) { + const std::vector Lines = lines(MIR); + std::vector Result; + std::set Seen; + bool InBody = false; + for (const Line &Value : Lines) { + if (isBodyStart(MIR, Value)) { + InBody = true; + continue; + } + if (InBody && Value.Indent == 0) { + InBody = false; + continue; + } + if (!isInstructionLine(MIR, Value, InBody)) + continue; + addCandidate(Result, Seen, withoutRange(MIR, Value.Begin, Value.End), Invocation, + "remove machine instruction", MIR); + } + return Result; +} + +[[nodiscard]] std::vector cfgEdgeCandidates(std::string_view MIR, + const BackendInvocation &Invocation) { + const std::vector Lines = lines(MIR); + std::vector Result; + std::set Seen; + bool InBody = false; + for (const Line &Value : Lines) { + if (isBodyStart(MIR, Value)) { + InBody = true; + continue; + } + if (InBody && Value.Indent == 0) { + InBody = false; + continue; + } + const std::string_view Trimmed = trimmedLine(MIR, Value); + if (!InBody || !Trimmed.starts_with("successors:")) + continue; + const std::size_t LastComma = Trimmed.rfind(','); + if (LastComma == std::string_view::npos) { + addCandidate(Result, Seen, withoutRange(MIR, Value.Begin, Value.End), Invocation, + "remove CFG successor list", MIR); + continue; + } + const std::size_t EraseBegin = Value.Begin + Value.Indent + LastComma; + addCandidate(Result, Seen, withoutRange(MIR, EraseBegin, Value.ContentEnd), Invocation, + "remove CFG successor", MIR); + } + return Result; +} + +[[nodiscard]] std::vector operandCandidates(std::string_view MIR, + const BackendInvocation &Invocation) { + const std::vector Lines = lines(MIR); + std::vector Result; + std::set Seen; + bool InBody = false; + for (const Line &Value : Lines) { + if (isBodyStart(MIR, Value)) { + InBody = true; + continue; + } + if (InBody && Value.Indent == 0) { + InBody = false; + continue; + } + if (!isInstructionLine(MIR, Value, InBody)) + continue; + const std::string_view Text = lineText(MIR, Value); + const std::size_t LastComma = Text.rfind(','); + if (LastComma != std::string_view::npos) { + addCandidate(Result, Seen, withoutRange(MIR, Value.Begin + LastComma, Value.ContentEnd), + Invocation, "remove trailing machine operand", MIR); + } + const std::size_t Implicit = Text.rfind(" implicit "); + if (Implicit != std::string_view::npos) { + addCandidate(Result, Seen, withoutRange(MIR, Value.Begin + Implicit, Value.ContentEnd), + Invocation, "remove implicit machine operand", MIR); + } + } + return Result; +} + +[[nodiscard]] bool containsRegisterToken(std::string_view LineText, std::string_view Token) { + std::size_t Position = 0; + while ((Position = LineText.find(Token, Position)) != std::string_view::npos) { + const std::size_t End = Position + Token.size(); + const bool LeftBoundary = Position == 0 || LineText[Position - 1U] != '%'; + const bool RightBoundary = + End == LineText.size() || std::isdigit(static_cast(LineText[End])) == 0; + if (LeftBoundary && RightBoundary) + return true; + Position = End; + } + return false; +} + +[[nodiscard]] std::vector registerCandidates(std::string_view MIR, + const BackendInvocation &Invocation) { + const std::vector Lines = lines(MIR); + std::set Registers; + bool InBody = false; + for (const Line &Value : Lines) { + if (isBodyStart(MIR, Value)) { + InBody = true; + continue; + } + if (InBody && Value.Indent == 0) { + InBody = false; + continue; + } + if (!isInstructionLine(MIR, Value, InBody)) + continue; + const std::string_view Text = lineText(MIR, Value); + for (std::size_t Position = 0; Position < Text.size(); ++Position) { + if (Text[Position] != '%' || Position + 1U >= Text.size() || + std::isdigit(static_cast(Text[Position + 1U])) == 0) { + continue; + } + std::size_t End = Position + 2U; + while (End < Text.size() && std::isdigit(static_cast(Text[End])) != 0) { + ++End; + } + Registers.emplace(Text.substr(Position, End - Position)); + Position = End - 1U; + } + } + + std::vector Result; + std::set Seen; + for (const std::string &Register : Registers) { + std::string CandidateMIR; + CandidateMIR.reserve(MIR.size()); + InBody = false; + for (const Line &Value : Lines) { + if (isBodyStart(MIR, Value)) + InBody = true; + else if (InBody && Value.Indent == 0) + InBody = false; + if (isInstructionLine(MIR, Value, InBody) && + containsRegisterToken(lineText(MIR, Value), Register)) { + continue; + } + CandidateMIR.append(MIR.substr(Value.Begin, Value.End - Value.Begin)); + } + addCandidate(Result, Seen, std::move(CandidateMIR), Invocation, + "remove register subgraph " + Register, MIR); + } + return Result; +} + +[[nodiscard]] std::vector sequenceItemCandidates(std::string_view MIR, + const BackendInvocation &Invocation, + std::string_view Field, + std::size_t ItemIndent, + std::string_view Description) { + const std::vector Lines = lines(MIR); + std::vector Result; + std::set Seen; + bool InField = false; + for (std::size_t Index = 0; Index < Lines.size(); ++Index) { + const std::string_view Trimmed = trimmedLine(MIR, Lines[Index]); + if (Lines[Index].Indent == 0 && Trimmed == std::string(Field) + ":") { + InField = true; + continue; + } + if (InField && Lines[Index].Indent == 0) { + InField = false; + continue; + } + if (!InField || Lines[Index].Indent != ItemIndent || !Trimmed.starts_with("- ")) + continue; + std::size_t EndIndex = Index + 1U; + while (EndIndex < Lines.size() && Lines[EndIndex].Indent > ItemIndent) + ++EndIndex; + const std::size_t End = EndIndex < Lines.size() ? Lines[EndIndex].Begin : MIR.size(); + addCandidate(Result, Seen, withoutRange(MIR, Lines[Index].Begin, End), Invocation, + std::string(Description), MIR); + } + return Result; +} + +[[nodiscard]] std::vector invocationCandidates(std::string_view MIR, + const BackendInvocation &Invocation, + ReductionGranularity Granularity) { + std::vector Result; + if (Granularity == ReductionGranularity::TargetFeature) { + for (std::size_t Index = Invocation.Features.size(); Index > 0; --Index) { + BackendInvocation CandidateInvocation = Invocation; + const std::string Feature = CandidateInvocation.Features[Index - 1U]; + CandidateInvocation.Features.erase(CandidateInvocation.Features.begin() + + static_cast(Index - 1U)); + auto Verified = CandidateInvocation.canonicalizeAndVerify(); + if (Verified) { + Result.push_back( + {std::string(MIR), std::move(CandidateInvocation), "remove target feature " + Feature}); + } + } + } else if (Granularity == ReductionGranularity::PassPipeline && + (Invocation.PassPipeline.size() != 1 || + Invocation.PassPipeline.front() != "default")) { + for (std::size_t Index = Invocation.PassPipeline.size(); Index > 0; --Index) { + if (Invocation.PassPipeline.size() == 1) + break; + BackendInvocation CandidateInvocation = Invocation; + const std::string Pass = CandidateInvocation.PassPipeline[Index - 1U]; + CandidateInvocation.PassPipeline.erase(CandidateInvocation.PassPipeline.begin() + + static_cast(Index - 1U)); + Result.push_back( + {std::string(MIR), std::move(CandidateInvocation), "remove pipeline pass " + Pass}); + } + } + return Result; +} + +[[nodiscard]] std::vector candidatesFor(ReductionGranularity Granularity, + std::string_view MIR, + const BackendInvocation &Invocation) { + switch (Granularity) { + case ReductionGranularity::CompilationUnit: + case ReductionGranularity::MachineFunction: + return functionCandidates(MIR, Invocation); + case ReductionGranularity::BasicBlock: + return blockCandidates(MIR, Invocation); + case ReductionGranularity::CFGEdge: + return cfgEdgeCandidates(MIR, Invocation); + case ReductionGranularity::Instruction: + return instructionCandidates(MIR, Invocation); + case ReductionGranularity::Operand: + return operandCandidates(MIR, Invocation); + case ReductionGranularity::RegisterSubgraph: + return registerCandidates(MIR, Invocation); + case ReductionGranularity::FrameObject: { + std::vector Result = + sequenceItemCandidates(MIR, Invocation, "fixedStack", 2, "remove fixed stack object"); + std::vector Stack = + sequenceItemCandidates(MIR, Invocation, "stack", 2, "remove stack object"); + Result.insert(Result.end(), std::make_move_iterator(Stack.begin()), + std::make_move_iterator(Stack.end())); + return Result; + } + case ReductionGranularity::ConstantPool: + return sequenceItemCandidates(MIR, Invocation, "constants", 2, "remove constant-pool entry"); + case ReductionGranularity::JumpTable: + return sequenceItemCandidates(MIR, Invocation, "entries", 4, "remove jump-table entry"); + case ReductionGranularity::TargetFeature: + case ReductionGranularity::PassPipeline: + return invocationCandidates(MIR, Invocation, Granularity); + } + return {}; +} + +} // namespace + +std::string_view spelling(ReductionGranularity Granularity) noexcept { + switch (Granularity) { + case ReductionGranularity::CompilationUnit: + return "compilation-unit"; + case ReductionGranularity::MachineFunction: + return "machine-function"; + case ReductionGranularity::BasicBlock: + return "basic-block"; + case ReductionGranularity::CFGEdge: + return "cfg-edge"; + case ReductionGranularity::Instruction: + return "instruction"; + case ReductionGranularity::Operand: + return "operand"; + case ReductionGranularity::RegisterSubgraph: + return "register-subgraph"; + case ReductionGranularity::FrameObject: + return "frame-object"; + case ReductionGranularity::ConstantPool: + return "constant-pool"; + case ReductionGranularity::JumpTable: + return "jump-table"; + case ReductionGranularity::TargetFeature: + return "target-feature"; + case ReductionGranularity::PassPipeline: + return "pass-pipeline"; + } + return "invalid"; +} + +Expected reduceMIR(std::string SourceName, std::string SourceText, + BackendInvocation Invocation, + const TargetDescription &Target, + const MIRInterestingnessTest &Interesting, + MIRReducerOptions Options) { + if (Options.Mode == ReductionMode::PreserveFailure && !Interesting) + return makeUnexpected(ErrorCode::InvalidArgument, + "failure-preserving MIR reduction requires an interestingness test"); + if (Options.MaximumAttempts == 0) + return makeUnexpected(ErrorCode::InvalidArgument, "MIR reducer attempt budget must be nonzero"); + std::set SeenGranularities; + for (ReductionGranularity Granularity : Options.Granularities) { + if (spelling(Granularity) == "invalid") { + return makeUnexpected(ErrorCode::InvalidArgument, + "MIR reducer granularity is not recognized"); + } + if (!SeenGranularities.insert(Granularity).second) { + return makeUnexpected(ErrorCode::InvalidArgument, "MIR reducer granularities must be unique"); + } + } + auto InvocationVerification = Invocation.canonicalizeAndVerify(); + if (!InvocationVerification) + return std::unexpected(InvocationVerification.error()); + auto Initial = + parseAndCanonicalizeMIR(std::move(SourceName), std::move(SourceText), Target, Invocation); + if (!Initial) + return std::unexpected(Initial.error()); + if (Options.Mode == ReductionMode::PreserveFailure) { + auto InitiallyInteresting = Interesting(Initial->Text, Invocation); + if (!InitiallyInteresting) + return std::unexpected(InitiallyInteresting.error()); + if (!*InitiallyInteresting) { + return makeUnexpected(ErrorCode::ReductionFailed, + "initial MIR input does not satisfy the interestingness test"); + } + } + if (Options.Granularities.empty()) + Options.Granularities = defaultGranularities(); + + MIRReductionResult Result{ + .MIR = std::move(Initial->Text), + .Invocation = std::move(Invocation), + .Steps = {}, + .Attempts = 0, + .Exhausted = false, + }; + for (ReductionGranularity Granularity : Options.Granularities) { + bool Changed = true; + while (Changed && Result.Attempts < Options.MaximumAttempts) { + Changed = false; + std::vector Candidates = candidatesFor(Granularity, Result.MIR, Result.Invocation); + for (Candidate &CandidateValue : Candidates) { + if (Result.Attempts == Options.MaximumAttempts) + break; + ++Result.Attempts; + auto Canonical = parseAndCanonicalizeMIR("reducer-candidate.mir", CandidateValue.MIR, + Target, CandidateValue.Invocation); + if (!Canonical) + continue; + if (Canonical->Text == Result.MIR && CandidateValue.Invocation == Result.Invocation) + continue; + if (Options.Mode == ReductionMode::PreserveFailure) { + auto IsInteresting = Interesting(Canonical->Text, CandidateValue.Invocation); + if (!IsInteresting) + return std::unexpected(IsInteresting.error()); + if (!*IsInteresting) + continue; + } + const std::size_t PreviousSize = Result.MIR.size(); + Result.MIR = std::move(Canonical->Text); + Result.Invocation = std::move(CandidateValue.Invocation); + Result.Steps.push_back({ + .Granularity = Granularity, + .Description = std::move(CandidateValue.Description), + .PreviousSize = PreviousSize, + .ReducedSize = Result.MIR.size(), + }); + Changed = true; + break; + } + } + } + Result.Exhausted = Result.Attempts == Options.MaximumAttempts; + return Result; +} + +} // namespace rcc::tooling diff --git a/backend/lib/Tooling/PipelineDriver.cpp b/backend/lib/Tooling/PipelineDriver.cpp new file mode 100644 index 0000000..330730e --- /dev/null +++ b/backend/lib/Tooling/PipelineDriver.cpp @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Tooling/PipelineDriver.h" + +#include "rcc/CodeGen/CompilationUnit.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineOptimizationPipeline.h" +#include "rcc/CodeGen/MachineVerifier.h" +#include "rcc/MIR/MIRDocument.h" +#include "rcc/Target/RISCV/RISCVPipeline.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace rcc::tooling { +namespace { + +struct FunctionRunResult final { + std::vector ExecutedPasses; + std::vector Snapshots; + std::optional Failure; +}; + +[[nodiscard]] unsigned stageOrdinal(BackendStage Stage) noexcept { + return static_cast(Stage); +} + +[[nodiscard]] bool selectorMatches(std::string_view Selector, std::string_view Pass, + std::uint32_t Occurrence) { + if (Selector == "all" || Selector == Pass) + return true; + std::string Qualified(Pass); + Qualified.push_back('#'); + Qualified.append(std::to_string(Occurrence)); + return Selector == Qualified; +} + +[[nodiscard]] bool anySelectorMatches(std::span Selectors, std::string_view Pass, + std::uint32_t Occurrence) { + return std::ranges::any_of(Selectors, [Pass, Occurrence](const std::string &Selector) { + return selectorMatches(Selector, Pass, Occurrence); + }); +} + +[[nodiscard]] std::optional stageAfterPass(std::string_view Pass) noexcept { + if (Pass == "Legalizer") + return BackendStage::LegalizedMIR; + if (Pass == "RegisterBankSelect") + return BackendStage::RegisterBankMIR; + if (Pass == "InstructionSelect") + return BackendStage::SelectedMIR; + if (Pass == "TwoAddressRewrite") + return BackendStage::PostSSAMIR; + if (Pass == "PBQPAllocator") + return BackendStage::PostRegisterAllocationMIR; + if (Pass == "final-encodability-verifier") + return BackendStage::FinalMIR; + return std::nullopt; +} + +[[nodiscard]] Expected +createPasses(const MachineFunction &Function, const BackendInvocation &Invocation, + MachinePassManagerOptions Options) { + MachinePassManager Pipeline(Options); + if (Invocation.PassPipeline.size() == 1 && Invocation.PassPipeline.front() == "default") { + auto Added = riscv::addCanonicalPipeline(Pipeline, Function); + if (!Added) + return std::unexpected(Added.error()); + } else { + if (std::ranges::find(Invocation.PassPipeline, "default") != Invocation.PassPipeline.end()) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "the default pipeline marker cannot be mixed with named passes"); + } + for (const std::string &Name : Invocation.PassPipeline) { + auto Pass = riscv::createCanonicalPipelinePass(Name); + if (!Pass) + return std::unexpected(Pass.error()); + auto Added = Pipeline.addPass(std::move(*Pass)); + if (!Added) + return std::unexpected(Added.error()); + } + } + return std::move(Pipeline).takePasses(); +} + +[[nodiscard]] Expected validateSelector(std::string_view Selector) { + if (Selector == "all") + return {}; + const std::size_t Hash = Selector.rfind('#'); + const std::string_view Name = + Hash == std::string_view::npos ? Selector : Selector.substr(0, Hash); + if (Name.empty()) + return makeUnexpected(ErrorCode::InvalidInvocation, "empty pipeline pass selector"); + auto Pass = riscv::createCanonicalPipelinePass(Name); + if (!Pass) + return std::unexpected(Pass.error()); + if (Hash != std::string_view::npos) { + const std::string_view Occurrence = Selector.substr(Hash + 1U); + if (Occurrence.empty() || Occurrence == "0" || + !std::ranges::all_of(Occurrence, + [](char Character) { return Character >= '0' && Character <= '9'; })) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "pipeline pass occurrence must be a positive integer"); + } + } + return {}; +} + +[[nodiscard]] Expected validateControls(const PipelineControls &Controls) { + if (Controls.StopBefore) { + auto Result = validateSelector(*Controls.StopBefore); + if (!Result) + return Result; + } + if (Controls.StopAfter) { + auto Result = validateSelector(*Controls.StopAfter); + if (!Result) + return Result; + } + for (const std::string &Selector : Controls.PrintBefore) { + auto Result = validateSelector(Selector); + if (!Result) + return Result; + } + for (const std::string &Selector : Controls.PrintAfter) { + auto Result = validateSelector(Selector); + if (!Result) + return Result; + } + return {}; +} + +[[nodiscard]] Expected +captureSnapshot(FunctionRunResult &Result, const mir::MIRDocument &Document, + const TargetDescription &Target, const BackendInvocation &Invocation, + SnapshotPhase Phase, BackendStage Stage, std::string_view Function, + std::string_view Pass, std::uint32_t Occurrence) { + mir::MIRDocumentPrinterOptions PrintOptions; + PrintOptions.Mode = mir::MIPrintMode::Complete; + PrintOptions.EmitEmbeddedIR = true; + PrintOptions.DataLayout = Invocation.DataLayout; + PrintOptions.DefaultPointerSizeInBits = 64; + auto Printed = mir::printMIRDocument(Document, Target, std::move(PrintOptions)); + if (!Printed) + return std::unexpected(Printed.error()); + Result.Snapshots.push_back({ + .Phase = Phase, + .Stage = Stage, + .Function = std::string(Function), + .Pass = std::string(Pass), + .Occurrence = Occurrence, + .MIR = std::move(*Printed), + }); + return {}; +} + +[[nodiscard]] Expected +runFunction(MachineFunction &Function, mir::MIRDocument &Document, const TargetDescription &Target, + const BackendInvocation &Invocation, const PipelineControls &Controls, + bool AllowSnapshots, MachinePassInstrumentation *Instrumentation) { + FunctionRunResult Result; + MachinePassManagerOptions ManagerOptions{ + .VerifyBefore = true, + .VerifyEach = Controls.VerifyEach, + .DetectUnreportedChanges = Controls.DetectUnreportedChanges, + }; + auto Passes = createPasses(Function, Invocation, ManagerOptions); + if (!Passes) + return std::unexpected(Passes.error()); + MachineAnalysisManager Analyses; + auto Registered = registerMachineOptimizationAnalyses(Analyses); + if (!Registered) + return std::unexpected(Registered.error()); + + BackendStage CurrentStage = stageForFunction(Function); + if (AllowSnapshots && Controls.CaptureStageSnapshots) { + auto Captured = captureSnapshot(Result, Document, Target, Invocation, SnapshotPhase::Stage, + CurrentStage, Function.name(), {}, 0); + if (!Captured) + return std::unexpected(Captured.error()); + } + if (Controls.StopAtStage && stageOrdinal(CurrentStage) >= stageOrdinal(*Controls.StopAtStage)) { + return Result; + } + + std::unordered_map Occurrences; + for (std::unique_ptr &Pass : *Passes) { + if (Pass == nullptr) + return makeUnexpected(ErrorCode::InternalError, "pipeline contains a null pass"); + const std::string Name = Pass->contract().Name; + const std::uint32_t Occurrence = ++Occurrences[Name]; + if (Controls.StopBefore && selectorMatches(*Controls.StopBefore, Name, Occurrence)) + break; + if (AllowSnapshots && anySelectorMatches(Controls.PrintBefore, Name, Occurrence)) { + auto Captured = + captureSnapshot(Result, Document, Target, Invocation, SnapshotPhase::BeforePass, + CurrentStage, Function.name(), Name, Occurrence); + if (!Captured) + return std::unexpected(Captured.error()); + } + + MachinePassManager SinglePass(ManagerOptions); + auto Added = SinglePass.addPass(std::move(Pass)); + if (!Added) + return std::unexpected(Added.error()); + auto Run = SinglePass.run(Function, Target, Analyses, Instrumentation); + if (!Run) + return std::unexpected(Run.error()); + Result.ExecutedPasses.push_back({ + .Function = std::string(Function.name()), + .Pass = Name, + .Occurrence = Occurrence, + }); + + if (const std::optional NewStage = stageAfterPass(Name)) + CurrentStage = *NewStage; + if (AllowSnapshots && anySelectorMatches(Controls.PrintAfter, Name, Occurrence)) { + auto Captured = + captureSnapshot(Result, Document, Target, Invocation, SnapshotPhase::AfterPass, + CurrentStage, Function.name(), Name, Occurrence); + if (!Captured) + return std::unexpected(Captured.error()); + } + if (AllowSnapshots && Controls.CaptureStageSnapshots && stageAfterPass(Name)) { + auto Captured = captureSnapshot(Result, Document, Target, Invocation, SnapshotPhase::Stage, + CurrentStage, Function.name(), Name, Occurrence); + if (!Captured) + return std::unexpected(Captured.error()); + } + if (Controls.StopAfter && selectorMatches(*Controls.StopAfter, Name, Occurrence)) + break; + if (Controls.StopAtStage && stageOrdinal(CurrentStage) >= stageOrdinal(*Controls.StopAtStage)) { + break; + } + } + return Result; +} + +[[nodiscard]] bool requiresSequentialExecution(const PipelineControls &Controls) noexcept { + return Controls.CaptureStageSnapshots || !Controls.PrintBefore.empty() || + !Controls.PrintAfter.empty(); +} + +} // namespace + +BackendStage stageForFunction(const MachineFunction &Function) noexcept { + const MachineFunctionProperties &Properties = Function.properties(); + if (Properties.has(MachineFunctionProperty::Selected) && + Properties.has(MachineFunctionProperty::NoExecutablePseudos)) + return BackendStage::FinalMIR; + if (Properties.has(MachineFunctionProperty::Selected) && + Properties.has(MachineFunctionProperty::NoVRegs)) + return BackendStage::PostRegisterAllocationMIR; + if (Properties.has(MachineFunctionProperty::Selected) && + Properties.has(MachineFunctionProperty::TiedOpsRewritten)) { + return BackendStage::PostSSAMIR; + } + if (Properties.has(MachineFunctionProperty::Selected)) + return BackendStage::SelectedMIR; + if (Properties.has(MachineFunctionProperty::RegBankSelected)) + return BackendStage::RegisterBankMIR; + if (Properties.has(MachineFunctionProperty::Legalized)) + return BackendStage::LegalizedMIR; + return BackendStage::PreLegalizerMIR; +} + +std::string_view spelling(BackendStage Stage) noexcept { + switch (Stage) { + case BackendStage::PreLegalizerMIR: + return "prelegalizer-mir"; + case BackendStage::LegalizedMIR: + return "legalized-mir"; + case BackendStage::RegisterBankMIR: + return "regbank-mir"; + case BackendStage::SelectedMIR: + return "selected-mir"; + case BackendStage::PostSSAMIR: + return "post-ssa-mir"; + case BackendStage::PostRegisterAllocationMIR: + return "post-ra-mir"; + case BackendStage::FinalMIR: + return "final-mir"; + } + return "invalid"; +} + +Expected parseBackendStage(std::string_view Value) { + if (Value == "prelegalizer-mir") + return BackendStage::PreLegalizerMIR; + if (Value == "legalized-mir") + return BackendStage::LegalizedMIR; + if (Value == "regbank-mir") + return BackendStage::RegisterBankMIR; + if (Value == "selected-mir") + return BackendStage::SelectedMIR; + if (Value == "post-ssa-mir") + return BackendStage::PostSSAMIR; + if (Value == "post-ra-mir") + return BackendStage::PostRegisterAllocationMIR; + if (Value == "final-mir") + return BackendStage::FinalMIR; + return makeUnexpected(ErrorCode::InvalidInvocation, "unknown backend stage"); +} + +Expected verifyRV64GCInvocation(const BackendInvocation &Invocation) { + BackendInvocation Canonical = Invocation; + auto Verified = Canonical.canonicalizeAndVerify(); + if (!Verified) + return std::unexpected(Verified.error()); + if (Canonical != Invocation) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "RV64GC backend invocation must be canonicalized before execution"); + } + const BackendInvocation Expected = defaultRV64GCInvocation(); + if (Invocation.Triple != Expected.Triple || Invocation.CPU != Expected.CPU || + Invocation.TuneCPU != Expected.TuneCPU || Invocation.Features != Expected.Features || + Invocation.ABI != Expected.ABI || Invocation.DataLayout != Expected.DataLayout) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "backend invocation does not select the supported RV64GC LP64D target"); + } + if (Invocation.MIRReferenceRevision != LLVMReferenceRevision || + Invocation.GenericOpcodeVersion != GenericOpcodeReferenceVersion || + Invocation.TargetABILoweringVersion != RV64GCTargetABILoweringVersion) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "backend invocation reference versions do not match this build"); + } + return {}; +} + +Expected runRV64GCPipeline(mir::MIRDocument &Document, + const TargetDescription &Target, + const BackendInvocation &Invocation, + PipelineControls Controls, + MachinePassInstrumentation *Instrumentation) { + auto InvocationVerification = verifyRV64GCInvocation(Invocation); + if (!InvocationVerification) + return std::unexpected(InvocationVerification.error()); + auto ControlVerification = validateControls(Controls); + if (!ControlVerification) + return std::unexpected(ControlVerification.error()); + auto UnitVerification = Document.unit().verify(); + if (!UnitVerification) + return std::unexpected(UnitVerification.error()); + if (Document.unit().functionCount() == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "pipeline input must contain at least one machine function"); + } + + MachinePassInstrumentation LocalInstrumentation; + MachinePassInstrumentation *ActiveInstrumentation = + Instrumentation == nullptr ? &LocalInstrumentation : Instrumentation; + ActiveInstrumentation->setPerFunctionFuel(Invocation.ActionFuel); + if (!Invocation.EnabledActions.empty()) { + const std::vector Enabled = Invocation.EnabledActions; + ActiveInstrumentation->setActionFilter([Enabled](const MachineActionID &Action, std::uint64_t) { + return std::ranges::binary_search(Enabled, Action.Rule) || + std::ranges::binary_search(Enabled, Action.Pass) || + std::ranges::binary_search(Enabled, Action.toString()); + }); + } + + const std::size_t FunctionCount = Document.unit().functionCount(); + std::vector FunctionResults(FunctionCount); + const bool Sequential = requiresSequentialExecution(Controls) || + Invocation.Determinism.ThreadCount == 1 || FunctionCount == 1; + if (Sequential) { + for (std::size_t Index = 0; Index < FunctionCount; ++Index) { + MachineFunction *Function = Document.unit().functionAt(Index); + if (Function == nullptr) + return makeUnexpected(ErrorCode::InternalError, + "compilation unit contains a null function"); + auto Run = runFunction(*Function, Document, Target, Invocation, Controls, true, + ActiveInstrumentation); + if (!Run) + return std::unexpected(Run.error()); + FunctionResults[Index] = std::move(*Run); + } + } else { + const std::size_t WorkerCount = + std::min(Invocation.Determinism.ThreadCount, FunctionCount); + std::atomic NextFunction = 0; + const auto Worker = [&]() { + while (true) { + const std::size_t Index = NextFunction.fetch_add(1, std::memory_order_relaxed); + if (Index >= FunctionCount) + return; + MachineFunction *Function = Document.unit().functionAt(Index); + if (Function == nullptr) { + FunctionResults[Index].Failure = + Error(ErrorCode::InternalError, "compilation unit contains a null function"); + continue; + } + auto Run = runFunction(*Function, Document, Target, Invocation, Controls, false, + ActiveInstrumentation); + if (!Run) { + FunctionResults[Index].Failure = Run.error(); + } else { + FunctionResults[Index] = std::move(*Run); + } + } + }; + std::vector Workers; + Workers.reserve(WorkerCount); + for (std::size_t Index = 0; Index < WorkerCount; ++Index) + Workers.emplace_back(Worker); + for (std::thread &Thread : Workers) + Thread.join(); + } + + PipelineRunResult Result; + for (FunctionRunResult &FunctionResult : FunctionResults) { + if (FunctionResult.Failure) + return std::unexpected(std::move(*FunctionResult.Failure)); + Result.ExecutedPasses.insert(Result.ExecutedPasses.end(), + std::make_move_iterator(FunctionResult.ExecutedPasses.begin()), + std::make_move_iterator(FunctionResult.ExecutedPasses.end())); + Result.Snapshots.insert(Result.Snapshots.end(), + std::make_move_iterator(FunctionResult.Snapshots.begin()), + std::make_move_iterator(FunctionResult.Snapshots.end())); + } + return Result; +} + +} // namespace rcc::tooling -- cgit v1.2.3