// SPDX-License-Identifier: GPL-3.0-or-later #include "RISCV.h" #include "rcc/CodeGen/GenericOpcodes.h" #include "rcc/CodeGen/MachineBasicBlock.h" #include "rcc/CodeGen/MachineFunction.h" #include "rcc/CodeGen/MachineFunctionRecords.h" #include "rcc/CodeGen/MachineIRBuilder.h" #include "rcc/CodeGen/MachineInstr.h" #include "rcc/CodeGen/MachineOperand.h" #include "rcc/CodeGen/MachineRegisterInfo.h" #include "rcc/CodeGen/TargetDescription.h" #include "rcc/Target/RISCV/RISCVInstructions.h" #include "rcc/Target/RISCV/RISCVRegisters.h" #include "rcc/Target/RISCV/RISCVTarget.h" #include #include #include #include #include #include namespace rcc::riscv { namespace { constexpr std::uint32_t PCRelativeHighFlag = 2; constexpr std::uint32_t PCRelativeLowFlag = 4; constexpr std::uint32_t CopyF32Flag = 8; constexpr std::uint32_t CopyF64Flag = 16; constexpr std::uint32_t GOTPCRelativeHighFlag = 128; [[nodiscard]] std::optional registerBank(Register Reg, const MachineFunction &Function, const TargetDescription &Target) { if (Reg.isVirtual()) { auto Info = Function.registerInfo().virtualRegisterInfo(Reg); return Info ? Info->get().Bank : std::nullopt; } const std::optional Physical = Reg.physicalRegister(); if (!Physical) return std::nullopt; for (const RegisterBankDesc &Bank : Target.registers().registerBanks()) { if (Target.registers().bankContainsRegister(Bank.ID, *Physical)) return Bank.ID; } return std::nullopt; } [[nodiscard]] std::optional registerType(Register Reg, const MachineFunction &Function) { if (!Reg.isVirtual()) return std::nullopt; auto Info = Function.registerInfo().virtualRegisterInfo(Reg); return Info ? Info->get().Type : std::nullopt; } [[nodiscard]] Expected copyLocation(const MachineInstr &Source, MachineInstr &Destination) { if (Source.sourceLocation()) Destination.setSourceLocation(*Source.sourceLocation()); if (Source.debugLocation()) { auto Debug = Destination.setDebugLocation(*Source.debugLocation()); if (!Debug) return std::unexpected(Debug.error()); } if (Source.origin()) Destination.setOrigin(*Source.origin()); return {}; } [[nodiscard]] Expected copyCommonDecoration(const MachineInstr &Source, MachineInstr &Destination, bool CopyPreSymbol, bool CopyPostSymbol, bool CopyDebugNumber) { auto Location = copyLocation(Source, Destination); if (!Location) return std::unexpected(Location.error()); Destination.setFlags(Source.flags()); if (CopyDebugNumber && Source.debugInstrNumber() != 0) { auto Number = Destination.setDebugInstrNumber(Source.debugInstrNumber()); if (!Number) return std::unexpected(Number.error()); } if (CopyPreSymbol) { if (const auto Symbol = Source.preInstrSymbol()) { auto Set = Destination.setPreInstrSymbol(std::string(*Symbol)); if (!Set) return std::unexpected(Set.error()); } } if (CopyPostSymbol) { if (const auto Symbol = Source.postInstrSymbol()) { auto Set = Destination.setPostInstrSymbol(std::string(*Symbol)); if (!Set) return std::unexpected(Set.error()); } } if (Source.heapAllocMarker()) { auto Set = Destination.setHeapAllocMarker(*Source.heapAllocMarker()); if (!Set) return std::unexpected(Set.error()); } if (Source.pcSections()) { auto Set = Destination.setPCSections(*Source.pcSections()); if (!Set) return std::unexpected(Set.error()); } if (Source.mmra()) { auto Set = Destination.setMMRA(*Source.mmra()); if (!Set) return std::unexpected(Set.error()); } if (Source.cfiType()) { auto Set = Destination.setCFIType(*Source.cfiType()); if (!Set) return std::unexpected(Set.error()); } if (const auto Symbol = Source.deactivationSymbol()) { auto Set = Destination.setDeactivationSymbol(std::string(*Symbol)); if (!Set) return std::unexpected(Set.error()); } return {}; } [[nodiscard]] Expected> copyRegisters(const MachineInstr &Instruction) { const MachineOperand *DestinationOperand = Instruction.operandAt(0); const MachineOperand *SourceOperand = Instruction.operandAt(1); const Register *Destination = DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf(); const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf(); if (Destination == nullptr || Source == nullptr) return makeUnexpected(ErrorCode::VerificationError, "post-RA copy pseudo requires two register operands"); return std::pair{*Destination, *Source}; } [[nodiscard]] Expected prepareCopy(MachineInstr &Instruction, MachineFunction &Function, const TargetDescription &Target) { if (Instruction.opcode() != opcode(GenericOpcode::COPY)) return false; auto Registers = copyRegisters(Instruction); if (!Registers) return std::unexpected(Registers.error()); const auto DestinationBank = registerBank(Registers->first, Function, Target); const auto SourceBank = registerBank(Registers->second, Function, Target); if (!DestinationBank || !SourceBank) return makeUnexpected(ErrorCode::VerificationError, "RISC-V copy preparation requires constrained register banks"); if (*DestinationBank == *SourceBank) return false; const auto DestinationType = registerType(Registers->first, Function); const auto SourceType = registerType(Registers->second, Function); const std::optional Type = DestinationType ? DestinationType : SourceType; if (!Type || Type->isVector() || (Type->scalarBitWidth() != 32 && Type->scalarBitWidth() != 64)) { return makeUnexpected(ErrorCode::Unsupported, "RISC-V cross-bank COPY requires a scalar 32-bit or 64-bit type"); } if (DestinationType && SourceType && DestinationType->scalarBitWidth() != SourceType->scalarBitWidth()) { return makeUnexpected(ErrorCode::VerificationError, "RISC-V cross-bank COPY has mismatched value widths"); } const std::uint32_t Flag = Type->scalarBitWidth() == 32 ? CopyF32Flag : CopyF64Flag; MachineOperand *DestinationOperand = Instruction.operandAt(0); MachineOperand *SourceOperand = Instruction.operandAt(1); const bool Changed = DestinationOperand->targetFlags() != Flag || SourceOperand->targetFlags() != Flag; DestinationOperand->setTargetFlags(Flag); SourceOperand->setTargetFlags(Flag); return Changed; } [[nodiscard]] Expected expandCopy(MachineInstr &Instruction, MachineFunction &Function, const TargetDescription &Target) { auto Registers = copyRegisters(Instruction); if (!Registers) return std::unexpected(Registers.error()); const Register Destination = Registers->first; const Register Source = Registers->second; if (!Destination.isPhysical() || !Source.isPhysical()) { return makeUnexpected(ErrorCode::VerificationError, "RISC-V post-RA COPY retains a virtual register"); } if (Destination == Source) { auto Removed = Instruction.eraseFromParent(); if (!Removed) return std::unexpected(Removed.error()); return true; } const auto DestinationBank = registerBank(Destination, Function, Target); const auto SourceBank = registerBank(Source, Function, Target); if (!DestinationBank || !SourceBank) return makeUnexpected(ErrorCode::VerificationError, "RISC-V post-RA COPY references an unknown register bank"); const RegisterBankID GPR = RegisterBankID::fromValue(static_cast(RegisterBank::GPR)); const RegisterBankID FPR = RegisterBankID::fromValue(static_cast(RegisterBank::FPR)); InstructionOpcode Selected = InstructionOpcode::Invalid; bool DuplicateSource = false; bool AddZeroImmediate = false; if (*DestinationBank == GPR && *SourceBank == GPR) { Selected = InstructionOpcode::ADDI; AddZeroImmediate = true; } else if (*DestinationBank == FPR && *SourceBank == FPR) { Selected = InstructionOpcode::FSGNJ_D; DuplicateSource = true; } else { const std::uint32_t DestinationFlags = Instruction.operandAt(0)->targetFlags(); const std::uint32_t SourceFlags = Instruction.operandAt(1)->targetFlags(); if (DestinationFlags != SourceFlags || (DestinationFlags != CopyF32Flag && DestinationFlags != CopyF64Flag)) { return makeUnexpected(ErrorCode::PseudoExpansionFailed, "RISC-V cross-bank COPY has no prepared transfer width"); } if (*DestinationBank == GPR && *SourceBank == FPR) { Selected = DestinationFlags == CopyF32Flag ? InstructionOpcode::FMV_X_W : InstructionOpcode::FMV_X_D; } else if (*DestinationBank == FPR && *SourceBank == GPR) { Selected = DestinationFlags == CopyF32Flag ? InstructionOpcode::FMV_W_X : InstructionOpcode::FMV_D_X; } } if (Selected == InstructionOpcode::Invalid) return makeUnexpected(ErrorCode::Unsupported, "RISC-V COPY crosses unsupported banks"); MachineIRBuilder Builder(Function, Target); auto Point = Builder.setInsertPointBefore(Instruction); if (!Point) return std::unexpected(Point.error()); auto Pending = Builder.buildInstr(instructionOpcode(Selected)); if (!Pending) return std::unexpected(Pending.error()); auto Def = Pending->addDef(Destination); if (!Def) return std::unexpected(Def.error()); RegisterOperandFlags SourceFlags; const RegisterOperandFlags OriginalSourceFlags = Instruction.operandAt(1)->registerFlags(); if (OriginalSourceFlags.has(RegisterOperandFlag::Kill)) SourceFlags.set(RegisterOperandFlag::Kill); if (*DestinationBank == GPR && *SourceBank == GPR && OriginalSourceFlags.has(RegisterOperandFlag::Renamable)) { SourceFlags.set(RegisterOperandFlag::Renamable); } auto Use = Pending->addUse(Source, SourceFlags); if (!Use) return std::unexpected(Use.error()); if (DuplicateSource) { auto SecondUse = Pending->addUse(Source); if (!SecondUse) return std::unexpected(SecondUse.error()); } if (AddZeroImmediate) { auto Immediate = Pending->addImmediate(0); if (!Immediate) return std::unexpected(Immediate.error()); } auto Replacement = Pending->build(); if (!Replacement) return std::unexpected(Replacement.error()); auto Decoration = copyCommonDecoration(Instruction, **Replacement, true, true, true); if (!Decoration) return std::unexpected(Decoration.error()); auto Removed = Instruction.eraseFromParent(); if (!Removed) return std::unexpected(Removed.error()); return true; } [[nodiscard]] Expected expandSubregisterCopy(MachineInstr &Instruction, MachineFunction &Function, const TargetDescription &Target) { const Opcode Value = Instruction.opcode(); if (Value == opcode(GenericOpcode::EXTRACT_SUBREG) || Value == opcode(GenericOpcode::SUBREG_TO_REG)) { const MachineOperand *Index = Instruction.operandAt(2); const auto *Immediate = Index == nullptr ? nullptr : Index->getIf(); if (Immediate == nullptr || *Immediate != 0) return makeUnexpected(ErrorCode::Unsupported, "RV64GC does not support nonzero subregister indices"); } else if (Value == opcode(GenericOpcode::COPY_LANEMASK)) { const MachineOperand *MaskOperand = Instruction.operandAt(2); const auto *Mask = MaskOperand == nullptr ? nullptr : MaskOperand->getIf(); if (Mask == nullptr || Mask->Mask.empty()) return makeUnexpected(ErrorCode::VerificationError, "RISC-V lane copy requires a nonempty lane mask"); } else { return false; } return expandCopy(Instruction, Function, Target); } [[nodiscard]] Expected addDirectTarget(MachineInstrBuilder &Builder, const MachineOperand &Callee) { if (const auto *Global = Callee.getIf()) return Builder.addGlobalSymbol(Global->Name, Global->Offset, PCRelativeHighFlag); if (const auto *External = Callee.getIf()) return Builder.addExternalSymbol(External->Name, External->Offset, PCRelativeHighFlag); return makeUnexpected(ErrorCode::VerificationError, "RISC-V direct call pseudo has no symbol target"); } [[nodiscard]] Expected createGPRScratchRegister(MachineFunction &Function, const TargetDescription &Target) { const RegisterBankID GPRBank = RegisterBankID::fromValue(static_cast(RegisterBank::GPR)); const RegisterClassID GPRClass = RegisterClassID::fromValue(static_cast(RegisterClass::GPR)); if (Target.registers().registerBank(GPRBank) == nullptr || Target.registers().registerClass(GPRClass) == nullptr) { return makeUnexpected(ErrorCode::InternalError, "RISC-V pre-RA address expansion requires the GPR register class"); } auto Scratch = Function.registerInfo().createVirtualRegister(); if (!Scratch) return std::unexpected(Scratch.error()); auto SetBank = Function.registerInfo().setRegisterBank(*Scratch, GPRBank); if (!SetBank) return std::unexpected(SetBank.error()); auto SetClass = Function.registerInfo().setRegisterClass(*Scratch, GPRClass); if (!SetClass) return std::unexpected(SetClass.error()); return *Scratch; } [[nodiscard]] Expected expandAddressPseudo(MachineInstr &Instruction, MachineFunction &Function, const TargetDescription &Target, bool LoadFromGOT) { const MachineOperand *Destination = Instruction.operandAt(0); const MachineOperand *Symbol = Instruction.operandAt(1); const Register *DestinationRegister = Destination == nullptr ? nullptr : Destination->getIf(); if (DestinationRegister == nullptr || Symbol == nullptr || Instruction.operandCount() != 2) { return makeUnexpected(ErrorCode::VerificationError, "RISC-V address pseudo has malformed explicit operands"); } if (LoadFromGOT && Instruction.memOperands().size() != 1) { return makeUnexpected(ErrorCode::VerificationError, "RISC-V global-address pseudo requires one GOT load memory operand"); } if (!LoadFromGOT && !Instruction.memOperands().empty()) { return makeUnexpected(ErrorCode::VerificationError, "RISC-V local-address pseudo cannot carry memory operands"); } auto Scratch = createGPRScratchRegister(Function, Target); if (!Scratch) return std::unexpected(Scratch.error()); MachineIRBuilder Builder(Function, Target); auto Point = Builder.setInsertPointBefore(Instruction); if (!Point) return std::unexpected(Point.error()); auto HighPending = Builder.buildInstr(instructionOpcode(InstructionOpcode::AUIPC)); if (!HighPending) return std::unexpected(HighPending.error()); auto HighDef = HighPending->addDef(*Scratch); if (!HighDef) return std::unexpected(HighDef.error()); MachineOperand HighSymbol = Symbol->cloneDetached(); HighSymbol.setTargetFlags(LoadFromGOT ? GOTPCRelativeHighFlag : PCRelativeHighFlag); auto HighTarget = HighPending->addOperand(std::move(HighSymbol)); if (!HighTarget) return std::unexpected(HighTarget.error()); auto High = HighPending->build(); if (!High) return std::unexpected(High.error()); const std::string Anchor = Instruction.preInstrSymbol() ? std::string(*Instruction.preInstrSymbol()) : ".Lrcc_pcrel_hi_" + std::to_string(Function.stableID().value()) + "_" + std::to_string((*High)->stableID().value()); auto HighAnchor = (*High)->setPreInstrSymbol(Anchor); if (!HighAnchor) return std::unexpected(HighAnchor.error()); auto HighDecoration = copyCommonDecoration(Instruction, **High, false, false, false); if (!HighDecoration) return std::unexpected(HighDecoration.error()); const InstructionOpcode LowOpcode = LoadFromGOT ? InstructionOpcode::LD : InstructionOpcode::ADDI; auto LowPending = Builder.buildInstr(instructionOpcode(LowOpcode)); if (!LowPending) return std::unexpected(LowPending.error()); auto LowDef = LowPending->addOperand(Destination->cloneDetached()); if (!LowDef) return std::unexpected(LowDef.error()); auto LowBase = LowPending->addUse(*Scratch); if (!LowBase) return std::unexpected(LowBase.error()); auto LowOffset = LowPending->addMCSymbol(Anchor, 0, PCRelativeLowFlag); if (!LowOffset) return std::unexpected(LowOffset.error()); if (LoadFromGOT) { const MachineMemOperand *Memory = Instruction.memOperands().front(); if (Memory == nullptr) return makeUnexpected(ErrorCode::VerificationError, "RISC-V global-address pseudo has a null memory operand"); auto AddedMemory = LowPending->addMemOperand(*Memory); if (!AddedMemory) return std::unexpected(AddedMemory.error()); } auto Low = LowPending->build(); if (!Low) return std::unexpected(Low.error()); auto LowDecoration = copyCommonDecoration(Instruction, **Low, false, true, true); if (!LowDecoration) return std::unexpected(LowDecoration.error()); auto Records = Function.records().replaceInstructionReference(Instruction.stableID(), (*Low)->stableID()); if (!Records) return std::unexpected(Records.error()); auto Removed = Instruction.eraseFromParent(); if (!Removed) return std::unexpected(Removed.error()); return true; } [[nodiscard]] bool isRegister(const MachineOperand &Operand, PhysicalRegister Reg) noexcept { const Register *Value = Operand.getIf(); return Value != nullptr && Value->physicalRegister() == Reg; } [[nodiscard]] Expected copyExtraImplicitOperands( const MachineInstr &Source, std::size_t FirstImplicit, MachineInstrBuilder &Destination, std::optional ExplicitDef, std::optional ExplicitUse) { for (std::size_t Index = FirstImplicit; Index < Source.operandCount(); ++Index) { const MachineOperand *Operand = Source.operandAt(Index); if (Operand == nullptr || !Operand->isImplicit()) return makeUnexpected(ErrorCode::VerificationError, "RISC-V call pseudo has malformed implicit operands"); if ((ExplicitDef && Operand->isDef() && isRegister(*Operand, *ExplicitDef)) || (ExplicitUse && Operand->isUse() && isRegister(*Operand, *ExplicitUse))) { continue; } auto Added = Destination.addImplicitOperand(Operand->cloneDetached()); if (!Added) return std::unexpected(Added.error()); } return {}; } [[nodiscard]] Expected expandCall(MachineInstr &Instruction, MachineFunction &Function, const TargetDescription &Target, bool IsTail) { MachineBasicBlock *Block = Instruction.parent(); const bool HasExplicitLink = Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoCALLReg); const std::size_t CalleeIndex = HasExplicitLink ? 1U : 0U; const MachineOperand *Callee = Instruction.operandAt(CalleeIndex); const MachineOperand *Mask = Instruction.operandAt(CalleeIndex + 1U); const bool HasMask = Mask != nullptr && !Mask->isImplicit() && Mask->kind() == MachineOperand::Kind::RegisterMask; if (Block == nullptr || Callee == nullptr || (Mask != nullptr && !Mask->isImplicit() && !HasMask)) { return makeUnexpected(ErrorCode::VerificationError, "RISC-V call pseudo has malformed explicit operands"); } auto Zero = physicalRegister(PhysicalRegisterID::X0); auto Link = physicalRegister(PhysicalRegisterID::X1); auto Scratch = physicalRegister(PhysicalRegisterID::X6); if (!Zero || !Link || !Scratch) return makeUnexpected(ErrorCode::InternalError, "RISC-V call registers are unavailable"); PhysicalRegister DestinationRegister = IsTail ? *Zero : *Link; if (HasExplicitLink) { const MachineOperand *Destination = Instruction.operandAt(0); const Register *DestinationValue = Destination == nullptr ? nullptr : Destination->getIf(); const auto PhysicalDestination = DestinationValue == nullptr ? std::nullopt : DestinationValue->physicalRegister(); if (!PhysicalDestination || !Destination->isDef()) { return makeUnexpected(ErrorCode::VerificationError, "RISC-V call-register pseudo has an invalid link register"); } DestinationRegister = *PhysicalDestination; } MachineIRBuilder Builder(Function, Target); auto Point = Builder.setInsertPointBefore(Instruction); if (!Point) return std::unexpected(Point.error()); Register Base; std::optional Anchor; const bool Direct = Callee->kind() == MachineOperand::Kind::GlobalSymbol || Callee->kind() == MachineOperand::Kind::ExternalSymbol; if (Direct) { const PhysicalRegister HighRegister = IsTail ? *Scratch : DestinationRegister; Base = Register::fromPhysical(HighRegister); Anchor = Instruction.preInstrSymbol() ? std::optional{std::string(*Instruction.preInstrSymbol())} : std::optional{".Lrcc_call_" + std::to_string(Function.stableID().value()) + "_" + std::to_string(Instruction.stableID().value())}; auto HighPending = Builder.buildInstr(instructionOpcode(InstructionOpcode::AUIPC)); if (!HighPending) return std::unexpected(HighPending.error()); auto HighDef = HighPending->addDef(Base); if (!HighDef) return std::unexpected(HighDef.error()); auto HighTarget = addDirectTarget(*HighPending, *Callee); if (!HighTarget) return std::unexpected(HighTarget.error()); auto High = HighPending->build(); if (!High) return std::unexpected(High.error()); auto Symbol = (*High)->setPreInstrSymbol(*Anchor); if (!Symbol) return std::unexpected(Symbol.error()); auto Decoration = copyLocation(Instruction, **High); if (!Decoration) return std::unexpected(Decoration.error()); } else { const Register *CalleeRegister = Callee->getIf(); if (CalleeRegister == nullptr || !CalleeRegister->isPhysical()) return makeUnexpected(ErrorCode::VerificationError, "RISC-V indirect call retains a virtual callee register"); Base = *CalleeRegister; } const InstructionOpcode LowOpcode = IsTail ? InstructionOpcode::JALR_TAIL : InstructionOpcode::JALR_CALL; auto LowPending = Builder.buildInstr(instructionOpcode(LowOpcode)); if (!LowPending) return std::unexpected(LowPending.error()); auto LowDef = LowPending->addDef(Register::fromPhysical(DestinationRegister)); if (!LowDef) return std::unexpected(LowDef.error()); auto LowBase = LowPending->addUse(Base); if (!LowBase) return std::unexpected(LowBase.error()); Expected LowOffset = Anchor ? LowPending->addMCSymbol(*Anchor, 0, PCRelativeLowFlag) : LowPending->addImmediate(0); if (!LowOffset) return std::unexpected(LowOffset.error()); if (HasMask) { auto LowMask = LowPending->addOperand(Mask->cloneDetached()); if (!LowMask) return std::unexpected(LowMask.error()); } const std::size_t FirstImplicit = CalleeIndex + 1U + static_cast(HasMask); auto Implicit = copyExtraImplicitOperands( Instruction, FirstImplicit, *LowPending, IsTail ? std::nullopt : std::optional{DestinationRegister}, std::nullopt); if (!Implicit) return std::unexpected(Implicit.error()); auto Low = LowPending->build(); if (!Low) return std::unexpected(Low.error()); auto Decoration = copyCommonDecoration(Instruction, **Low, !Direct, true, true); if (!Decoration) return std::unexpected(Decoration.error()); auto Records = Function.records().replaceInstructionReference(Instruction.stableID(), (*Low)->stableID()); if (!Records) return std::unexpected(Records.error()); auto Removed = Instruction.eraseFromParent(); if (!Removed) return std::unexpected(Removed.error()); return true; } class RISCVPseudoExpansionInfo final : public TargetPseudoExpansionInfo { public: [[nodiscard]] Expected preparePostRAPseudo(MachineInstr &Instruction, MachineFunction &Function, const TargetDescription &Target) const override { return prepareCopy(Instruction, Function, Target); } [[nodiscard]] Expected expandPseudo(MachineInstr &Instruction, MachinePseudoExpansionPhase Phase, MachineFunction &Function, const TargetDescription &Target) const override { if (Phase == MachinePseudoExpansionPhase::PreRA) { if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoLLA)) return expandAddressPseudo(Instruction, Function, Target, false); if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoLGA)) return expandAddressPseudo(Instruction, Function, Target, true); return false; } if (Phase == MachinePseudoExpansionPhase::PostRA) { if (Instruction.opcode() == opcode(GenericOpcode::COPY)) return expandCopy(Instruction, Function, Target); return expandSubregisterCopy(Instruction, Function, Target); } if (Phase == MachinePseudoExpansionPhase::TargetLate) { if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoCALL)) return expandCall(Instruction, Function, Target, false); if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoCALLReg)) return expandCall(Instruction, Function, Target, false); if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoTAIL)) return expandCall(Instruction, Function, Target, true); } return false; } }; } // namespace const TargetPseudoExpansionInfo &pseudoExpansionInfo() noexcept { static const RISCVPseudoExpansionInfo Value; return Value; } } // namespace rcc::riscv