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/Tooling/MIRFuzzer.cpp | 613 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 613 insertions(+) create mode 100644 backend/lib/Tooling/MIRFuzzer.cpp (limited to 'backend/lib/Tooling/MIRFuzzer.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 -- cgit v1.2.3