aboutsummaryrefslogtreecommitdiff
path: root/backend/lib/Tooling/BackendManifest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backend/lib/Tooling/BackendManifest.cpp')
-rw-r--r--backend/lib/Tooling/BackendManifest.cpp733
1 files changed, 733 insertions, 0 deletions
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 <algorithm>
+#include <cctype>
+#include <charconv>
+#include <cstddef>
+#include <cstdint>
+#include <initializer_list>
+#include <limits>
+#include <optional>
+#include <ranges>
+#include <span>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+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<unsigned char>(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<char>(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 <typename IntegerT>
+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<const std::string> 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<const BackendOption> 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<std::uint64_t> 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<BackendManifest> run() {
+ if (!Parsed.succeeded()) {
+ const std::span<const TextDiagnostic> 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<std::uint32_t>(**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 <typename... Results>
+ [[nodiscard]] Expected<BackendManifest> firstError(const Results &...Values) const {
+ std::optional<Error> 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<const yaml::Node *> 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<void> rejectUnknown(const yaml::Node &Node,
+ std::initializer_list<std::string_view> Allowed,
+ std::string_view Context) const {
+ std::unordered_set<std::string_view> 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<yaml::NodeID> ID = Node.mappingValue(Name);
+ return ID && Stream != nullptr ? Stream->node(*ID) : nullptr;
+ }
+
+ [[nodiscard]] Expected<std::string> 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<std::string> 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 <typename IntegerT>
+ [[nodiscard]] Expected<IntegerT> 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 <typename IntegerT>
+ [[nodiscard]] Expected<IntegerT> 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<IntegerT>(Value, std::string(Context) + "." + std::string(Name));
+ }
+
+ [[nodiscard]] Expected<bool> 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<std::vector<std::string>> 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<std::string> 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<std::vector<BackendOption>> 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<BackendOption> 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<BackendInvocation> 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<std::uint32_t>(**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<std::uint64_t>(**Mapping, "random_seed", "manifest invocation");
+ auto ThreadCount =
+ requiredUnsigned<std::uint32_t>(**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<std::uint64_t>(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<std::vector<StructuredDiagnostic>>
+ 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<StructuredDiagnostic> 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<void> {
+ 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<decltype(Destination)>::value_type;
+ auto ParsedValue = unsignedValue<ValueT>(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<ManifestKind> 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<ArtifactKind> 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<void> 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<std::string> 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<BackendManifest> parseBackendManifest(std::string SourceName, std::string SourceText) {
+ return ManifestParser(std::move(SourceName), std::move(SourceText)).run();
+}
+
+} // namespace rcc::tooling