diff options
Diffstat (limited to 'backend/lib/Target/RISCV/GISel')
| -rw-r--r-- | backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp | 1102 | ||||
| -rw-r--r-- | backend/lib/Target/RISCV/GISel/RISCVCallLowering.h | 49 | ||||
| -rw-r--r-- | backend/lib/Target/RISCV/GISel/RISCVGISelCombiner.cpp | 56 | ||||
| -rw-r--r-- | backend/lib/Target/RISCV/GISel/RISCVInstructionSelector.cpp | 1343 | ||||
| -rw-r--r-- | backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp | 1382 | ||||
| -rw-r--r-- | backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h | 37 | ||||
| -rw-r--r-- | backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.cpp | 388 | ||||
| -rw-r--r-- | backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.h | 30 |
8 files changed, 4387 insertions, 0 deletions
diff --git a/backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp b/backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp new file mode 100644 index 0000000..3909020 --- /dev/null +++ b/backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp @@ -0,0 +1,1102 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCVCallLowering.h" + +#include "../RISCVCallingConv.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFrameInfo.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <algorithm> +#include <array> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <optional> +#include <span> +#include <string> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +constexpr std::string_view PlanTarget = "riscv64-unknown-linux-gnu"; +constexpr std::uint32_t AllowedDirectCallFlags = + static_cast<std::uint32_t>(CallTargetFlag::DirectCall); + +[[nodiscard]] std::uint64_t stackObjectFlag(CallStackObjectFlag Flag) noexcept { + return static_cast<std::uint64_t>(Flag); +} + +[[nodiscard]] Expected<Alignment> alignment(std::uint64_t Bytes) { + return Alignment::create(Bytes); +} + +[[nodiscard]] Expected<std::uint64_t> alignTo(std::uint64_t Value, std::uint64_t AlignmentBytes) { + const std::uint64_t Remainder = Value % AlignmentBytes; + if (Remainder == 0) + return Value; + const std::uint64_t Increment = AlignmentBytes - Remainder; + if (Value > std::numeric_limits<std::uint64_t>::max() - Increment) { + return makeUnexpected(ErrorCode::Unsupported, "RISC-V ABI stack-area size overflows uint64"); + } + return Value + Increment; +} + +} // namespace + +Expected<CallLoweringPlan> +RISCVCallLowering::planFormalArguments(const FormalArgumentRequest &Request) const { + auto Convention = riscvConvention(Request.Convention); + if (!Convention) + return std::unexpected(Convention.error()); + auto OriginalCount = verifyValueSequence(Request.Arguments, true); + if (!OriginalCount) + return std::unexpected(OriginalCount.error()); + if (Request.FixedArgumentCount != *OriginalCount) { + return makeUnexpected(ErrorCode::InvalidArgument, + "formal-argument request must identify every declared argument as fixed"); + } + if (!Request.IsVariadic && Request.FixedArgumentCount != *OriginalCount) { + return makeUnexpected(ErrorCode::InvalidArgument, + "non-variadic formal request has an invalid fixed-argument boundary"); + } + if (hasStructReturn(Request.Arguments) && + (Request.Arguments.empty() || Request.Arguments.front().OriginalValue != 0 || + !Request.Arguments.front().Flags.has(ABIValueFlag::StructReturn))) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V sret must be the first formal argument"); + } + + auto Assignments = assignArguments(Request.Arguments, Request.FixedArgumentCount, + Request.IsVariadic, **Convention); + if (!Assignments) + return std::unexpected(Assignments.error()); + + VarArgLoweringState VarArgs; + if (Request.IsVariadic) { + VarArgs.IsVariadic = true; + VarArgs.FixedArgumentCount = Request.FixedArgumentCount; + VarArgs.FirstUnallocatedIntegerRegister = Assignments->FirstUnallocatedGPR; + VarArgs.OverflowAreaOffsetBytes = Assignments->StackArea.SizeBytes; + const std::size_t First = Assignments->FirstUnallocatedGPR; + const std::size_t RegisterCount = (*Convention)->IntegerArgumentRegisters.size(); + VarArgs.RegisterSaveAreaSizeBytes = (RegisterCount - First) * XLenBytes; + VarArgs.RegisterSaveAreaOffsetBytes = + VarArgs.RegisterSaveAreaSizeBytes == 0 + ? static_cast<std::int64_t>(VarArgs.OverflowAreaOffsetBytes) + : -static_cast<std::int64_t>(VarArgs.RegisterSaveAreaSizeBytes); + if (VarArgs.RegisterSaveAreaSizeBytes != 0 && First % 2 != 0) + VarArgs.RegisterSavePaddingBytes = XLenBytes; + for (std::size_t Index = First; Index < RegisterCount; ++Index) { + VarArgs.RegistersToSave.push_back( + {(*Convention)->IntegerArgumentRegisters[Index], (Index - First) * XLenBytes}); + } + } + + CallLoweringPlanDesc Plan; + Plan.Kind = CallLoweringPlanKind::FormalArguments; + Plan.Target = PlanTarget; + Plan.Convention = Request.Convention; + Plan.ArgumentAssignments = std::move(Assignments->Assignments); + Plan.StackArguments = Assignments->StackArea; + Plan.VarArgs = std::move(VarArgs); + return CallLoweringPlan::create(std::move(Plan)); +} + +Expected<CallLoweringPlan> RISCVCallLowering::planCall(const CallLoweringRequest &Request) const { + auto Convention = riscvConvention(Request.Convention); + if (!Convention) + return std::unexpected(Convention.error()); + auto OriginalCount = verifyValueSequence(Request.Arguments, true); + if (!OriginalCount) + return std::unexpected(OriginalCount.error()); + if ((!Request.IsVariadic && Request.FixedArgumentCount != *OriginalCount) || + (Request.IsVariadic && Request.FixedArgumentCount > *OriginalCount)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "call request has an invalid fixed/variadic argument boundary"); + } + if (!Request.Callee.isValid()) + return makeUnexpected(ErrorCode::InvalidArgument, "call request requires a valid callee"); + if (hasStructReturn(Request.Arguments) && + (Request.Arguments.empty() || + !Request.Arguments.front().Flags.has(ABIValueFlag::StructReturn))) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V sret must be the first call argument"); + } + if (hasStructReturn(Request.Arguments) && !Request.Results.empty()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V sret calls cannot also request direct return values"); + } + + MachineCallTarget EffectiveCallee = Request.Callee; + switch (Request.Callee.kind()) { + case MachineCallTargetKind::GlobalSymbol: { + if ((Request.Callee.targetFlags() & ~AllowedDirectCallFlags) != 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "direct RISC-V call uses unknown target flags"); + } + auto Callee = MachineCallTarget::globalSymbol( + std::string(Request.Callee.symbolName()), + Request.Callee.targetFlags() | static_cast<std::uint32_t>(CallTargetFlag::DirectCall)); + if (!Callee) + return std::unexpected(Callee.error()); + EffectiveCallee = std::move(*Callee); + break; + } + case MachineCallTargetKind::ExternalSymbol: { + if ((Request.Callee.targetFlags() & ~AllowedDirectCallFlags) != 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "direct RISC-V call uses unknown target flags"); + } + auto Callee = MachineCallTarget::externalSymbol( + std::string(Request.Callee.symbolName()), + Request.Callee.targetFlags() | static_cast<std::uint32_t>(CallTargetFlag::DirectCall)); + if (!Callee) + return std::unexpected(Callee.error()); + EffectiveCallee = std::move(*Callee); + break; + } + case MachineCallTargetKind::IndirectRegister: + if (Request.Callee.targetFlags() != 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "indirect RISC-V calls cannot carry direct-call target flags"); + } + break; + case MachineCallTargetKind::Invalid: + return makeUnexpected(ErrorCode::InvalidArgument, "call target is invalid"); + } + + const bool RequireResultRegisters = Request.TailKind == TailCallKind::NotTail; + auto ResultSequence = verifyValueSequence(Request.Results, RequireResultRegisters); + if (!ResultSequence) + return std::unexpected(ResultSequence.error()); + auto Arguments = assignArguments(Request.Arguments, Request.FixedArgumentCount, + Request.IsVariadic, **Convention); + if (!Arguments) + return std::unexpected(Arguments.error()); + auto Results = assignReturns(Request.Results, RequireResultRegisters, **Convention); + if (!Results) + return std::unexpected(Results.error()); + + auto Align16 = alignment(ABIStackAlignmentBytes); + if (!Align16) + return std::unexpected(Align16.error()); + std::uint64_t CallFrameCursor = Arguments->StackArea.SizeBytes; + std::vector<ByValCopyAssignment> ByValCopies; + for (std::size_t Index = 0; Index < Arguments->Assignments.size(); ++Index) { + const CCAssignment &Assignment = Arguments->Assignments[Index]; + if (!Assignment.CustomFlags.has(CCAssignmentFlag::ByVal)) + continue; + const ABIValue &Value = Request.Arguments[Assignment.ValueIndex]; + const std::uint64_t CopyAlignment = + std::min<std::uint64_t>(Value.ByValAlignment.bytes(), ABIStackAlignmentBytes); + auto CopyOffset = alignTo(CallFrameCursor, CopyAlignment); + if (!CopyOffset) + return std::unexpected(CopyOffset.error()); + if (*CopyOffset > std::numeric_limits<std::uint64_t>::max() - Value.ByValSizeBytes) { + return makeUnexpected(ErrorCode::Unsupported, "RISC-V byval area overflows uint64"); + } + auto CopyAlign = alignment(CopyAlignment); + if (!CopyAlign) + return std::unexpected(CopyAlign.error()); + ByValCopies.push_back( + {Index, Value.Value, Value.Type, *CopyOffset, Value.ByValSizeBytes, *CopyAlign}); + CallFrameCursor = *CopyOffset + Value.ByValSizeBytes; + } + auto CallFrameSize = alignTo(CallFrameCursor, ABIStackAlignmentBytes); + if (!CallFrameSize) + return std::unexpected(CallFrameSize.error()); + + TailCallKind Decision = TailCallKind::NotTail; + std::string RejectionReason; + if (Request.TailKind != TailCallKind::NotTail) { + auto Rejection = tailCallRejection(Request, *Arguments, *Results, ByValCopies, **Convention); + if (!Rejection) + return std::unexpected(Rejection.error()); + RejectionReason = std::move(*Rejection); + if (RejectionReason.empty()) { + Decision = Request.TailKind; + } else if (Request.TailKind == TailCallKind::MustTail) { + return makeUnexpected(ErrorCode::Unsupported, + "musttail call is not ABI-compatible: " + RejectionReason); + } else { + auto NormalResults = verifyValueSequence(Request.Results, true); + if (!NormalResults) + return std::unexpected(NormalResults.error()); + } + } + + CallLoweringPlanDesc Plan; + Plan.Kind = CallLoweringPlanKind::Call; + Plan.Target = PlanTarget; + Plan.Convention = Request.Convention; + Plan.ArgumentAssignments = std::move(Arguments->Assignments); + Plan.ReturnAssignments = std::move(*Results); + Plan.StackArguments = Arguments->StackArea; + Plan.CallFrame = CallFrameRequirements{ + Decision == TailCallKind::NotTail ? *CallFrameSize : Arguments->StackArea.SizeBytes, *Align16, + Decision == TailCallKind::NotTail, Decision == TailCallKind::NotTail, + Decision != TailCallKind::NotTail}; + if (Request.IsVariadic) { + Plan.VarArgs.IsVariadic = true; + Plan.VarArgs.FixedArgumentCount = Request.FixedArgumentCount; + } + Plan.ByValCopies = std::move(ByValCopies); + Plan.PreservedMask = createCallPreservedMask((*Convention)->ID); + if (!Plan.PreservedMask) + return makeUnexpected(ErrorCode::InternalError, "RISC-V call-preserved mask is unavailable"); + Plan.Callee = std::move(EffectiveCallee); + Plan.TailDecision = Decision; + Plan.TailRejectionReason = std::move(RejectionReason); + return CallLoweringPlan::create(std::move(Plan)); +} + +Expected<CallLoweringPlan> +RISCVCallLowering::planReturn(const ReturnLoweringRequest &Request) const { + auto Convention = riscvConvention(Request.Convention); + if (!Convention) + return std::unexpected(Convention.error()); + auto Values = assignReturns(Request.Values, true, **Convention); + if (!Values) + return std::unexpected(Values.error()); + + CallLoweringPlanDesc Plan; + Plan.Kind = CallLoweringPlanKind::Return; + Plan.Target = PlanTarget; + Plan.Convention = Request.Convention; + Plan.ReturnAssignments = std::move(*Values); + return CallLoweringPlan::create(std::move(Plan)); +} + +[[nodiscard]] CallValueTransform outgoingTransform(const CCAssignment &Assignment) noexcept { + if (Assignment.CustomFlags.has(CCAssignmentFlag::Bitcast)) + return CallValueTransform::Bitcast; + switch (Assignment.Extension) { + case ABIExtensionKind::None: + return CallValueTransform::Full; + case ABIExtensionKind::AnyExtend: + return CallValueTransform::AnyExtend; + case ABIExtensionKind::SignExtend: + return CallValueTransform::SignExtend; + case ABIExtensionKind::ZeroExtend: + return CallValueTransform::ZeroExtend; + case ABIExtensionKind::Count: + break; + } + return CallValueTransform::Full; +} + +[[nodiscard]] CallValueTransform incomingTransform(const CCAssignment &Assignment) noexcept { + if (Assignment.CustomFlags.has(CCAssignmentFlag::Bitcast)) + return CallValueTransform::Bitcast; + if (Assignment.ValueType.scalarBitWidth() < Assignment.LocationType.scalarBitWidth()) + return CallValueTransform::Truncate; + return CallValueTransform::Full; +} + +[[nodiscard]] Expected<void> addUnsignedImmediate(MachineInstrBuilder &Builder, + std::uint64_t Value) { + auto Immediate = APInt::fromUnsigned(64, Value); + if (!Immediate) + return std::unexpected(Immediate.error()); + return Builder.addIntegerImmediate(std::move(*Immediate)); +} + +[[nodiscard]] Expected<const MachineMemOperand *> +createMemoryOperand(MachineFunction &Function, MemoryAccessFlags Accesses, std::uint64_t SizeBytes, + Alignment Align, MemoryBase Base, std::int64_t Offset = 0) { + auto Size = MemorySize::fixed(SizeBytes); + if (!Size) + return std::unexpected(Size.error()); + MachineMemOperandDesc Description; + Description.Accesses = Accesses; + Description.Size = *Size; + Description.Align = Align; + Description.Base = std::move(Base); + Description.Offset = Offset; + return Function.createMemOperand(std::move(Description)); +} + +[[nodiscard]] Expected<Register> extendOutgoingRegister(MachineIRBuilder &Builder, Register Source, + const CCAssignment &Assignment) { + const std::uint32_t ValueWidth = Assignment.ValueType.scalarBitWidth(); + const std::uint32_t LocationWidth = Assignment.LocationType.scalarBitWidth(); + if (ValueWidth == LocationWidth) + return Source; + if (ValueWidth > LocationWidth) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V ABI register assignment narrows an outgoing value"); + } + + auto Extended = Builder.function().registerInfo().createVirtualRegister(Assignment.LocationType); + if (!Extended) + return std::unexpected(Extended.error()); + + Expected<MachineInstr *> Built = makeUnexpected( + ErrorCode::InternalError, "RISC-V ABI outgoing register extension was not initialized"); + switch (outgoingTransform(Assignment)) { + case CallValueTransform::AnyExtend: + case CallValueTransform::Bitcast: + Built = Builder.buildAnyExt(*Extended, Source); + break; + case CallValueTransform::SignExtend: + Built = Builder.buildSExt(*Extended, Source); + break; + case CallValueTransform::ZeroExtend: + Built = Builder.buildZExt(*Extended, Source); + break; + case CallValueTransform::Full: + case CallValueTransform::Truncate: + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V ABI outgoing register assignment lacks an extension transform"); + } + if (!Built) + return std::unexpected(Built.error()); + return *Extended; +} + +struct OutgoingRegisterValue final { + PhysicalRegister Location; + Register Value; +}; + +[[nodiscard]] Expected<OutgoingRegisterValue> +prepareOutgoingRegisterAssignment(MachineIRBuilder &Builder, const CCAssignment &Assignment, + Register Source) { + if (!Assignment.LocationRegister) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V register assignment has no location register"); + } + auto Extended = extendOutgoingRegister(Builder, Source, Assignment); + if (!Extended) + return std::unexpected(Extended.error()); + return OutgoingRegisterValue{ + .Location = *Assignment.LocationRegister, + .Value = *Extended, + }; +} + +[[nodiscard]] Expected<void> +emitOutgoingRegisterAssignments(MachineIRBuilder &Builder, + std::span<const OutgoingRegisterValue> Assignments) { + for (const OutgoingRegisterValue &Assignment : Assignments) { + auto Copy = Builder.buildCopy(Register::fromPhysical(Assignment.Location), Assignment.Value); + if (!Copy) + return std::unexpected(Copy.error()); + } + return {}; +} + +[[nodiscard]] Expected<void> emitIncomingRegisterAssignment(MachineIRBuilder &Builder, + const CCAssignment &Assignment) { + if (!Assignment.LocationRegister) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V register assignment has no location register"); + } + + const std::uint32_t ValueWidth = Assignment.ValueType.scalarBitWidth(); + const std::uint32_t LocationWidth = Assignment.LocationType.scalarBitWidth(); + if (ValueWidth == LocationWidth) { + auto Copy = Builder.buildCopy(Assignment.OriginalValue, + Register::fromPhysical(*Assignment.LocationRegister)); + if (!Copy) + return std::unexpected(Copy.error()); + return {}; + } + if (ValueWidth > LocationWidth) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V ABI register assignment widens an incoming value"); + } + + auto LocationValue = + Builder.function().registerInfo().createVirtualRegister(Assignment.LocationType); + if (!LocationValue) + return std::unexpected(LocationValue.error()); + auto Copy = + Builder.buildCopy(*LocationValue, Register::fromPhysical(*Assignment.LocationRegister)); + if (!Copy) + return std::unexpected(Copy.error()); + auto Truncation = Builder.buildTrunc(Assignment.OriginalValue, *LocationValue); + if (!Truncation) + return std::unexpected(Truncation.error()); + return {}; +} + +[[nodiscard]] Expected<void> emitStackLoad(MachineIRBuilder &Builder, + const CCAssignment &Assignment, FrameIndex Object, + std::uint64_t ObjectOffset, + const MachineMemOperand &Memory) { + auto Instruction = Builder.buildInstr(instructionOpcode(InstructionOpcode::PseudoLOAD_STACK_ARG)); + if (!Instruction) + return std::unexpected(Instruction.error()); + auto Def = Instruction->addDef(Assignment.OriginalValue); + if (!Def) + return std::unexpected(Def.error()); + auto Frame = Instruction->addFrameIndex(Object); + if (!Frame) + return std::unexpected(Frame.error()); + auto Offset = addUnsignedImmediate(*Instruction, ObjectOffset); + if (!Offset) + return std::unexpected(Offset.error()); + auto Transform = + Instruction->addImmediate(static_cast<std::int64_t>(incomingTransform(Assignment))); + if (!Transform) + return std::unexpected(Transform.error()); + auto Mem = Instruction->addMemOperand(Memory); + if (!Mem) + return std::unexpected(Mem.error()); + auto Built = Instruction->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] Expected<void> emitStackStore(MachineIRBuilder &Builder, Register Source, + FrameIndex Object, std::uint64_t ObjectOffset, + const MachineMemOperand &Memory, + CallValueTransform Transform) { + auto Instruction = + Builder.buildInstr(instructionOpcode(InstructionOpcode::PseudoSTORE_STACK_ARG)); + if (!Instruction) + return std::unexpected(Instruction.error()); + auto Use = Instruction->addUse(Source); + if (!Use) + return std::unexpected(Use.error()); + auto Frame = Instruction->addFrameIndex(Object); + if (!Frame) + return std::unexpected(Frame.error()); + auto Offset = addUnsignedImmediate(*Instruction, ObjectOffset); + if (!Offset) + return std::unexpected(Offset.error()); + auto Action = Instruction->addImmediate(static_cast<std::int64_t>(Transform)); + if (!Action) + return std::unexpected(Action.error()); + auto Mem = Instruction->addMemOperand(Memory); + if (!Mem) + return std::unexpected(Mem.error()); + auto Built = Instruction->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] Expected<void> ensureFunctionLiveIn(MachineRegisterInfo &Registers, + PhysicalRegister Reg, + std::optional<Register> InitialValue) { + const auto Existing = + std::ranges::find(Registers.liveIns(), Reg, &PhysicalRegisterLiveIn::PhysReg); + if (Existing == Registers.liveIns().end()) + return Registers.addLiveIn(Reg, InitialValue); + if (InitialValue && Existing->InitialVirtualRegister && + Existing->InitialVirtualRegister != InitialValue) { + return makeUnexpected(ErrorCode::VerificationError, + "physical formal register already maps to another virtual live-in"); + } + return {}; +} + +[[nodiscard]] Expected<void> ensureBlockLiveIn(MachineBasicBlock &Block, PhysicalRegister Reg) { + return Block.addLiveIn(Reg, LaneMask::all()); +} + +[[nodiscard]] Expected<void> verifyPlanRegisters(const CallLoweringPlan &Plan, + const MachineFunction &Function, + const TargetDescription &Target) { + const MachineRegisterInfo &Registers = Function.registerInfo(); + const auto Verify = [&Registers, &Target](const CCAssignment &Assignment) -> Expected<void> { + if (Assignment.LocationRegister && + !Target.registers().isKnownPhysicalRegister(*Assignment.LocationRegister)) { + return makeUnexpected(ErrorCode::VerificationError, + "call-lowering plan names an unknown ABI location register"); + } + const Register Reg = Assignment.OriginalValue; + if (Reg.isNoRegister()) + return {}; + if (Reg.isPhysical()) { + if (!Target.registers().isKnownPhysicalRegister(*Reg.physicalRegister())) { + return makeUnexpected(ErrorCode::VerificationError, + "call-lowering plan names an unknown physical value register"); + } + return {}; + } + auto Info = Registers.virtualRegisterInfo(Reg); + if (!Info) + return std::unexpected(Info.error()); + if (!Info->get().Type || *Info->get().Type != Assignment.ValueType) { + return makeUnexpected( + ErrorCode::VerificationError, + "call-lowering plan register LowLevelType differs from MachineRegisterInfo"); + } + return {}; + }; + for (const CCAssignment &Assignment : Plan.argumentAssignments()) { + auto Result = Verify(Assignment); + if (!Result) + return std::unexpected(Result.error()); + } + for (const CCAssignment &Assignment : Plan.returnAssignments()) { + auto Result = Verify(Assignment); + if (!Result) + return std::unexpected(Result.error()); + } + if (const MachineCallTarget *Callee = Plan.callee(); + Callee != nullptr && Callee->kind() == MachineCallTargetKind::IndirectRegister) { + const Register CalleeReg = Callee->indirectRegister(); + if (CalleeReg.isPhysical()) { + const std::optional<PhysicalRegister> Physical = CalleeReg.physicalRegister(); + if (!Physical || !Target.registers().isKnownPhysicalRegister(*Physical)) { + return makeUnexpected(ErrorCode::VerificationError, + "indirect RISC-V callee names an unknown physical register"); + } + } else { + auto Info = Registers.virtualRegisterInfo(CalleeReg); + if (!Info) + return std::unexpected(Info.error()); + const std::optional<LowLevelType> Type = Info->get().Type; + if (!Type || ((!Type->isPointer() && !Type->isCategoryNeutralScalar()) || + Type->scalarBitWidth() != 64)) { + return makeUnexpected( + ErrorCode::VerificationError, + "indirect RISC-V callee requires a 64-bit pointer or integer LowLevelType"); + } + } + } + return {}; +} + +[[nodiscard]] Expected<void> verifyRequiredOpcodes(const MachineIRBuilder &Builder) { + static constexpr std::array Required{ + InstructionOpcode::PseudoLOAD_STACK_ARG, InstructionOpcode::PseudoSTORE_STACK_ARG, + InstructionOpcode::PseudoBYVAL_COPY, InstructionOpcode::ADJCALLSTACKDOWN, + InstructionOpcode::ADJCALLSTACKUP, InstructionOpcode::PseudoCALL, + InstructionOpcode::PseudoTAIL, InstructionOpcode::PseudoRET, + }; + for (const InstructionOpcode OpcodeValue : Required) { + if (Builder.target().instructionDescriptor(instructionOpcode(OpcodeValue)) == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V target description lacks a call-lowering pseudo"); + } + } + return {}; +} + +[[nodiscard]] Expected<void> verifyFormalLiveIns(const CallLoweringPlan &Plan, + const MachineFunction &Function) { + if (Plan.kind() != CallLoweringPlanKind::FormalArguments) + return {}; + + const auto LiveIns = Function.registerInfo().liveIns(); + for (const CCAssignment &Assignment : Plan.argumentAssignments()) { + if (!Assignment.LocationRegister || !Assignment.OriginalValue.isVirtual()) + continue; + const auto Existing = + std::ranges::find(LiveIns, *Assignment.LocationRegister, &PhysicalRegisterLiveIn::PhysReg); + if (Existing != LiveIns.end() && Existing->InitialVirtualRegister && + *Existing->InitialVirtualRegister != Assignment.OriginalValue) { + return makeUnexpected(ErrorCode::VerificationError, + "physical formal register already maps to another virtual live-in"); + } + } + return {}; +} + +[[nodiscard]] Expected<void> preflightPlan(MachineIRBuilder &Builder, + const CallLoweringPlan &Plan) { + auto Convention = riscvConvention(Plan.convention()); + if (!Convention) + return std::unexpected(Convention.error()); + if (Plan.target() != PlanTarget) { + return makeUnexpected(ErrorCode::InvalidArgument, + "call-lowering plan belongs to another target or ABI"); + } + auto InsertPoint = Builder.verifyInsertPoint(); + if (!InsertPoint) + return std::unexpected(InsertPoint.error()); + auto Opcodes = verifyRequiredOpcodes(Builder); + if (!Opcodes) + return std::unexpected(Opcodes.error()); + auto Registers = verifyPlanRegisters(Plan, Builder.function(), Builder.target()); + if (!Registers) + return std::unexpected(Registers.error()); + auto LiveIns = verifyFormalLiveIns(Plan, Builder.function()); + if (!LiveIns) + return std::unexpected(LiveIns.error()); + for (const CCAssignment &Assignment : Plan.argumentAssignments()) { + if (Assignment.StackOffsetBytes && + *Assignment.StackOffsetBytes > + static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V stack assignment offset exceeds the frame-index range"); + } + } + for (const ByValCopyAssignment &Copy : Plan.byValCopies()) { + if (Copy.StackOffsetBytes > + static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V byval offset exceeds the frame-index range"); + } + } + if (Plan.kind() != CallLoweringPlanKind::Return && Builder.function().frameInfo().isFrozen()) { + return makeUnexpected(ErrorCode::VerificationError, + "call lowering cannot modify a frozen machine frame"); + } + if (Plan.kind() == CallLoweringPlanKind::FormalArguments && + (Builder.function().firstBlock() == nullptr || + Builder.insertionBlock() != Builder.function().firstBlock())) { + return makeUnexpected(ErrorCode::InvalidArgument, + "formal arguments must be lowered in the entry machine block"); + } + return {}; +} + +[[nodiscard]] Expected<void> updateMaximumFrameAlignment(MachineFrameState &State, + Alignment Required) { + if (!State.MaxAlignment || State.MaxAlignment->bytes() < Required.bytes()) + State.MaxAlignment = Required; + return {}; +} + +[[nodiscard]] Expected<void> emitFormalArguments(MachineIRBuilder &Builder, + const CallLoweringPlan &Plan) { + MachineFunction &Function = Builder.function(); + MachineBasicBlock &Entry = *Function.firstBlock(); + MachineRegisterInfo &Registers = Function.registerInfo(); + MachineFrameInfo &Frame = Function.frameInfo(); + Alignment RequiredFrameAlignment; + + for (const CCAssignment &Assignment : Plan.argumentAssignments()) { + if (Assignment.LocationRegister) { + const PhysicalRegister Location = *Assignment.LocationRegister; + const std::optional<Register> Initial = + Assignment.OriginalValue.isVirtual() ? std::optional<Register>(Assignment.OriginalValue) + : std::nullopt; + auto FunctionLiveIn = ensureFunctionLiveIn(Registers, Location, Initial); + if (!FunctionLiveIn) + return std::unexpected(FunctionLiveIn.error()); + auto BlockLiveIn = ensureBlockLiveIn(Entry, Location); + if (!BlockLiveIn) + return std::unexpected(BlockLiveIn.error()); + auto Copy = emitIncomingRegisterAssignment(Builder, Assignment); + if (!Copy) + return std::unexpected(Copy.error()); + continue; + } + + if (!Assignment.StackOffsetBytes) { + return makeUnexpected(ErrorCode::InternalError, + "verified formal stack assignment has no stack offset"); + } + StackObjectDesc Object; + Object.Kind = StackObjectKind::FixedABI; + Object.Size = Assignment.StackSlotSizeBytes; + Object.Align = Assignment.LocationAlignment; + Object.Offset = static_cast<std::int64_t>(*Assignment.StackOffsetBytes); + Object.IsImmutable = true; + Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::IncomingArgument); + auto FrameObject = Frame.createObject(std::move(Object)); + if (!FrameObject) + return std::unexpected(FrameObject.error()); + auto Memory = + createMemoryOperand(Function, {MemoryAccessFlag::Load}, Assignment.LocationSizeBytes, + Assignment.LocationAlignment, MemoryBase::frameIndex(*FrameObject)); + if (!Memory) + return std::unexpected(Memory.error()); + auto Load = emitStackLoad(Builder, Assignment, *FrameObject, 0, **Memory); + if (!Load) + return std::unexpected(Load.error()); + if (RequiredFrameAlignment.bytes() < Assignment.LocationAlignment.bytes()) + RequiredFrameAlignment = Assignment.LocationAlignment; + } + + const VarArgLoweringState &VarArgs = Plan.varArgs(); + if (!VarArgs.IsVariadic) { + if (RequiredFrameAlignment.bytes() == 1) + return {}; + MachineFrameState State = Frame.state(); + auto AlignmentUpdate = updateMaximumFrameAlignment(State, RequiredFrameAlignment); + if (!AlignmentUpdate) + return std::unexpected(AlignmentUpdate.error()); + return Frame.setState(std::move(State)); + } + + auto Align8 = alignment(XLenBytes); + auto Align16 = alignment(ABIStackAlignmentBytes); + if (!Align8 || !Align16) + return makeUnexpected(ErrorCode::InternalError, "RISC-V ABI primitive type facts are invalid"); + + FrameIndex SaveObject; + if (VarArgs.RegisterSaveAreaSizeBytes == 0) { + StackObjectDesc Object; + Object.Kind = StackObjectKind::FixedABI; + Object.Size = XLenBytes; + Object.Align = *Align8; + Object.Offset = VarArgs.RegisterSaveAreaOffsetBytes; + Object.IsImmutable = true; + Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::VarArgSaveArea); + auto Created = Frame.createObject(std::move(Object)); + if (!Created) + return std::unexpected(Created.error()); + SaveObject = *Created; + } else { + StackObjectDesc Object; + Object.Kind = StackObjectKind::FixedABI; + Object.Size = VarArgs.RegisterSaveAreaSizeBytes; + Object.Align = VarArgs.FirstUnallocatedIntegerRegister % 2 == 0 ? *Align16 : *Align8; + Object.Offset = VarArgs.RegisterSaveAreaOffsetBytes; + Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::VarArgSaveArea); + auto Created = Frame.createObject(std::move(Object)); + if (!Created) + return std::unexpected(Created.error()); + SaveObject = *Created; + + if (VarArgs.RegisterSavePaddingBytes != 0) { + StackObjectDesc Padding; + Padding.Kind = StackObjectKind::FixedABI; + Padding.Size = VarArgs.RegisterSavePaddingBytes; + Padding.Align = *Align8; + Padding.Offset = VarArgs.RegisterSaveAreaOffsetBytes - + static_cast<std::int64_t>(VarArgs.RegisterSavePaddingBytes); + Padding.IsImmutable = true; + Padding.TargetFlags = stackObjectFlag(CallStackObjectFlag::VarArgAlignmentPadding); + auto CreatedPadding = Frame.createObject(std::move(Padding)); + if (!CreatedPadding) + return std::unexpected(CreatedPadding.error()); + } + } + + for (const VarArgRegisterSave Save : VarArgs.RegistersToSave) { + auto FunctionLiveIn = ensureFunctionLiveIn(Registers, Save.Reg, std::nullopt); + if (!FunctionLiveIn) + return std::unexpected(FunctionLiveIn.error()); + auto BlockLiveIn = ensureBlockLiveIn(Entry, Save.Reg); + if (!BlockLiveIn) + return std::unexpected(BlockLiveIn.error()); + auto Memory = createMemoryOperand(Function, {MemoryAccessFlag::Store}, XLenBytes, *Align8, + MemoryBase::frameIndex(SaveObject), + static_cast<std::int64_t>(Save.OffsetBytes)); + if (!Memory) + return std::unexpected(Memory.error()); + auto Store = emitStackStore(Builder, Register::fromPhysical(Save.Reg), SaveObject, + Save.OffsetBytes, **Memory, CallValueTransform::Full); + if (!Store) + return std::unexpected(Store.error()); + } + + MachineFrameState State = Frame.state(); + const Alignment VarArgAlignment = + RequiredFrameAlignment.bytes() > Align16->bytes() ? RequiredFrameAlignment : *Align16; + auto AlignmentUpdate = updateMaximumFrameAlignment(State, VarArgAlignment); + if (!AlignmentUpdate) + return std::unexpected(AlignmentUpdate.error()); + return Frame.setState(std::move(State)); +} + +[[nodiscard]] Expected<void> emitCallFrameAdjustment(MachineIRBuilder &Builder, + InstructionOpcode OpcodeValue, + const CallFrameRequirements &Frame) { + if (Frame.SizeBytes > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V call-frame size does not fit a MIR immediate"); + } + auto Instruction = Builder.buildInstr(instructionOpcode(OpcodeValue)); + if (!Instruction) + return std::unexpected(Instruction.error()); + auto Size = Instruction->addImmediate(static_cast<std::int64_t>(Frame.SizeBytes)); + if (!Size) + return std::unexpected(Size.error()); + auto PreparedBytes = Instruction->addImmediate(0); + if (!PreparedBytes) + return std::unexpected(PreparedBytes.error()); + auto Built = Instruction->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] Expected<std::unordered_map<std::size_t, Register>> +emitByValCopies(MachineIRBuilder &Builder, const CallLoweringPlan &Plan) { + MachineFunction &Function = Builder.function(); + MachineFrameInfo &Frame = Function.frameInfo(); + std::unordered_map<std::size_t, Register> Replacements; + for (const ByValCopyAssignment Copy : Plan.byValCopies()) { + StackObjectDesc Object; + Object.Kind = StackObjectKind::Target; + Object.Size = Copy.SizeBytes; + Object.Align = Copy.Align; + Object.LocalOffset = static_cast<std::int64_t>(Copy.StackOffsetBytes); + Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::ByValCopy); + auto FrameObject = Frame.createObject(std::move(Object)); + if (!FrameObject) + return std::unexpected(FrameObject.error()); + auto Temporary = Function.registerInfo().createVirtualRegister(Copy.PointerType); + if (!Temporary) + return std::unexpected(Temporary.error()); + + auto SourceMemory = createMemoryOperand(Function, {MemoryAccessFlag::Load}, Copy.SizeBytes, + Copy.Align, MemoryBase{}); + if (!SourceMemory) + return std::unexpected(SourceMemory.error()); + auto DestinationMemory = + createMemoryOperand(Function, {MemoryAccessFlag::Store}, Copy.SizeBytes, Copy.Align, + MemoryBase::frameIndex(*FrameObject)); + if (!DestinationMemory) + return std::unexpected(DestinationMemory.error()); + + auto Instruction = Builder.buildInstr(instructionOpcode(InstructionOpcode::PseudoBYVAL_COPY)); + if (!Instruction) + return std::unexpected(Instruction.error()); + auto Def = Instruction->addDef(*Temporary); + if (!Def) + return std::unexpected(Def.error()); + auto Use = Instruction->addUse(Copy.Source); + if (!Use) + return std::unexpected(Use.error()); + auto FrameOperand = Instruction->addFrameIndex(*FrameObject); + if (!FrameOperand) + return std::unexpected(FrameOperand.error()); + auto Size = addUnsignedImmediate(*Instruction, Copy.SizeBytes); + if (!Size) + return std::unexpected(Size.error()); + auto Align = addUnsignedImmediate(*Instruction, Copy.Align.bytes()); + if (!Align) + return std::unexpected(Align.error()); + auto SourceMem = Instruction->addMemOperand(**SourceMemory); + if (!SourceMem) + return std::unexpected(SourceMem.error()); + auto DestinationMem = Instruction->addMemOperand(**DestinationMemory); + if (!DestinationMem) + return std::unexpected(DestinationMem.error()); + auto Built = Instruction->build(); + if (!Built) + return std::unexpected(Built.error()); + Replacements.emplace(Copy.ArgumentAssignmentIndex, *Temporary); + } + return Replacements; +} + +[[nodiscard]] Expected<void> +emitOutgoingArguments(MachineIRBuilder &Builder, const CallLoweringPlan &Plan, + const std::unordered_map<std::size_t, Register> &Replacements) { + MachineFunction &Function = Builder.function(); + MachineFrameInfo &Frame = Function.frameInfo(); + const auto Assignments = Plan.argumentAssignments(); + std::vector<OutgoingRegisterValue> RegisterAssignments; + RegisterAssignments.reserve(Assignments.size()); + for (std::size_t Index = 0; Index < Assignments.size(); ++Index) { + const CCAssignment &Assignment = Assignments[Index]; + const auto Replacement = Replacements.find(Index); + const Register Source = + Replacement == Replacements.end() ? Assignment.OriginalValue : Replacement->second; + if (Assignment.LocationRegister) { + auto Prepared = prepareOutgoingRegisterAssignment(Builder, Assignment, Source); + if (!Prepared) + return std::unexpected(Prepared.error()); + RegisterAssignments.push_back(*Prepared); + continue; + } + + if (!Assignment.StackOffsetBytes) { + return makeUnexpected(ErrorCode::InternalError, + "verified outgoing stack assignment has no stack offset"); + } + StackObjectDesc Object; + Object.Size = Assignment.StackSlotSizeBytes; + Object.Align = Assignment.LocationAlignment; + Object.TargetFlags = stackObjectFlag(CallStackObjectFlag::OutgoingArgument); + if (Plan.tailDecision() == TailCallKind::NotTail) { + Object.Kind = StackObjectKind::Target; + Object.LocalOffset = static_cast<std::int64_t>(*Assignment.StackOffsetBytes); + } else { + Object.Kind = StackObjectKind::FixedABI; + Object.Offset = static_cast<std::int64_t>(*Assignment.StackOffsetBytes); + } + auto FrameObject = Frame.createObject(std::move(Object)); + if (!FrameObject) + return std::unexpected(FrameObject.error()); + auto Memory = + createMemoryOperand(Function, {MemoryAccessFlag::Store}, Assignment.LocationSizeBytes, + Assignment.LocationAlignment, MemoryBase::frameIndex(*FrameObject)); + if (!Memory) + return std::unexpected(Memory.error()); + auto Store = + emitStackStore(Builder, Source, *FrameObject, 0, **Memory, outgoingTransform(Assignment)); + if (!Store) + return std::unexpected(Store.error()); + } + return emitOutgoingRegisterAssignments( + Builder, std::span<const OutgoingRegisterValue>(RegisterAssignments)); +} + +[[nodiscard]] Expected<void> addCallTarget(MachineInstrBuilder &Instruction, + const MachineCallTarget &Callee) { + switch (Callee.kind()) { + case MachineCallTargetKind::GlobalSymbol: + return Instruction.addGlobalSymbol(std::string(Callee.symbolName()), 0, Callee.targetFlags()); + case MachineCallTargetKind::ExternalSymbol: + return Instruction.addExternalSymbol(std::string(Callee.symbolName()), 0, Callee.targetFlags()); + case MachineCallTargetKind::IndirectRegister: + return Instruction.addUse(Callee.indirectRegister()); + case MachineCallTargetKind::Invalid: + break; + } + return makeUnexpected(ErrorCode::InternalError, + "verified call-lowering plan has an invalid callee"); +} + +[[nodiscard]] Expected<PhysicalRegister> requiredLocationRegister(const CCAssignment &Assignment) { + if (!Assignment.LocationRegister) { + return makeUnexpected(ErrorCode::InternalError, + "verified return assignment has no location register"); + } + return *Assignment.LocationRegister; +} + +[[nodiscard]] Expected<void> emitCallInstruction(MachineIRBuilder &Builder, + const CallLoweringPlan &Plan) { + const bool IsTail = Plan.tailDecision() != TailCallKind::NotTail; + auto Instruction = Builder.buildInstr( + instructionOpcode(IsTail ? InstructionOpcode::PseudoTAIL : InstructionOpcode::PseudoCALL)); + if (!Instruction) + return std::unexpected(Instruction.error()); + auto Callee = addCallTarget(*Instruction, *Plan.callee()); + if (!Callee) + return std::unexpected(Callee.error()); + auto Mask = Instruction->addRegisterMask(Plan.preservedMask()); + if (!Mask) + return std::unexpected(Mask.error()); + + std::unordered_set<PhysicalRegister> DynamicUses; + for (const CCAssignment &Assignment : Plan.argumentAssignments()) { + if (Assignment.LocationRegister && DynamicUses.insert(*Assignment.LocationRegister).second) { + auto Use = Instruction->addImplicitUse(Register::fromPhysical(*Assignment.LocationRegister)); + if (!Use) + return std::unexpected(Use.error()); + } + } + std::unordered_set<PhysicalRegister> DynamicDefs; + if (!IsTail) { + for (const CCAssignment &Assignment : Plan.returnAssignments()) { + if (Assignment.LocationRegister && DynamicDefs.insert(*Assignment.LocationRegister).second) { + auto Def = + Instruction->addImplicitDef(Register::fromPhysical(*Assignment.LocationRegister)); + if (!Def) + return std::unexpected(Def.error()); + } + } + } + auto Built = Instruction->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] Expected<void> emitCall(MachineIRBuilder &Builder, const CallLoweringPlan &Plan) { + if (Plan.callFrame().EmitSetup) { + auto Down = + emitCallFrameAdjustment(Builder, InstructionOpcode::ADJCALLSTACKDOWN, Plan.callFrame()); + if (!Down) + return std::unexpected(Down.error()); + } + + auto Replacements = emitByValCopies(Builder, Plan); + if (!Replacements) + return std::unexpected(Replacements.error()); + auto Arguments = emitOutgoingArguments(Builder, Plan, *Replacements); + if (!Arguments) + return std::unexpected(Arguments.error()); + auto Call = emitCallInstruction(Builder, Plan); + if (!Call) + return std::unexpected(Call.error()); + + if (Plan.callFrame().EmitDestroy) { + auto Up = emitCallFrameAdjustment(Builder, InstructionOpcode::ADJCALLSTACKUP, Plan.callFrame()); + if (!Up) + return std::unexpected(Up.error()); + } + if (Plan.tailDecision() == TailCallKind::NotTail) { + for (const CCAssignment &Assignment : Plan.returnAssignments()) { + auto Copy = emitIncomingRegisterAssignment(Builder, Assignment); + if (!Copy) + return std::unexpected(Copy.error()); + } + } + + MachineFrameInfo &Frame = Builder.function().frameInfo(); + MachineFrameState State = Frame.state(); + State.HasCalls = true; + State.HasTailCall = State.HasTailCall || Plan.tailDecision() != TailCallKind::NotTail; + State.AdjustsStack = + State.AdjustsStack || (Plan.callFrame().EmitSetup && Plan.callFrame().SizeBytes != 0); + State.HasMustTailInVarArgFunc = + State.HasMustTailInVarArgFunc || + (Plan.tailDecision() == TailCallKind::MustTail && Plan.varArgs().IsVariadic); + if (Plan.tailDecision() == TailCallKind::NotTail) { + State.MaxCallFrameSize = + std::max(State.MaxCallFrameSize.value_or(0), Plan.callFrame().SizeBytes); + } + auto AlignmentUpdate = updateMaximumFrameAlignment(State, Plan.callFrame().Align); + if (!AlignmentUpdate) + return std::unexpected(AlignmentUpdate.error()); + return Frame.setState(std::move(State)); +} + +[[nodiscard]] Expected<void> emitReturn(MachineIRBuilder &Builder, const CallLoweringPlan &Plan) { + std::vector<OutgoingRegisterValue> RegisterAssignments; + RegisterAssignments.reserve(Plan.returnAssignments().size()); + for (const CCAssignment &Assignment : Plan.returnAssignments()) { + auto Prepared = + prepareOutgoingRegisterAssignment(Builder, Assignment, Assignment.OriginalValue); + if (!Prepared) + return std::unexpected(Prepared.error()); + RegisterAssignments.push_back(*Prepared); + } + auto Copies = emitOutgoingRegisterAssignments( + Builder, std::span<const OutgoingRegisterValue>(RegisterAssignments)); + if (!Copies) + return std::unexpected(Copies.error()); + + auto Return = Builder.buildInstr(instructionOpcode(InstructionOpcode::PseudoRET)); + if (!Return) + return std::unexpected(Return.error()); + for (const CCAssignment &Assignment : Plan.returnAssignments()) { + auto Location = requiredLocationRegister(Assignment); + if (!Location) + return std::unexpected(Location.error()); + auto Use = Return->addImplicitUse(Register::fromPhysical(*Location)); + if (!Use) + return std::unexpected(Use.error()); + } + auto Built = Return->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +Expected<void> RISCVCallLowering::emitPlan(MachineIRBuilder &Builder, + const CallLoweringPlan &Plan) const { + auto Preflight = preflightPlan(Builder, Plan); + if (!Preflight) + return std::unexpected(Preflight.error()); + switch (Plan.kind()) { + case CallLoweringPlanKind::FormalArguments: + return emitFormalArguments(Builder, Plan); + case CallLoweringPlanKind::Call: + return emitCall(Builder, Plan); + case CallLoweringPlanKind::Return: + return emitReturn(Builder, Plan); + } + return makeUnexpected(ErrorCode::InternalError, "call-lowering plan kind is invalid"); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/GISel/RISCVCallLowering.h b/backend/lib/Target/RISCV/GISel/RISCVCallLowering.h new file mode 100644 index 0000000..bc03fe8 --- /dev/null +++ b/backend/lib/Target/RISCV/GISel/RISCVCallLowering.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_LIB_TARGET_RISCV_GISEL_RISCVCALLLOWERING_H +#define RCC_LIB_TARGET_RISCV_GISEL_RISCVCALLLOWERING_H + +#include "../RISCVCallingConv.h" +#include "rcc/CodeGen/TargetCallLowering.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" + +#include <cstdint> + +namespace rcc::riscv { + +enum class CallTargetFlag : std::uint32_t { + DirectCall = 1U << 0U, +}; + +enum class CallStackObjectFlag : std::uint64_t { + IncomingArgument = 1ULL << 0U, + OutgoingArgument = 1ULL << 1U, + ByValCopy = 1ULL << 2U, + VarArgSaveArea = 1ULL << 3U, + VarArgAlignmentPadding = 1ULL << 4U, +}; + +enum class CallValueTransform : std::uint8_t { + Full, + AnyExtend, + SignExtend, + ZeroExtend, + Truncate, + Bitcast, +}; + +class RISCVCallLowering final : public TargetCallLowering { +public: + [[nodiscard]] Expected<CallLoweringPlan> + planFormalArguments(const FormalArgumentRequest &Request) const override; + [[nodiscard]] Expected<CallLoweringPlan> + planCall(const CallLoweringRequest &Request) const override; + [[nodiscard]] Expected<CallLoweringPlan> + planReturn(const ReturnLoweringRequest &Request) const override; + [[nodiscard]] Expected<void> emitPlan(MachineIRBuilder &Builder, + const CallLoweringPlan &Plan) const override; +}; + +} // namespace rcc::riscv + +#endif // RCC_LIB_TARGET_RISCV_GISEL_RISCVCALLLOWERING_H diff --git a/backend/lib/Target/RISCV/GISel/RISCVGISelCombiner.cpp b/backend/lib/Target/RISCV/GISel/RISCVGISelCombiner.cpp new file mode 100644 index 0000000..bf67c0a --- /dev/null +++ b/backend/lib/Target/RISCV/GISel/RISCVGISelCombiner.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "rcc/CodeGen/GlobalISel/GIMatchTableExecutor.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" + +#include <string> + +namespace rcc::riscv { +namespace { + +#define GET_GLOBALISEL_COMBINER_TABLE +#include "RISCVGenGlobalISelCombiner.inc" +#undef GET_GLOBALISEL_COMBINER_TABLE + +class RISCVGISelCombinerExecutor final : public GIMatchTableExecutor { +protected: + [[nodiscard]] Expected<bool> + matchesTargetPredicate(std::string_view Name, const MachineInstr &Instruction, + const MachineFunction &Function, + const TargetDescription &Target) const override { + if (Name != "sext_cheaper_than_zext") + return GIMatchTableExecutor::matchesTargetPredicate(Name, Instruction, Function, Target); + const MachineOperand *Destination = Instruction.operandAt(0); + const MachineOperand *Source = Instruction.operandAt(1); + const Register *DestinationReg = + Destination == nullptr ? nullptr : Destination->getIf<Register>(); + const Register *SourceReg = Source == nullptr ? nullptr : Source->getIf<Register>(); + if (DestinationReg == nullptr || SourceReg == nullptr || !DestinationReg->isVirtual() || + !SourceReg->isVirtual()) + return false; + auto DestinationType = Function.registerInfo().getType(*DestinationReg); + auto SourceType = Function.registerInfo().getType(*SourceReg); + if (!DestinationType || !SourceType) + return false; + return DestinationType->isCategoryNeutralScalar() && SourceType->isCategoryNeutralScalar() && + DestinationType->scalarBitWidth() == 64 && SourceType->scalarBitWidth() == 32; + } +}; + +} // namespace + +const GIMatchTable &giselCombinerTable() noexcept { + static constexpr GIMatchTable Table = generatedGlobalISelCombinerMatchTable(); + return Table; +} + +const GIMatchTableExecutor &giselCombinerExecutor() noexcept { + static const RISCVGISelCombinerExecutor Executor; + return Executor; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/GISel/RISCVInstructionSelector.cpp b/backend/lib/Target/RISCV/GISel/RISCVInstructionSelector.cpp new file mode 100644 index 0000000..4504fa7 --- /dev/null +++ b/backend/lib/Target/RISCV/GISel/RISCVInstructionSelector.cpp @@ -0,0 +1,1343 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "RISCVImmediateMaterialization.h" +#include "RISCVInstrInfo.h" +#include "RISCVRegisterBankInfo.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/GlobalISel/GISelValueTracking.h" +#include "rcc/CodeGen/GlobalISel/InstructionSelector.h" +#include "rcc/CodeGen/GlobalObject.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/MachineMemOperand.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVSubtarget.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <algorithm> +#include <array> +#include <bit> +#include <cstdint> +#include <initializer_list> +#include <limits> +#include <optional> +#include <ranges> +#include <string> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +constexpr std::uint32_t AbsoluteHighFlag = 32; +constexpr std::uint32_t AbsoluteLowFlag = 64; + +#define GET_GLOBALISEL_MATCH_TABLE +#include "RISCVGenGlobalISel.inc" +#undef GET_GLOBALISEL_MATCH_TABLE + +[[nodiscard]] std::optional<LowLevelType> registerType(const MachineFunction &Function, + const MachineOperand &Operand) { + const Register *Reg = Operand.getIf<Register>(); + if (Reg == nullptr || !Reg->isVirtual()) + return std::nullopt; + auto Info = Function.registerInfo().virtualRegisterInfo(*Reg); + if (!Info) + return std::nullopt; + return Info->get().Type; +} + +[[nodiscard]] std::optional<LowLevelType> resultType(const MachineFunction &Function, + const MachineInstr &Instruction) { + const MachineOperand *Result = Instruction.operandAt(0); + return Result == nullptr ? std::nullopt : registerType(Function, *Result); +} + +[[nodiscard]] std::optional<LowLevelType> +operandType(const MachineFunction &Function, const MachineInstr &Instruction, std::size_t Index) { + const MachineOperand *Operand = Instruction.operandAt(Index); + return Operand == nullptr ? std::nullopt : registerType(Function, *Operand); +} + +[[nodiscard]] bool isSupportedFloatingScalar(LowLevelType Type) noexcept { + return Type.isCategoryNeutralScalar() && + (Type.scalarBitWidth() == 32 || Type.scalarBitWidth() == 64); +} + +[[nodiscard]] Expected<InstructionOpcode> floatingCompareOpcode(FloatingPredicate Predicate, + std::uint32_t BitWidth) { + if (BitWidth == 32) { + switch (Predicate) { + case FloatingPredicate::OrderedEqual: + return InstructionOpcode::FEQ_S; + case FloatingPredicate::OrderedLessThan: + return InstructionOpcode::FLT_S; + case FloatingPredicate::OrderedLessEqual: + return InstructionOpcode::FLE_S; + default: + break; + } + } else if (BitWidth == 64) { + switch (Predicate) { + case FloatingPredicate::OrderedEqual: + return InstructionOpcode::FEQ_D; + case FloatingPredicate::OrderedLessThan: + return InstructionOpcode::FLT_D; + case FloatingPredicate::OrderedLessEqual: + return InstructionOpcode::FLE_D; + default: + break; + } + } + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V floating comparison predicate is not directly supported"); +} + +// Convert a floating predicate by swapping or inverting it to one supported by +// a scalar RISC-V comparison instruction. +[[nodiscard]] bool legalizeFloatingPredicate(Register &Left, Register &Right, + FloatingPredicate &Predicate, + bool &NeedsInvert) noexcept { + switch (Predicate) { + case FloatingPredicate::OrderedGreaterThan: + Predicate = FloatingPredicate::OrderedLessThan; + std::swap(Left, Right); + return true; + case FloatingPredicate::OrderedGreaterEqual: + Predicate = FloatingPredicate::OrderedLessEqual; + std::swap(Left, Right); + return true; + case FloatingPredicate::UnorderedNotEqual: + Predicate = FloatingPredicate::OrderedEqual; + NeedsInvert = true; + return true; + case FloatingPredicate::UnorderedGreaterThan: + Predicate = FloatingPredicate::OrderedLessEqual; + NeedsInvert = true; + return true; + case FloatingPredicate::UnorderedGreaterEqual: + Predicate = FloatingPredicate::OrderedLessThan; + NeedsInvert = true; + return true; + case FloatingPredicate::UnorderedLessThan: + Predicate = FloatingPredicate::OrderedLessEqual; + NeedsInvert = true; + std::swap(Left, Right); + return true; + case FloatingPredicate::UnorderedLessEqual: + Predicate = FloatingPredicate::OrderedLessThan; + NeedsInvert = true; + std::swap(Left, Right); + return true; + default: + return false; + } +} + +[[nodiscard]] bool isSupportedIntegerType(LowLevelType Type) noexcept { + return (Type.isCategoryNeutralScalar() && Type.scalarBitWidth() != 0 && + Type.scalarBitWidth() <= 64) || + (Type.isPointer() && Type.scalarBitWidth() == 64); +} + +[[nodiscard]] Expected<void> insertPhysicalRegister(MachineInstr &Instruction, std::size_t Index, + PhysicalRegister Reg, + RegisterOperandFlag Flag) { + auto Operand = MachineOperand::registerOperand(Register::fromPhysical(Reg), {Flag}); + if (!Operand) + return std::unexpected(Operand.error()); + return Instruction.insertOperand(Index, std::move(*Operand)); +} + +[[nodiscard]] Expected<std::int64_t> signedImmediate(const MachineOperand &Operand) { + if (const std::int64_t *Value = Operand.getIf<std::int64_t>()) + return *Value; + const auto *Integer = Operand.getIf<IntegerImmediateOperand>(); + if (Integer == nullptr || Integer->Value.bitWidth() == 0 || Integer->Value.bitWidth() > 64) + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V constant selection requires an immediate no wider than 64 bits"); + std::uint64_t Raw = Integer->Value.words().empty() ? 0 : Integer->Value.words().front(); + const std::uint32_t Width = Integer->Value.bitWidth(); + if (Width < 64 && (Raw & (std::uint64_t{1} << (Width - 1U))) != 0) + Raw |= ~std::uint64_t{0} << Width; + return std::bit_cast<std::int64_t>(Raw); +} + +[[nodiscard]] std::optional<std::int64_t> +directIntegerConstant(const MachineRegisterInfo &RegisterInfo, Register ValueRegister) { + if (!ValueRegister.isVirtual()) + return std::nullopt; + const MachineInstr *Definition = RegisterInfo.getVRegDef(ValueRegister); + if (Definition == nullptr || Definition->opcode() != opcode(GenericOpcode::G_CONSTANT)) + return std::nullopt; + const MachineOperand *ValueOperand = Definition->operandAt(1); + if (ValueOperand == nullptr) + return std::nullopt; + auto Value = signedImmediate(*ValueOperand); + return Value ? std::optional<std::int64_t>(*Value) : std::nullopt; +} + +struct SelectedAddress final { + MachineOperand Base; + std::int64_t Offset = 0; +}; + +[[nodiscard]] std::optional<SelectedAddress> selectAddress(const MachineInstr &Instruction, + const MachineFunction &Function, + std::uint32_t OperandIndex) { + const MachineOperand *Root = Instruction.operandAt(OperandIndex); + const Register *RootRegister = Root == nullptr ? nullptr : Root->getIf<Register>(); + if (RootRegister == nullptr || !RootRegister->isVirtual()) + return std::nullopt; + + const MachineRegisterInfo &Registers = Function.registerInfo(); + const MachineInstr *RootDefinition = Registers.getVRegDef(*RootRegister); + if (RootDefinition != nullptr && + RootDefinition->opcode() == opcode(GenericOpcode::G_FRAME_INDEX)) { + const MachineOperand *FrameIndex = RootDefinition->operandAt(1); + if (FrameIndex == nullptr) + return std::nullopt; + return SelectedAddress{.Base = FrameIndex->cloneDetached(), .Offset = 0}; + } + + if (RootDefinition != nullptr && RootDefinition->opcode() == opcode(GenericOpcode::G_PTR_ADD)) { + const MachineOperand *Base = RootDefinition->operandAt(1); + const MachineOperand *Offset = RootDefinition->operandAt(2); + const Register *BaseRegister = Base == nullptr ? nullptr : Base->getIf<Register>(); + const Register *OffsetRegister = Offset == nullptr ? nullptr : Offset->getIf<Register>(); + if (BaseRegister != nullptr && OffsetRegister != nullptr) { + const std::optional<std::int64_t> Constant = + directIntegerConstant(Registers, *OffsetRegister); + if (Constant && *Constant >= -2048 && *Constant <= 2047) { + const MachineInstr *BaseDefinition = + BaseRegister->isVirtual() ? Registers.getVRegDef(*BaseRegister) : nullptr; + if (BaseDefinition != nullptr && + BaseDefinition->opcode() == opcode(GenericOpcode::G_FRAME_INDEX)) { + const MachineOperand *FrameIndex = BaseDefinition->operandAt(1); + if (FrameIndex == nullptr) + return std::nullopt; + return SelectedAddress{.Base = FrameIndex->cloneDetached(), .Offset = *Constant}; + } + return SelectedAddress{.Base = Base->cloneDetached(), .Offset = *Constant}; + } + } + } + + return SelectedAddress{.Base = Root->cloneDetached(), .Offset = 0}; +} + +struct SelectedIntegerCondition final { + Register Left; + Register Right; + BranchConditionCode Code = BranchConditionCode::NotEqual; +}; + +[[nodiscard]] Expected<std::optional<SelectedIntegerCondition>> +selectIntegerCondition(const MachineInstr &Instruction, const MachineFunction &Function, + std::uint32_t OperandIndex) { + const MachineOperand *ConditionOperand = Instruction.operandAt(OperandIndex); + const Register *Condition = + ConditionOperand == nullptr ? nullptr : ConditionOperand->getIf<Register>(); + if (Condition == nullptr) + return std::optional<SelectedIntegerCondition>{}; + + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + SelectedIntegerCondition Result{ + .Left = *Condition, + .Right = Register::fromPhysical(*Zero), + .Code = BranchConditionCode::NotEqual, + }; + const MachineInstr *Comparison = + Condition->isVirtual() ? Function.registerInfo().getVRegDef(*Condition) : nullptr; + if (Comparison == nullptr || Comparison->opcode() != opcode(GenericOpcode::G_ICMP)) + return std::optional<SelectedIntegerCondition>{Result}; + + const MachineOperand *PredicateMO = Comparison->operandAt(1); + const auto *Predicate = PredicateMO == nullptr ? nullptr : PredicateMO->getIf<PredicateOperand>(); + const IntegerPredicate *Integer = Predicate == nullptr ? nullptr : Predicate->integerPredicate(); + const MachineOperand *LeftOperand = Comparison->operandAt(2); + const MachineOperand *RightOperand = Comparison->operandAt(3); + const Register *Left = LeftOperand == nullptr ? nullptr : LeftOperand->getIf<Register>(); + const Register *Right = RightOperand == nullptr ? nullptr : RightOperand->getIf<Register>(); + if (Integer == nullptr || Left == nullptr || Right == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V integer comparison has malformed branch operands"); + } + Result.Left = *Left; + Result.Right = *Right; + + if (const std::optional<std::int64_t> Constant = + directIntegerConstant(Function.registerInfo(), *Right)) { + if (*Integer == IntegerPredicate::SignedGreaterThan && *Constant == -1) { + Result.Right = Register::fromPhysical(*Zero); + Result.Code = BranchConditionCode::SignedGreaterEqual; + return std::optional<SelectedIntegerCondition>{Result}; + } + if (*Integer == IntegerPredicate::SignedLessThan && *Constant == 1) { + Result.Right = Result.Left; + Result.Left = Register::fromPhysical(*Zero); + Result.Code = BranchConditionCode::SignedGreaterEqual; + return std::optional<SelectedIntegerCondition>{Result}; + } + } + + switch (*Integer) { + case IntegerPredicate::Equal: + Result.Code = BranchConditionCode::Equal; + break; + case IntegerPredicate::NotEqual: + Result.Code = BranchConditionCode::NotEqual; + break; + case IntegerPredicate::SignedLessThan: + Result.Code = BranchConditionCode::SignedLessThan; + break; + case IntegerPredicate::SignedGreaterEqual: + Result.Code = BranchConditionCode::SignedGreaterEqual; + break; + case IntegerPredicate::UnsignedLessThan: + Result.Code = BranchConditionCode::UnsignedLessThan; + break; + case IntegerPredicate::UnsignedGreaterEqual: + Result.Code = BranchConditionCode::UnsignedGreaterEqual; + break; + case IntegerPredicate::SignedGreaterThan: + std::swap(Result.Left, Result.Right); + Result.Code = BranchConditionCode::SignedLessThan; + break; + case IntegerPredicate::SignedLessEqual: + std::swap(Result.Left, Result.Right); + Result.Code = BranchConditionCode::SignedGreaterEqual; + break; + case IntegerPredicate::UnsignedGreaterThan: + std::swap(Result.Left, Result.Right); + Result.Code = BranchConditionCode::UnsignedLessThan; + break; + case IntegerPredicate::UnsignedLessEqual: + std::swap(Result.Left, Result.Right); + Result.Code = BranchConditionCode::UnsignedGreaterEqual; + break; + case IntegerPredicate::Count: + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V select has an invalid integer predicate"); + } + return std::optional<SelectedIntegerCondition>{Result}; +} + +class RISCVInstructionSelector final : public InstructionSelector { +public: + RISCVInstructionSelector(const RISCVSubtarget &Subtarget, + const RISCVRegisterBankInfo &RegisterBanks) + : Subtarget(Subtarget), RegisterBanks(RegisterBanks) {} + + [[nodiscard]] Expected<bool> matchesTargetPredicate(std::string_view Name, const MachineInstr &, + const MachineFunction &, + const TargetDescription &) const override { + if (Name == "a") + return Subtarget.features().has(RISCVExtension::A); + if (Name == "zba") + return Subtarget.features().has(RISCVExtension::Zba); + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V generated GlobalISel rule uses unknown target predicate '" + + std::string(Name) + "'"); + } + + [[nodiscard]] Expected<void> + constrainSelectedInstruction(MachineInstr &Instruction, MachineFunction &Function, + const TargetDescription &Target) const override { + return RegisterBanks.constrainSelectedInstruction(Instruction, Function, Target); + } + + [[nodiscard]] Expected<std::optional<GIMatchComplexOperand>> + renderComplexOperand(std::string_view Name, const MachineInstr &Instruction, + const MachineFunction &Function, const TargetDescription &Target, + std::uint32_t OperandIndex) const override { + if (Name == "addr_reg_imm_base" || Name == "addr_reg_imm_offset") { + std::optional<SelectedAddress> Address = selectAddress(Instruction, Function, OperandIndex); + if (!Address) + return std::optional<GIMatchComplexOperand>{}; + if (Name == "addr_reg_imm_offset") { + return GIMatchComplexOperand{ + .Operand = MachineOperand::immediate(Address->Offset), + .Instruction = std::nullopt, + }; + } + return GIMatchComplexOperand{ + .Operand = std::move(Address->Base), + .Instruction = std::nullopt, + }; + } + if (Name == "select_condition_lhs" || Name == "select_condition_rhs" || + Name == "select_condition_code") { + auto Condition = selectIntegerCondition(Instruction, Function, OperandIndex); + if (!Condition) + return std::unexpected(Condition.error()); + if (!*Condition) + return std::optional<GIMatchComplexOperand>{}; + if (Name == "select_condition_code") { + return GIMatchComplexOperand{ + .Operand = MachineOperand::immediate(static_cast<std::int64_t>((*Condition)->Code)), + .Instruction = std::nullopt, + }; + } + const Register Value = + Name == "select_condition_lhs" ? (*Condition)->Left : (*Condition)->Right; + auto Operand = MachineOperand::registerOperand(Value, {RegisterOperandFlag::Use}); + if (!Operand) + return std::unexpected(Operand.error()); + return GIMatchComplexOperand{ + .Operand = std::move(*Operand), + .Instruction = std::nullopt, + }; + } + if (Name == "fcopysign_to_s32" || Name == "fcopysign_to_s64") { + const MachineOperand *SourceOperand = Instruction.operandAt(OperandIndex); + const Register *Source = + SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Source == nullptr || !Source->isVirtual()) + return std::optional<GIMatchComplexOperand>{}; + const std::uint32_t DestinationWidth = Name == "fcopysign_to_s32" ? 32U : 64U; + auto DestinationType = LowLevelType::makeCategoryNeutralScalar(DestinationWidth); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + auto SourceUse = MachineOperand::registerOperand(*Source, {RegisterOperandFlag::Use}); + if (!SourceUse) + return std::unexpected(SourceUse.error()); + std::vector<MachineOperand> Operands; + Operands.push_back(std::move(*SourceUse)); + Operands.push_back(MachineOperand::immediate(DestinationWidth == 32 ? 7 : 0)); + return GIMatchComplexOperand{ + .Operand = std::nullopt, + .Instruction = + GIMatchComplexInstruction{ + .ResultOpcode = + instructionOpcode(DestinationWidth == 32 ? InstructionOpcode::FCVT_S_D + : InstructionOpcode::FCVT_D_S), + .ResultType = *DestinationType, + .Operands = std::move(Operands), + }, + }; + } + const std::uint64_t ShiftWidth = Name == "shift_mask_xlen" ? 64U + : Name == "shift_mask_32" ? 32U + : 0U; + if (ShiftWidth == 0) + return std::optional<GIMatchComplexOperand>{}; + const MachineOperand *Root = Instruction.operandAt(OperandIndex); + const Register *RootRegister = Root == nullptr ? nullptr : Root->getIf<Register>(); + if (RootRegister == nullptr || !RootRegister->isVirtual()) + return std::optional<GIMatchComplexOperand>{}; + + Register ShiftRegister = *RootRegister; + const auto Definition = [&Function](Register Reg) -> const MachineInstr * { + return Reg.isVirtual() ? Function.registerInfo().getVRegDef(Reg) : nullptr; + }; + const auto UseOperand = [](Register Reg) -> Expected<MachineOperand> { + return MachineOperand::registerOperand(Reg, {RegisterOperandFlag::Use}); + }; + const auto DirectResult = [&UseOperand](Register Reg) -> Expected<GIMatchComplexOperand> { + auto Operand = UseOperand(Reg); + if (!Operand) + return std::unexpected(Operand.error()); + return GIMatchComplexOperand{ + .Operand = std::move(*Operand), + .Instruction = std::nullopt, + }; + }; + + if (const MachineInstr *Extension = Definition(ShiftRegister); + Extension != nullptr && Extension->opcode() == opcode(GenericOpcode::G_ZEXT)) { + const MachineOperand *SourceOperand = Extension->operandAt(1); + const Register *Source = + SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Source != nullptr) + ShiftRegister = *Source; + } + + const std::uint64_t ShiftMask = ShiftWidth - 1U; + if (const MachineInstr *And = Definition(ShiftRegister); + And != nullptr && And->opcode() == opcode(GenericOpcode::G_AND)) { + const MachineOperand *SourceOperand = And->operandAt(1); + const MachineOperand *MaskOperand = And->operandAt(2); + const Register *Source = + SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + const Register *Mask = MaskOperand == nullptr ? nullptr : MaskOperand->getIf<Register>(); + const auto MaskValue = + Mask == nullptr ? std::nullopt : directIntegerConstant(Function.registerInfo(), *Mask); + if (Source != nullptr && MaskValue) { + const std::uint64_t RawMask = std::bit_cast<std::uint64_t>(*MaskValue); + bool ElideMask = (RawMask & ShiftMask) == ShiftMask; + if (!ElideMask) { + GISelValueTracking ValueTracking(Function, Target); + auto KnownZeroes = ValueTracking.getKnownZeroes(*Source); + if (!KnownZeroes) + return std::unexpected(KnownZeroes.error()); + ElideMask = ((RawMask | KnownZeroes->word(0)) & ShiftMask) == ShiftMask; + } + if (ElideMask) + ShiftRegister = *Source; + } + } + + const MachineInstr *ShiftDefinition = Definition(ShiftRegister); + if (ShiftDefinition == nullptr) + return DirectResult(ShiftRegister); + const MachineOperand *LeftOperand = ShiftDefinition->operandAt(1); + const MachineOperand *RightOperand = ShiftDefinition->operandAt(2); + const Register *Left = LeftOperand == nullptr ? nullptr : LeftOperand->getIf<Register>(); + const Register *Right = RightOperand == nullptr ? nullptr : RightOperand->getIf<Register>(); + + if (ShiftDefinition->opcode() == opcode(GenericOpcode::G_ADD) && Left != nullptr && + Right != nullptr) { + const auto Immediate = directIntegerConstant(Function.registerInfo(), *Right); + if (Immediate != std::nullopt && *Immediate != 0 && + std::bit_cast<std::uint64_t>(*Immediate) % ShiftWidth == 0) + return DirectResult(*Left); + } + + if (ShiftDefinition->opcode() != opcode(GenericOpcode::G_SUB) || Left == nullptr || + Right == nullptr) + return DirectResult(ShiftRegister); + const auto Immediate = directIntegerConstant(Function.registerInfo(), *Left); + if (!Immediate || *Immediate == 0) + return DirectResult(ShiftRegister); + + const std::uint64_t Remainder = std::bit_cast<std::uint64_t>(*Immediate) % ShiftWidth; + if (Remainder != 0 && Remainder != ShiftMask) + return DirectResult(ShiftRegister); + auto Type = Function.registerInfo().getType(*Right); + if (!Type) + return std::unexpected(Type.error()); + std::vector<MachineOperand> Operands; + if (Remainder == 0) { + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + auto ZeroOperand = UseOperand(Register::fromPhysical(*Zero)); + auto SourceOperand = UseOperand(*Right); + if (!ZeroOperand) + return std::unexpected(ZeroOperand.error()); + if (!SourceOperand) + return std::unexpected(SourceOperand.error()); + Operands.push_back(std::move(*ZeroOperand)); + Operands.push_back(std::move(*SourceOperand)); + return GIMatchComplexOperand{ + .Operand = std::nullopt, + .Instruction = + GIMatchComplexInstruction{ + .ResultOpcode = instructionOpcode(InstructionOpcode::SUBW), + .ResultType = *Type, + .Operands = std::move(Operands), + }, + }; + } + + auto SourceOperand = UseOperand(*Right); + if (!SourceOperand) + return std::unexpected(SourceOperand.error()); + Operands.push_back(std::move(*SourceOperand)); + Operands.push_back(MachineOperand::immediate(-1)); + return GIMatchComplexOperand{ + .Operand = std::nullopt, + .Instruction = + GIMatchComplexInstruction{ + .ResultOpcode = instructionOpcode(InstructionOpcode::XORI), + .ResultType = *Type, + .Operands = std::move(Operands), + }, + }; + } + + [[nodiscard]] Expected<void> select(MachineInstr &Instruction, MachineFunction &Function, + const TargetDescription &Target) const override { + auto Generated = GIMatchTableExecutor::select(generatedGlobalISelMatchTable(), Instruction, + Function, Target); + if (!Generated) + return std::unexpected(Generated.error()); + if (*Generated) + return {}; + if (Instruction.opcode() == opcode(GenericOpcode::COPY)) + return selectCopy(Instruction, Function, Target); + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + const std::optional<LowLevelType> Type = resultType(Function, Instruction); + + switch (Generic) { + case GenericOpcode::G_CONSTANT: + return selectIntegerConstant(Instruction, Function, Target, Type); + case GenericOpcode::G_FCONSTANT: + return selectFloatingConstant(Instruction, Function, Target, Type); + case GenericOpcode::G_ASSERT_SEXT: + case GenericOpcode::G_ASSERT_ZEXT: + case GenericOpcode::G_ASSERT_ALIGN: + if (Instruction.operandCount() == 3) { + auto EraseImmediate = Instruction.eraseOperand(2); + if (!EraseImmediate) + return std::unexpected(EraseImmediate.error()); + } + return Instruction.setOpcode(opcode(GenericOpcode::COPY)); + case GenericOpcode::G_FREEZE: + case GenericOpcode::G_CONSTANT_FOLD_BARRIER: + case GenericOpcode::G_PTRTOINT: + case GenericOpcode::G_INTTOPTR: + case GenericOpcode::G_ANYEXT: + case GenericOpcode::G_TRUNC: + return Instruction.setOpcode(opcode(GenericOpcode::COPY)); + case GenericOpcode::G_FCMP: + return selectFloatingCompare(Instruction, Function, Target); + case GenericOpcode::G_FENCE: + return selectFence(Instruction); + case GenericOpcode::G_GLOBAL_VALUE: + case GenericOpcode::G_JUMP_TABLE: + case GenericOpcode::G_CONSTANT_POOL: + return selectGlobalAddress(Instruction, Function, Target); + case GenericOpcode::G_PHI: + return Instruction.setOpcode(opcode(GenericOpcode::PHI)); + case GenericOpcode::COPY: + case GenericOpcode::IMPLICIT_DEF: + case GenericOpcode::PHI: + case GenericOpcode::REG_SEQUENCE: + case GenericOpcode::INSERT_SUBREG: + case GenericOpcode::EXTRACT_SUBREG: + case GenericOpcode::SUBREG_TO_REG: + case GenericOpcode::KILL: + case GenericOpcode::BUNDLE: + case GenericOpcode::INLINEASM: + case GenericOpcode::CORE_PSEUDO_END: + break; + default: + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V instruction selector has no rule for generic opcode"); + } + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V instruction selector has no rule for generic opcode"); + } + +private: + [[nodiscard]] Expected<const MachineMemOperand *> createGOTLoad(MachineFunction &Function, + LowLevelType Type) const { + auto Size = MemorySize::fixed(Type.minimumBitWidth() / 8U); + if (!Size) + return std::unexpected(Size.error()); + auto Alignment = rcc::Alignment::create(Type.minimumBitWidth() / 8U); + if (!Alignment) + return std::unexpected(Alignment.error()); + MachineMemOperandDesc Description; + Description.Accesses = {MemoryAccessFlag::Load, MemoryAccessFlag::Dereferenceable, + MemoryAccessFlag::Invariant}; + Description.Size = *Size; + Description.MemoryType = Type; + Description.Align = *Alignment; + Description.Base = MemoryBase::got(); + return Function.createMemOperand(std::move(Description)); + } + + [[nodiscard]] Expected<void> selectGlobalAddress(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target) const { + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *SymbolOperand = Instruction.operandAt(1); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const std::optional<LowLevelType> Type = resultType(Function, Instruction); + if (Destination == nullptr || SymbolOperand == nullptr || !Type || !Type->isPointer()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V address selection has malformed generic operands"); + } + auto DestinationConstraint = constrainBuilderRegisters(Function, Target, {*Destination}); + if (!DestinationConstraint) + return std::unexpected(DestinationConstraint.error()); + + GlobalValueProperties Properties; + if (Instruction.opcode() == opcode(GenericOpcode::G_GLOBAL_VALUE)) { + const auto *Global = SymbolOperand->getIf<GlobalSymbolOperand>(); + if (Global == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V global address has no global symbol operand"); + Properties = Global->Properties; + if (Properties.ThreadLocal) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V GlobalISel does not support thread-local global addresses"); + } + } + + const bool TaggedGlobals = Subtarget.features().has(RISCVSubtargetOption::TaggedGlobals); + const bool UseGOT = + Subtarget.relocationModel() == RelocationModel::PositionIndependent || TaggedGlobals; + const auto ReplaceWithPseudo = [&](InstructionOpcode OpcodeValue) -> Expected<void> { + auto SetOpcode = Instruction.setOpcode(instructionOpcode(OpcodeValue)); + if (!SetOpcode) + return std::unexpected(SetOpcode.error()); + return constrainSelectedInstruction(Instruction, Function, Target); + }; + const auto BuildGOTLoad = [&]() -> Expected<void> { + auto Memory = createGOTLoad(Function, *Type); + if (!Memory) + return std::unexpected(Memory.error()); + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + auto Pending = Builder.buildInstr(instructionOpcode(InstructionOpcode::PseudoLGA)); + if (!Pending) + return std::unexpected(Pending.error()); + auto Def = Pending->addDef(*Destination); + if (!Def) + return std::unexpected(Def.error()); + auto Symbol = Pending->addOperand(SymbolOperand->cloneDetached()); + if (!Symbol) + return std::unexpected(Symbol.error()); + auto MemoryAdded = Pending->addMemOperand(**Memory); + if (!MemoryAdded) + return std::unexpected(MemoryAdded.error()); + auto Result = Pending->build(); + if (!Result) + return std::unexpected(Result.error()); + auto Flags = propagateSelectionFlags(**Result, Instruction, Target); + if (!Flags) + return std::unexpected(Flags.error()); + auto Constraint = constrainSelectedInstruction(**Result, Function, Target); + if (!Constraint) + return std::unexpected(Constraint.error()); + return Instruction.eraseFromParent(); + }; + + if (UseGOT) { + if (Properties.DSOLocal && !TaggedGlobals) + return ReplaceWithPseudo(InstructionOpcode::PseudoLLA); + return BuildGOTLoad(); + } + + if (Subtarget.codeModel() == CodeModel::Medium) { + if (Properties.ExternalWeak) + return BuildGOTLoad(); + return ReplaceWithPseudo(InstructionOpcode::PseudoLLA); + } + if (Subtarget.codeModel() != CodeModel::Small) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V GlobalISel does not support this global address code model"); + } + + auto Temporary = createTemporary(Function, *Type, Target); + if (!Temporary) + return std::unexpected(Temporary.error()); + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + auto HighPending = Builder.buildInstr(instructionOpcode(InstructionOpcode::LUI)); + if (!HighPending) + return std::unexpected(HighPending.error()); + auto HighDef = HighPending->addDef(*Temporary); + if (!HighDef) + return std::unexpected(HighDef.error()); + MachineOperand HighSymbol = SymbolOperand->cloneDetached(); + HighSymbol.setTargetFlags(AbsoluteHighFlag); + auto HighOperand = HighPending->addOperand(std::move(HighSymbol)); + if (!HighOperand) + return std::unexpected(HighOperand.error()); + auto High = HighPending->build(); + if (!High) + return std::unexpected(High.error()); + auto HighFlags = propagateSelectionFlags(**High, Instruction, Target); + if (!HighFlags) + return std::unexpected(HighFlags.error()); + auto HighConstraint = constrainSelectedInstruction(**High, Function, Target); + if (!HighConstraint) + return std::unexpected(HighConstraint.error()); + + auto LowPending = Builder.buildInstr(instructionOpcode(InstructionOpcode::ADDI)); + if (!LowPending) + return std::unexpected(LowPending.error()); + auto LowDef = LowPending->addDef(*Destination); + if (!LowDef) + return std::unexpected(LowDef.error()); + auto LowSource = LowPending->addUse(*Temporary); + if (!LowSource) + return std::unexpected(LowSource.error()); + MachineOperand LowSymbol = SymbolOperand->cloneDetached(); + LowSymbol.setTargetFlags(AbsoluteLowFlag); + auto LowOperand = LowPending->addOperand(std::move(LowSymbol)); + if (!LowOperand) + return std::unexpected(LowOperand.error()); + auto Low = LowPending->build(); + if (!Low) + return std::unexpected(Low.error()); + auto LowFlags = propagateSelectionFlags(**Low, Instruction, Target); + if (!LowFlags) + return std::unexpected(LowFlags.error()); + auto LowConstraint = constrainSelectedInstruction(**Low, Function, Target); + if (!LowConstraint) + return std::unexpected(LowConstraint.error()); + return Instruction.eraseFromParent(); + } + + [[nodiscard]] Expected<void> selectCopy(MachineInstr &Instruction, MachineFunction &Function, + const TargetDescription &Target) const { + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V COPY selection requires a register destination"); + if (!Destination->isVirtual()) + return {}; + return RegisterBanks.constrainRegister(Function, *Destination, Target); + } + + [[nodiscard]] Expected<Register> + createTemporary(MachineFunction &Function, LowLevelType Type, const TargetDescription &Target, + std::optional<RegisterBankID> Bank = std::nullopt) const { + auto Temporary = Function.registerInfo().createVirtualRegister(Type); + if (!Temporary) + return std::unexpected(Temporary.error()); + if (Bank) { + auto SetBank = Function.registerInfo().setRegisterBank(*Temporary, *Bank); + if (!SetBank) + return std::unexpected(SetBank.error()); + } + auto Constraint = RegisterBanks.constrainRegister(Function, *Temporary, Target); + if (!Constraint) + return std::unexpected(Constraint.error()); + return *Temporary; + } + + [[nodiscard]] Expected<void> + constrainBuilderRegisters(MachineFunction &Function, const TargetDescription &Target, + std::initializer_list<Register> Registers) const { + for (Register Reg : Registers) { + if (!Reg.isVirtual()) + continue; + auto Constraint = RegisterBanks.constrainRegister(Function, Reg, Target); + if (!Constraint) + return std::unexpected(Constraint.error()); + } + return {}; + } + + [[nodiscard]] Expected<void> replaceImmediate(MachineInstr &Instruction, std::size_t Index, + std::int64_t Value) const { + if (Instruction.operandAt(Index) == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V instruction is missing an immediate operand"); + auto Erased = Instruction.eraseOperand(Index); + if (!Erased) + return std::unexpected(Erased.error()); + return Instruction.insertOperand(Index, MachineOperand::immediate(Value)); + } + + [[nodiscard]] Expected<MachineInstr *> + buildImmediateAfter(MachineIRBuilder &Builder, MachineInstr &Anchor, InstructionOpcode Opcode, + Register Def, Register Source, std::int64_t Value) const { + auto Point = Builder.setInsertPointAfter(Anchor); + if (!Point) + return std::unexpected(Point.error()); + auto RegisterConstraints = + constrainBuilderRegisters(Builder.function(), Builder.target(), {Def, Source}); + if (!RegisterConstraints) + return std::unexpected(RegisterConstraints.error()); + auto Pending = Builder.buildInstr(instructionOpcode(Opcode)); + if (!Pending) + return std::unexpected(Pending.error()); + auto DefResult = Pending->addDef(Def); + if (!DefResult) + return std::unexpected(DefResult.error()); + auto SourceResult = Pending->addUse(Source); + if (!SourceResult) + return std::unexpected(SourceResult.error()); + auto ImmediateResult = Pending->addImmediate(Value); + if (!ImmediateResult) + return std::unexpected(ImmediateResult.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + auto FlagPropagation = propagateSelectionFlags(**Built, Anchor, Builder.target()); + if (!FlagPropagation) + return std::unexpected(FlagPropagation.error()); + auto Constraint = constrainSelectedInstruction(**Built, Builder.function(), Builder.target()); + if (!Constraint) + return std::unexpected(Constraint.error()); + return *Built; + } + + [[nodiscard]] Expected<MachineInstr *> + buildImmediateOnlyAfter(MachineIRBuilder &Builder, MachineInstr &Anchor, InstructionOpcode Opcode, + Register Def, std::int64_t Value) const { + auto Point = Builder.setInsertPointAfter(Anchor); + if (!Point) + return std::unexpected(Point.error()); + auto RegisterConstraints = + constrainBuilderRegisters(Builder.function(), Builder.target(), {Def}); + if (!RegisterConstraints) + return std::unexpected(RegisterConstraints.error()); + auto Pending = Builder.buildInstr(instructionOpcode(Opcode)); + if (!Pending) + return std::unexpected(Pending.error()); + auto DefResult = Pending->addDef(Def); + if (!DefResult) + return std::unexpected(DefResult.error()); + auto ImmediateResult = Pending->addImmediate(Value); + if (!ImmediateResult) + return std::unexpected(ImmediateResult.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + auto FlagPropagation = propagateSelectionFlags(**Built, Anchor, Builder.target()); + if (!FlagPropagation) + return std::unexpected(FlagPropagation.error()); + auto Constraint = constrainSelectedInstruction(**Built, Builder.function(), Builder.target()); + if (!Constraint) + return std::unexpected(Constraint.error()); + return *Built; + } + + [[nodiscard]] Expected<MachineInstr *> + buildImmediateBefore(MachineIRBuilder &Builder, MachineInstr &Anchor, InstructionOpcode Opcode, + Register Def, Register Source, std::int64_t Value) const { + auto Point = Builder.setInsertPointBefore(Anchor); + if (!Point) + return std::unexpected(Point.error()); + auto RegisterConstraints = + constrainBuilderRegisters(Builder.function(), Builder.target(), {Def, Source}); + if (!RegisterConstraints) + return std::unexpected(RegisterConstraints.error()); + auto Pending = Builder.buildInstr(instructionOpcode(Opcode)); + if (!Pending) + return std::unexpected(Pending.error()); + auto DefResult = Pending->addDef(Def); + if (!DefResult) + return std::unexpected(DefResult.error()); + auto SourceResult = Pending->addUse(Source); + if (!SourceResult) + return std::unexpected(SourceResult.error()); + auto ImmediateResult = Pending->addImmediate(Value); + if (!ImmediateResult) + return std::unexpected(ImmediateResult.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + auto FlagPropagation = propagateSelectionFlags(**Built, Anchor, Builder.target()); + if (!FlagPropagation) + return std::unexpected(FlagPropagation.error()); + auto Constraint = constrainSelectedInstruction(**Built, Builder.function(), Builder.target()); + if (!Constraint) + return std::unexpected(Constraint.error()); + return *Built; + } + + [[nodiscard]] Expected<void> selectFloatingCompare(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target) const { + const MachineOperand *PredicateMO = Instruction.operandAt(1); + const auto *Predicate = + PredicateMO == nullptr ? nullptr : PredicateMO->getIf<PredicateOperand>(); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *LeftOperand = Instruction.operandAt(2); + const MachineOperand *RightOperand = Instruction.operandAt(3); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Left = LeftOperand == nullptr ? nullptr : LeftOperand->getIf<Register>(); + const Register *Right = RightOperand == nullptr ? nullptr : RightOperand->getIf<Register>(); + const std::optional<LowLevelType> FloatType = operandType(Function, Instruction, 2); + const std::optional<LowLevelType> ResultType = resultType(Function, Instruction); + if (Predicate == nullptr || Predicate->floatingPredicate() == nullptr || + Destination == nullptr || Left == nullptr || Right == nullptr || !FloatType || !ResultType) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V floating comparison has invalid operands"); + FloatingPredicate Condition = *Predicate->floatingPredicate(); + const Register DestinationRegister = *Destination; + Register LeftRegister = *Left; + Register RightRegister = *Right; + MachineIRBuilder Builder(Function, Target); + + const auto BuildBinary = [&](InstructionOpcode OpcodeValue, Register Def, Register LeftValue, + Register RightValue) -> Expected<MachineInstr *> { + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + auto Registers = constrainBuilderRegisters(Function, Target, {Def, LeftValue, RightValue}); + if (!Registers) + return std::unexpected(Registers.error()); + auto Pending = Builder.buildInstr(instructionOpcode(OpcodeValue)); + if (!Pending) + return std::unexpected(Pending.error()); + auto DefResult = Pending->addDef(Def); + if (!DefResult) + return std::unexpected(DefResult.error()); + auto LeftResult = Pending->addUse(LeftValue); + if (!LeftResult) + return std::unexpected(LeftResult.error()); + auto RightResult = Pending->addUse(RightValue); + if (!RightResult) + return std::unexpected(RightResult.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + auto Constraint = constrainSelectedInstruction(**Built, Function, Target); + if (!Constraint) + return std::unexpected(Constraint.error()); + return *Built; + }; + const auto BuildImmediate = [&](InstructionOpcode OpcodeValue, Register Def, Register Source, + std::int64_t Immediate) -> Expected<MachineInstr *> { + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + auto Registers = constrainBuilderRegisters(Function, Target, {Def, Source}); + if (!Registers) + return std::unexpected(Registers.error()); + auto Pending = Builder.buildInstr(instructionOpcode(OpcodeValue)); + if (!Pending) + return std::unexpected(Pending.error()); + auto DefResult = Pending->addDef(Def); + if (!DefResult) + return std::unexpected(DefResult.error()); + auto SourceResult = Pending->addUse(Source); + if (!SourceResult) + return std::unexpected(SourceResult.error()); + auto ImmediateResult = Pending->addImmediate(Immediate); + if (!ImmediateResult) + return std::unexpected(ImmediateResult.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + auto Constraint = constrainSelectedInstruction(**Built, Function, Target); + if (!Constraint) + return std::unexpected(Constraint.error()); + return *Built; + }; + + Register TemporaryResult = DestinationRegister; + bool NeedsInvert = false; + if (legalizeFloatingPredicate(LeftRegister, RightRegister, Condition, NeedsInvert)) { + if (NeedsInvert) { + auto Temporary = createTemporary(Function, *ResultType, Target); + if (!Temporary) + return std::unexpected(Temporary.error()); + TemporaryResult = *Temporary; + } + auto OpcodeValue = floatingCompareOpcode(Condition, FloatType->scalarBitWidth()); + if (!OpcodeValue) + return std::unexpected(OpcodeValue.error()); + auto Comparison = BuildBinary(*OpcodeValue, TemporaryResult, LeftRegister, RightRegister); + if (!Comparison) + return std::unexpected(Comparison.error()); + } else if (Condition == FloatingPredicate::OrderedNotEqual || + Condition == FloatingPredicate::UnorderedEqual) { + NeedsInvert = Condition == FloatingPredicate::UnorderedEqual; + auto First = createTemporary(Function, *ResultType, Target); + auto Second = createTemporary(Function, *ResultType, Target); + if (!First || !Second) + return std::unexpected((!First ? First.error() : Second.error())); + auto LessOpcode = + floatingCompareOpcode(FloatingPredicate::OrderedLessThan, FloatType->scalarBitWidth()); + if (!LessOpcode) + return std::unexpected(LessOpcode.error()); + auto FirstComparison = BuildBinary(*LessOpcode, *First, LeftRegister, RightRegister); + if (!FirstComparison) + return std::unexpected(FirstComparison.error()); + auto SecondComparison = BuildBinary(*LessOpcode, *Second, RightRegister, LeftRegister); + if (!SecondComparison) + return std::unexpected(SecondComparison.error()); + if (NeedsInvert) { + auto Temporary = createTemporary(Function, *ResultType, Target); + if (!Temporary) + return std::unexpected(Temporary.error()); + TemporaryResult = *Temporary; + } + auto Combined = BuildBinary(InstructionOpcode::OR, TemporaryResult, *First, *Second); + if (!Combined) + return std::unexpected(Combined.error()); + } else if (Condition == FloatingPredicate::Ordered || + Condition == FloatingPredicate::Unordered) { + NeedsInvert = Condition == FloatingPredicate::Unordered; + auto EqualOpcode = + floatingCompareOpcode(FloatingPredicate::OrderedEqual, FloatType->scalarBitWidth()); + if (!EqualOpcode) + return std::unexpected(EqualOpcode.error()); + if (NeedsInvert) { + auto Temporary = createTemporary(Function, *ResultType, Target); + if (!Temporary) + return std::unexpected(Temporary.error()); + TemporaryResult = *Temporary; + } + if (LeftRegister == RightRegister) { + auto Comparison = BuildBinary(*EqualOpcode, TemporaryResult, LeftRegister, LeftRegister); + if (!Comparison) + return std::unexpected(Comparison.error()); + } else { + auto First = createTemporary(Function, *ResultType, Target); + auto Second = createTemporary(Function, *ResultType, Target); + if (!First || !Second) + return std::unexpected((!First ? First.error() : Second.error())); + auto FirstComparison = BuildBinary(*EqualOpcode, *First, LeftRegister, LeftRegister); + if (!FirstComparison) + return std::unexpected(FirstComparison.error()); + auto SecondComparison = BuildBinary(*EqualOpcode, *Second, RightRegister, RightRegister); + if (!SecondComparison) + return std::unexpected(SecondComparison.error()); + auto Combined = BuildBinary(InstructionOpcode::AND, TemporaryResult, *First, *Second); + if (!Combined) + return std::unexpected(Combined.error()); + } + } else { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V floating comparison predicate has no LLVM selection"); + } + + if (NeedsInvert) { + auto Inverted = + BuildImmediate(InstructionOpcode::XORI, DestinationRegister, TemporaryResult, 1); + if (!Inverted) + return std::unexpected(Inverted.error()); + } + return Instruction.eraseFromParent(); + } + + [[nodiscard]] Expected<void> selectFence(MachineInstr &Instruction) const { + const auto Ordering = signedImmediate(*Instruction.operandAt(0)); + if (!Ordering) + return std::unexpected(Ordering.error()); + std::int64_t Pred = 0; + std::int64_t Succ = 0; + switch (*Ordering) { + case 4: + Pred = 1; + Succ = 3; + break; + case 5: + Pred = 3; + Succ = 2; + break; + case 6: + case 7: + Pred = 3; + Succ = 3; + break; + default: + return makeUnexpected(ErrorCode::Unsupported, "RISC-V fence ordering is unsupported"); + } + auto SetPred = replaceImmediate(Instruction, 0, Pred); + if (!SetPred) + return std::unexpected(SetPred.error()); + auto SetSucc = replaceImmediate(Instruction, 1, Succ); + if (!SetSucc) + return std::unexpected(SetSucc.error()); + return Instruction.setOpcode(instructionOpcode(InstructionOpcode::FENCE)); + } + + [[nodiscard]] Expected<void> selectIntegerConstant(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target, + std::optional<LowLevelType> Type) const { + if (!Type || !isSupportedIntegerType(*Type)) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V constant selection requires an i1-i64 or pointer result"); + } + const MachineOperand *ImmediateOperand = Instruction.operandAt(1); + if (ImmediateOperand == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V generic constant has no immediate operand"); + auto ImmediateValue = signedImmediate(*ImmediateOperand); + if (!ImmediateValue) + return std::unexpected(ImmediateValue.error()); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *DestinationValue = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (DestinationValue == nullptr || !DestinationValue->isVirtual()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V generic constant must define a virtual register"); + } + const Register Destination = *DestinationValue; + auto DestinationConstraint = constrainBuilderRegisters(Function, Target, {Destination}); + if (!DestinationConstraint) + return std::unexpected(DestinationConstraint.error()); + const std::int64_t Value = *ImmediateValue; + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + if (Value == 0) { + auto EraseImmediate = Instruction.eraseOperand(1); + if (!EraseImmediate) + return std::unexpected(EraseImmediate.error()); + auto Source = insertPhysicalRegister(Instruction, 1, *Zero, RegisterOperandFlag::Use); + if (!Source) + return std::unexpected(Source.error()); + return Instruction.setOpcode(opcode(GenericOpcode::COPY)); + } + + ImmediateMaterializationSequence Sequence = + generateRV64IntegerMaterialization(Value, Subtarget.features()); + if (Sequence.empty()) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V integer materialization produced an empty sequence"); + } + Register Current = Destination; + if (Sequence.size() != 1) { + auto Temporary = createTemporary(Function, *Type, Target); + if (!Temporary) + return std::unexpected(Temporary.error()); + Current = *Temporary; + } + + const auto SelectHead = [&](const ImmediateMaterializationInstruction &Step, + Register Definition) -> Expected<void> { + auto SetOpcode = Instruction.setOpcode(instructionOpcode(Step.Opcode)); + if (!SetOpcode) + return std::unexpected(SetOpcode.error()); + auto SetDestination = Instruction.operandAt(0)->setRegister(Definition); + if (!SetDestination) + return std::unexpected(SetDestination.error()); + auto EraseImmediate = Instruction.eraseOperand(1); + if (!EraseImmediate) + return std::unexpected(EraseImmediate.error()); + if (Step.OperandKind == ImmediateMaterializationOperandKind::Immediate) + return Instruction.addOperand(MachineOperand::immediate(Step.Immediate)); + auto Source = insertPhysicalRegister(Instruction, 1, *Zero, RegisterOperandFlag::Use); + if (!Source) + return std::unexpected(Source.error()); + return Instruction.addOperand(MachineOperand::immediate(Step.Immediate)); + }; + auto Head = SelectHead(Sequence.front(), Current); + if (!Head) + return std::unexpected(Head.error()); + + MachineIRBuilder Builder(Function, Target); + MachineInstr *Anchor = &Instruction; + for (std::size_t Index = 1; Index < Sequence.size(); ++Index) { + const bool IsFinal = Index + 1U == Sequence.size(); + Register Next = Destination; + if (!IsFinal) { + auto Temporary = createTemporary(Function, *Type, Target); + if (!Temporary) + return std::unexpected(Temporary.error()); + Next = *Temporary; + } + const ImmediateMaterializationInstruction &Step = Sequence[Index]; + Expected<MachineInstr *> Built = + Step.OperandKind == ImmediateMaterializationOperandKind::Immediate + ? buildImmediateOnlyAfter(Builder, *Anchor, Step.Opcode, Next, Step.Immediate) + : buildImmediateAfter(Builder, *Anchor, Step.Opcode, Next, Current, Step.Immediate); + if (!Built) + return std::unexpected(Built.error()); + Anchor = *Built; + Current = Next; + } + return {}; + } + + [[nodiscard]] Expected<void> selectFloatingConstant(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target, + std::optional<LowLevelType> Type) const { + if (!Type || !isSupportedFloatingScalar(*Type)) + return makeUnexpected(ErrorCode::Unsupported, "RISC-V floating constant requires f32 or f64"); + const MachineOperand *Immediate = Instruction.operandAt(1); + const auto *Floating = + Immediate == nullptr ? nullptr : Immediate->getIf<FloatingImmediateOperand>(); + if (Floating == nullptr || Floating->Bits.bitWidth() != Type->scalarBitWidth()) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V floating constant has mismatched bit semantics"); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr || !Destination->isVirtual()) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V floating constant must define a virtual register"); + const Register DestinationRegister = *Destination; + + const std::uint32_t BitWidth = Type->scalarBitWidth(); + const std::uint64_t Bits = Floating->Bits.words().empty() ? 0 : Floating->Bits.words().front(); + const InstructionOpcode Move = + BitWidth == 32 ? InstructionOpcode::FMV_W_X : InstructionOpcode::FMV_D_X; + if (Bits == 0) { + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + auto EraseImmediate = Instruction.eraseOperand(1); + if (!EraseImmediate) + return std::unexpected(EraseImmediate.error()); + auto Source = insertPhysicalRegister(Instruction, 1, *Zero, RegisterOperandFlag::Use); + if (!Source) + return std::unexpected(Source.error()); + return Instruction.setOpcode(instructionOpcode(Move)); + } + + auto Integer = APInt::fromUnsigned(BitWidth, Bits); + if (!Integer) + return std::unexpected(Integer.error()); + auto IntegerType = LowLevelType::makeCategoryNeutralScalar(64); + if (!IntegerType) + return std::unexpected(IntegerType.error()); + auto Temporary = createTemporary(Function, *IntegerType, Target); + if (!Temporary) + return std::unexpected(Temporary.error()); + + auto SetOpcode = Instruction.setOpcode(opcode(GenericOpcode::G_CONSTANT)); + if (!SetOpcode) + return std::unexpected(SetOpcode.error()); + auto SetDestination = Instruction.operandAt(0)->setRegister(*Temporary); + if (!SetDestination) + return std::unexpected(SetDestination.error()); + auto EraseImmediate = Instruction.eraseOperand(1); + if (!EraseImmediate) + return std::unexpected(EraseImmediate.error()); + auto AddInteger = Instruction.addOperand(MachineOperand::integerImmediate(*Integer)); + if (!AddInteger) + return std::unexpected(AddInteger.error()); + auto IntegerSelection = selectIntegerConstant(Instruction, Function, Target, *IntegerType); + if (!IntegerSelection) + return std::unexpected(IntegerSelection.error()); + + MachineInstr *IntegerDefinition = nullptr; + for (MachineOperand &DefOperand : Function.registerInfo().defOperands(*Temporary)) { + IntegerDefinition = DefOperand.parent(); + break; + } + if (IntegerDefinition == nullptr) + return makeUnexpected(ErrorCode::InternalError, + "RISC-V floating constant lost its integer materialization"); + MachineIRBuilder Builder(Function, Target); + auto InsertPoint = Builder.setInsertPointAfter(*IntegerDefinition); + if (!InsertPoint) + return std::unexpected(InsertPoint.error()); + auto RegisterConstraints = + constrainBuilderRegisters(Function, Target, {DestinationRegister, *Temporary}); + if (!RegisterConstraints) + return std::unexpected(RegisterConstraints.error()); + auto MoveInstruction = Builder.buildInstr(instructionOpcode(Move)); + if (!MoveInstruction) + return std::unexpected(MoveInstruction.error()); + auto MoveDef = MoveInstruction->addDef(DestinationRegister); + if (!MoveDef) + return std::unexpected(MoveDef.error()); + auto MoveUse = MoveInstruction->addUse(*Temporary); + if (!MoveUse) + return std::unexpected(MoveUse.error()); + auto BuiltMove = MoveInstruction->build(); + if (!BuiltMove) + return std::unexpected(BuiltMove.error()); + auto Constraint = constrainSelectedInstruction(**BuiltMove, Function, Target); + if (!Constraint) + return std::unexpected(Constraint.error()); + return {}; + } + + const RISCVSubtarget &Subtarget; + const RISCVRegisterBankInfo &RegisterBanks; +}; + +} // namespace + +InstructionSelector *createRISCVInstructionSelector(const RISCVSubtarget &Subtarget, + const RISCVRegisterBankInfo &RegisterBanks) { + return new RISCVInstructionSelector(Subtarget, RegisterBanks); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp b/backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp new file mode 100644 index 0000000..1e10b16 --- /dev/null +++ b/backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp @@ -0,0 +1,1382 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCVLegalizerInfo.h" + +#include "RISCVImmediateMaterialization.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/GlobalISel/GISelChangeObserver.h" +#include "rcc/CodeGen/GlobalISel/LegalizerHelper.h" +#include "rcc/CodeGen/GlobalISel/Utils.h" +#include "rcc/CodeGen/LowLevelType.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFrameInfo.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/IR/DataLayout.h" +#include "rcc/Target/RISCV/RISCVFunctionInfo.h" +#include "rcc/Target/RISCV/RISCVSubtarget.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <algorithm> +#include <array> +#include <bit> +#include <cstdint> +#include <limits> +#include <optional> +#include <ranges> +#include <string> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] std::optional<LowLevelType> registerType(const MachineFunction &Function, + Register Reg) { + if (!Reg.isVirtual()) + return std::nullopt; + auto Info = Function.registerInfo().virtualRegisterInfo(Reg); + if (!Info) + return std::nullopt; + return Info->get().Type; +} + +[[nodiscard]] std::optional<LowLevelType> registerType(const MachineFunction &Function, + const MachineOperand &Operand) { + const Register *Reg = Operand.getIf<Register>(); + return Reg == nullptr ? std::nullopt : registerType(Function, *Reg); +} + +[[nodiscard]] std::optional<LowLevelType> resultType(const MachineFunction &Function, + const MachineInstr &Instruction) { + const MachineOperand *Result = Instruction.operandAt(0); + return Result == nullptr ? std::nullopt : registerType(Function, *Result); +} + +[[nodiscard]] Expected<Opcode> getRISCVWOpcode(GenericOpcode Generic) { + switch (Generic) { + case GenericOpcode::G_ASHR: + return instructionOpcode(InstructionOpcode::G_SRAW); + case GenericOpcode::G_LSHR: + return instructionOpcode(InstructionOpcode::G_SRLW); + case GenericOpcode::G_SHL: + return instructionOpcode(InstructionOpcode::G_SLLW); + case GenericOpcode::G_SDIV: + return instructionOpcode(InstructionOpcode::G_DIVW); + case GenericOpcode::G_UDIV: + return instructionOpcode(InstructionOpcode::G_DIVUW); + case GenericOpcode::G_UREM: + return instructionOpcode(InstructionOpcode::G_REMUW); + case GenericOpcode::G_ROTL: + return instructionOpcode(InstructionOpcode::G_ROLW); + case GenericOpcode::G_ROTR: + return instructionOpcode(InstructionOpcode::G_RORW); + case GenericOpcode::G_CTLZ: + return instructionOpcode(InstructionOpcode::G_CLZW); + case GenericOpcode::G_CTTZ: + return instructionOpcode(InstructionOpcode::G_CTZW); + case GenericOpcode::G_CTLS: + return instructionOpcode(InstructionOpcode::G_CLSW); + case GenericOpcode::G_FPTOSI: + return instructionOpcode(InstructionOpcode::G_FCVT_W_RV64); + case GenericOpcode::G_FPTOUI: + return instructionOpcode(InstructionOpcode::G_FCVT_WU_RV64); + default: + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V word opcode mapping received an unexpected opcode"); + } +} + +[[nodiscard]] Expected<APInt> +extendIntegerImmediate(const APInt &Value, std::uint32_t DestinationWidth, bool SignExtend) { + if (DestinationWidth == 0 || DestinationWidth > 64) + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V constant extension requires a destination no wider than 64 bits"); + + std::uint64_t Raw = Value.words().empty() ? 0 : Value.words().front(); + const std::uint32_t SourceWidth = Value.bitWidth(); + if (SignExtend && Value.isNegative() && SourceWidth < 64) + Raw |= ~std::uint64_t{0} << SourceWidth; + if (DestinationWidth < 64) + Raw &= (std::uint64_t{1} << DestinationWidth) - 1U; + return APInt::create(DestinationWidth, std::span<const std::uint64_t>(&Raw, 1)); +} + +[[nodiscard]] Expected<void> +foldConstantExtension(MachineInstr &Instruction, MachineFunction &Function, GenericOpcode Generic) { + const MachineOperand *SourceOperand = Instruction.operandAt(1); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Source == nullptr || Destination == nullptr || !Source->isVirtual() || + !Destination->isVirtual()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V constant extension requires virtual-register operands"); + } + const Register SourceRegister = *Source; + const Register DestinationRegister = *Destination; + const MachineOperand *SourceDefinition = Function.registerInfo().getUniqueVRegDef(SourceRegister); + const MachineInstr *SourceDef = + SourceDefinition == nullptr ? nullptr : SourceDefinition->parent(); + if (SourceDef == nullptr || SourceDef->opcode() != opcode(GenericOpcode::G_CONSTANT)) + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V constant extension requires a single constant definition"); + const MachineOperand *ConstantOperand = SourceDef->operandAt(1); + const auto *Integer = + ConstantOperand == nullptr ? nullptr : ConstantOperand->getIf<IntegerImmediateOperand>(); + if (Integer == nullptr) + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V constant extension requires an integer constant"); + const auto DestinationType = registerType(Function, *Destination); + if (!DestinationType) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V constant extension destination has no type"); + const std::uint32_t DestinationWidth = DestinationType->scalarBitWidth(); + const bool SignExtend = Generic != GenericOpcode::G_ZEXT; + auto Extended = extendIntegerImmediate(Integer->Value, DestinationWidth, SignExtend); + if (!Extended) + return std::unexpected(Extended.error()); + + if (Function.registerInfo().hasOneUse(SourceRegister)) { + MachineInstr *Def = const_cast<MachineInstr *>(SourceDef); + if (auto Replaced = Def->operandAt(0)->setRegister(DestinationRegister); !Replaced) + return std::unexpected(Replaced.error()); + auto Erased = Def->eraseOperand(1); + if (!Erased) + return std::unexpected(Erased.error()); + auto Added = Def->addOperand(MachineOperand::integerImmediate(std::move(*Extended))); + if (!Added) + return std::unexpected(Added.error()); + return Instruction.eraseFromParent(); + } + + auto SetOpcode = Instruction.setOpcode(opcode(GenericOpcode::G_CONSTANT)); + if (!SetOpcode) + return std::unexpected(SetOpcode.error()); + while (Instruction.operandCount() > 1) { + auto Erased = Instruction.eraseOperand(1); + if (!Erased) + return std::unexpected(Erased.error()); + } + auto Added = Instruction.addOperand(MachineOperand::integerImmediate(std::move(*Extended))); + if (!Added) + return std::unexpected(Added.error()); + + if (Function.registerInfo().useEmpty(SourceRegister)) { + MachineInstr *Def = const_cast<MachineInstr *>(SourceDef); + auto Removed = Def->eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + // Keep the source register record as a tombstone-free historical vreg until + // the legalizer worklist transaction has completed. + } + return {}; +} + +[[nodiscard]] Expected<void> lowerConstantToPool(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target) { + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const MachineOperand *ImmediateOperand = Instruction.operandAt(1); + const auto *Immediate = + ImmediateOperand == nullptr ? nullptr : ImmediateOperand->getIf<IntegerImmediateOperand>(); + if (Destination == nullptr || !Destination->isVirtual() || Immediate == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V constant-pool legalization has invalid operands"); + const auto DestinationType = registerType(Function, *Destination); + if (!DestinationType || DestinationType->scalarBitWidth() != 64) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V constant-pool legalization requires a 64-bit destination"); + auto Value = GenericConstantData::integer(Immediate->Value); + if (!Value) + return std::unexpected(Value.error()); + auto Align = Alignment::create(8); + if (!Align) + return std::unexpected(Align.error()); + MachineConstantPoolEntryDesc Description; + Description.Value = std::move(*Value); + Description.Size = 8; + Description.Align = *Align; + auto PoolIndex = Function.constantPool().addEntry(std::move(Description)); + if (!PoolIndex) + return std::unexpected(PoolIndex.error()); + + auto PointerType = LowLevelType::makePointer(0, 64); + if (!PointerType) + return std::unexpected(PointerType.error()); + auto Address = Function.registerInfo().createVirtualRegister(*PointerType); + if (!Address) + return std::unexpected(Address.error()); + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + auto AddressInstruction = Builder.buildInstr(opcode(GenericOpcode::G_CONSTANT_POOL)); + if (!AddressInstruction) + return std::unexpected(AddressInstruction.error()); + auto AddressDef = AddressInstruction->addDef(*Address); + if (!AddressDef) + return std::unexpected(AddressDef.error()); + auto AddressPool = AddressInstruction->addConstantPool(*PoolIndex); + if (!AddressPool) + return std::unexpected(AddressPool.error()); + auto BuiltAddress = AddressInstruction->build(); + if (!BuiltAddress) + return std::unexpected(BuiltAddress.error()); + + MachineMemOperandDesc MemoryDescription; + MemoryDescription.Accesses = {MemoryAccessFlag::Load}; + MemoryDescription.Size = *MemorySize::fixed(8); + MemoryDescription.MemoryType = *DestinationType; + MemoryDescription.Align = *Align; + MemoryDescription.Base = MemoryBase::constantPoolPseudo(); + auto Memory = Function.createMemOperand(std::move(MemoryDescription)); + if (!Memory) + return std::unexpected(Memory.error()); + auto SetOpcode = Instruction.setOpcode(opcode(GenericOpcode::G_LOAD)); + if (!SetOpcode) + return std::unexpected(SetOpcode.error()); + while (Instruction.operandCount() > 1) { + auto Erased = Instruction.eraseOperand(1); + if (!Erased) + return std::unexpected(Erased.error()); + } + auto AddressUse = + MachineOperand::registerOperand(*Address, RegisterOperandFlags{RegisterOperandFlag::Use}); + if (!AddressUse) + return std::unexpected(AddressUse.error()); + auto AddedAddress = Instruction.addOperand(std::move(*AddressUse)); + if (!AddedAddress) + return std::unexpected(AddedAddress.error()); + auto AddedMemory = Instruction.addMemOperand(**Memory); + if (!AddedMemory) + return std::unexpected(AddedMemory.error()); + return {}; +} + +[[nodiscard]] Expected<std::int64_t> signedImmediate(const MachineOperand &Operand) { + if (const std::int64_t *Value = Operand.getIf<std::int64_t>()) + return *Value; + const auto *Integer = Operand.getIf<IntegerImmediateOperand>(); + if (Integer == nullptr || Integer->Value.bitWidth() == 0 || Integer->Value.bitWidth() > 64) + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V constant selection requires an immediate no wider than 64 bits"); + std::uint64_t Raw = Integer->Value.words().empty() ? 0 : Integer->Value.words().front(); + const std::uint32_t Width = Integer->Value.bitWidth(); + if (Width < 64 && (Raw & (std::uint64_t{1} << (Width - 1U))) != 0) + Raw |= ~std::uint64_t{0} << Width; + return std::bit_cast<std::int64_t>(Raw); +} + +} // namespace + +RISCVLegalizerInfo::RISCVLegalizerInfo(const RISCVSubtarget &Subtarget) : Subtarget(Subtarget) { + const LowLevelType S1 = *LowLevelType::makeCategoryNeutralScalar(1); + const LowLevelType S8 = *LowLevelType::makeCategoryNeutralScalar(8); + const LowLevelType S16 = *LowLevelType::makeCategoryNeutralScalar(16); + const LowLevelType S32 = *LowLevelType::makeCategoryNeutralScalar(32); + const LowLevelType S64 = *LowLevelType::makeCategoryNeutralScalar(64); + const LowLevelType S128 = *LowLevelType::makeCategoryNeutralScalar(128); + const LowLevelType P0 = *LowLevelType::makePointer(0, 64); + const bool HasM = Subtarget.features().has(RISCVExtension::M); + const bool HasA = Subtarget.features().has(RISCVExtension::A); + const bool HasF = Subtarget.features().has(RISCVExtension::F); + const bool HasD = Subtarget.features().has(RISCVExtension::D); + const bool HasZbb = Subtarget.features().has(RISCVExtension::Zbb); + + getActionDefinitionsBuilder({opcode(GenericOpcode::G_ASSERT_SEXT), + opcode(GenericOpcode::G_ASSERT_ZEXT), + opcode(GenericOpcode::G_ASSERT_ALIGN)}) + .alwaysLegal(); + + getActionDefinitionsBuilder({opcode(GenericOpcode::G_ADD), opcode(GenericOpcode::G_SUB)}) + .legalFor({S64}) + .customFor({S32}) + .widenScalarToNextPow2(0) + .clampScalar(0, S64, S64); + + getActionDefinitionsBuilder( + {opcode(GenericOpcode::G_AND), opcode(GenericOpcode::G_OR), opcode(GenericOpcode::G_XOR)}) + .legalFor({S64}) + .widenScalarToNextPow2(0) + .clampScalar(0, S64, S64); + + getActionDefinitionsBuilder({opcode(GenericOpcode::G_UADDE), opcode(GenericOpcode::G_UADDO), + opcode(GenericOpcode::G_USUBE), opcode(GenericOpcode::G_USUBO), + opcode(GenericOpcode::G_READ_REGISTER), + opcode(GenericOpcode::G_WRITE_REGISTER)}) + .lower(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SADDE), opcode(GenericOpcode::G_SADDO), + opcode(GenericOpcode::G_SSUBE), opcode(GenericOpcode::G_SSUBO)}) + .minScalar(0, S64) + .lower(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_UADDSAT), opcode(GenericOpcode::G_SADDSAT), + opcode(GenericOpcode::G_USUBSAT), opcode(GenericOpcode::G_SSUBSAT), + opcode(GenericOpcode::G_SSHLSAT), opcode(GenericOpcode::G_USHLSAT)}) + .lower(); + + getActionDefinitionsBuilder( + {opcode(GenericOpcode::G_SHL), opcode(GenericOpcode::G_ASHR), opcode(GenericOpcode::G_LSHR)}) + .legalFor({{S64, S64}}) + .customFor({{S32, S32}}) + .widenScalarToNextPow2(0) + .clampScalar(1, S64, S64) + .clampScalar(0, S64, S64); + + getActionDefinitionsBuilder({opcode(GenericOpcode::G_ZEXT), opcode(GenericOpcode::G_SEXT), + opcode(GenericOpcode::G_ANYEXT)}) + .legalFor({{S32, S16}, {S64, S16}, {S64, S32}}) + .maxScalar(0, S64); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_TRUNC)).alwaysLegal(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_SEXT_INREG)) + .customFor({S64}) + .clampScalar(0, S64, S64) + .lower(); + + for (const GenericOpcode Generic : + {GenericOpcode::G_MERGE_VALUES, GenericOpcode::G_UNMERGE_VALUES}) { + const std::size_t BigTypeIndex = Generic == GenericOpcode::G_MERGE_VALUES ? 0 : 1; + const std::size_t LittleTypeIndex = Generic == GenericOpcode::G_MERGE_VALUES ? 1 : 0; + getActionDefinitionsBuilder(opcode(Generic)) + .widenScalarToNextPow2(LittleTypeIndex, 64) + .widenScalarToNextPow2(BigTypeIndex, 64) + .clampScalar(LittleTypeIndex, S64, S64) + .clampScalar(BigTypeIndex, S64, S64); + } + getActionDefinitionsBuilder({opcode(GenericOpcode::G_FSHL), opcode(GenericOpcode::G_FSHR)}) + .lower(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_ROTR), opcode(GenericOpcode::G_ROTL)}) + .legalFor(HasZbb, {{S64, S64}}) + .customFor(HasZbb, {{S32, S32}}) + .lower(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_BITREVERSE)).maxScalar(0, S64).lower(); + auto &ByteSwapActions = getActionDefinitionsBuilder(opcode(GenericOpcode::G_BSWAP)); + if (HasZbb) + ByteSwapActions.legalFor({S64}).clampScalar(0, S64, S64); + else + ByteSwapActions.maxScalar(0, S64).lower(); + + auto &CountZerosActions = + getActionDefinitionsBuilder({opcode(GenericOpcode::G_CTLZ), opcode(GenericOpcode::G_CTTZ)}); + auto &CountZerosPoisonActions = getActionDefinitionsBuilder( + {opcode(GenericOpcode::G_CTLZ_ZERO_POISON), opcode(GenericOpcode::G_CTTZ_ZERO_POISON)}); + if (HasZbb) { + CountZerosActions.legalFor({{S64, S64}}) + .customFor({{S32, S32}}) + .clampScalar(0, S32, S64) + .widenScalarToNextPow2(0) + .scalarSameSizeAs(1, 0); + } else { + CountZerosActions.maxScalar(0, S64).scalarSameSizeAs(1, 0).lower(); + CountZerosPoisonActions.maxScalar(0, S64).scalarSameSizeAs(1, 0); + } + CountZerosPoisonActions.lower(); + + getActionDefinitionsBuilder(opcode(GenericOpcode::G_CTLS)) + .maxScalar(0, S64) + .scalarSameSizeAs(1, 0) + .lower(); + + auto &CountPopulationActions = getActionDefinitionsBuilder(opcode(GenericOpcode::G_CTPOP)); + if (HasZbb) { + CountPopulationActions.legalFor({{S64, S64}}).clampScalar(0, S64, S64).scalarSameSizeAs(1, 0); + } else { + CountPopulationActions.widenScalarToNextPow2(0, 8) + .clampScalar(0, S8, S64) + .scalarSameSizeAs(1, 0) + .lower(); + } + + getActionDefinitionsBuilder(opcode(GenericOpcode::G_CONSTANT)) + .legalFor({P0}) + .customFor({S64}) + .widenScalarToNextPow2(0) + .clampScalar(0, S64, S64); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FREEZE)) + .legalFor({S16, S32, S64, P0}) + .widenScalarToNextPow2(0) + .clampScalar(0, S16, S64); + getActionDefinitionsBuilder( + {opcode(GenericOpcode::G_IMPLICIT_DEF), opcode(GenericOpcode::G_CONSTANT_FOLD_BARRIER)}) + .legalFor({S32, S64, P0}) + .widenScalarToNextPow2(0) + .clampScalar(0, S32, S64); + + getActionDefinitionsBuilder(opcode(GenericOpcode::G_ICMP)) + .legalFor({{S64, S64}, {S64, P0}}) + .widenScalarToNextPow2(1, 8) + .clampScalar(1, S64, S64) + .clampScalar(0, S64, S64); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_SELECT)) + .legalFor({{S32, S64}, {S64, S64}, {P0, S64}}) + .widenScalarToNextPow2(0) + .clampScalar(0, S32, S64) + .clampScalar(1, S64, S64); + + auto &LoadRules = getActionDefinitionsBuilder(opcode(GenericOpcode::G_LOAD)); + auto &StoreRules = getActionDefinitionsBuilder(opcode(GenericOpcode::G_STORE)); + auto &ExtLoadRules = getActionDefinitionsBuilder( + {opcode(GenericOpcode::G_SEXTLOAD), opcode(GenericOpcode::G_ZEXTLOAD)}); + const auto GetScalarMemAlign = [&Subtarget](std::uint64_t Size) { + return Subtarget.features().has(RISCVSubtargetOption::UnalignedScalarMemory) ? 8 : Size; + }; + LoadRules + .legalForTypesWithMemDesc({{S16, P0, S8, GetScalarMemAlign(8)}, + {S32, P0, S8, GetScalarMemAlign(8)}, + {S16, P0, S16, GetScalarMemAlign(16)}, + {S32, P0, S16, GetScalarMemAlign(16)}, + {S32, P0, S32, GetScalarMemAlign(32)}, + {S64, P0, S8, GetScalarMemAlign(8)}, + {S64, P0, S16, GetScalarMemAlign(16)}, + {S64, P0, S32, GetScalarMemAlign(32)}, + {S64, P0, S64, GetScalarMemAlign(64)}, + {P0, P0, S64, GetScalarMemAlign(64)}}) + .widenScalarToNextPow2(0, 8) + .lowerIfMemSizeNotByteSizePow2() + .clampScalar(0, S16, S64) + .lower(); + StoreRules + .legalForTypesWithMemDesc({{S16, P0, S8, GetScalarMemAlign(8)}, + {S32, P0, S8, GetScalarMemAlign(8)}, + {S16, P0, S16, GetScalarMemAlign(16)}, + {S32, P0, S16, GetScalarMemAlign(16)}, + {S32, P0, S32, GetScalarMemAlign(32)}, + {S64, P0, S8, GetScalarMemAlign(8)}, + {S64, P0, S16, GetScalarMemAlign(16)}, + {S64, P0, S32, GetScalarMemAlign(32)}, + {S64, P0, S64, GetScalarMemAlign(64)}, + {P0, P0, S64, GetScalarMemAlign(64)}}) + .clampScalar(0, S16, S64) + .lowerIfMemSizeNotByteSizePow2() + .lower(); + ExtLoadRules + .legalForTypesWithMemDesc({{S64, P0, S8, GetScalarMemAlign(8)}, + {S64, P0, S16, GetScalarMemAlign(16)}, + {S64, P0, S32, GetScalarMemAlign(32)}}) + .widenScalarToNextPow2(0) + .clampScalar(0, S64, S64) + .lower(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FPEXTLOAD)) + .legalForTypesWithMemDesc({{S64, P0, S32, GetScalarMemAlign(32)}}) + .lower(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FPTRUNCSTORE)) + .legalForTypesWithMemDesc({{S64, P0, S32, GetScalarMemAlign(32)}}) + .lower(); + + getActionDefinitionsBuilder({opcode(GenericOpcode::G_PTR_ADD), opcode(GenericOpcode::G_PTRMASK)}) + .legalFor({{P0, S64}}); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_PTRTOINT)) + .legalFor({{S64, P0}}) + .clampScalar(0, S64, S64); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_INTTOPTR)) + .legalFor({{P0, S64}}) + .clampScalar(1, S64, S64); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_BR)).alwaysLegal(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_BRCOND)).legalFor({S64}).minScalar(0, S64); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_BRJT)).customFor({{P0, S64}}); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_BRINDIRECT)).legalFor({P0}); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_PHI)) + .legalFor({P0, S32, S64}) + .widenScalarToNextPow2(0) + .clampScalar(0, S32, S64); + getActionDefinitionsBuilder( + {opcode(GenericOpcode::G_GLOBAL_VALUE), opcode(GenericOpcode::G_JUMP_TABLE), + opcode(GenericOpcode::G_CONSTANT_POOL), opcode(GenericOpcode::G_FRAME_INDEX)}) + .legalFor({P0}); + + if (HasM) { + getActionDefinitionsBuilder(opcode(GenericOpcode::G_MUL)) + .legalFor({S64}) + .widenScalarToNextPow2(0) + .clampScalar(0, S64, S64); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SMULH), opcode(GenericOpcode::G_UMULH)}) + .legalFor({S64}) + .lower(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SMULO), opcode(GenericOpcode::G_UMULO)}) + .minScalar(0, S64) + .lower(); + } else { + getActionDefinitionsBuilder(opcode(GenericOpcode::G_MUL)) + .libcallFor({S64, S128}) + .widenScalarToNextPow2(0) + .clampScalar(0, S64, S128); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SMULH), opcode(GenericOpcode::G_UMULH)}) + .lowerFor({S64}); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SMULO), opcode(GenericOpcode::G_UMULO)}) + .minScalar(0, S64) + // Widen s64 to s128 so one libcall provides both the low result and + // the high half needed for the overflow check. + .widenScalarIf( + [S64](const LegalizationQuery &Query) { + return !Query.Types.empty() && Query.Types[0] == S64; + }, + [S128](const LegalizationQuery &) { return std::pair{std::size_t{0}, S128}; }) + .lower(); + } + if (HasM) { + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SDIV), opcode(GenericOpcode::G_UDIV), + opcode(GenericOpcode::G_UREM)}) + .legalFor({S64}) + .customFor({S32}) + .libcallFor({S128}) + .clampScalar(0, S32, S128) + .widenScalarToNextPow2(0); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_SREM)) + .legalFor({S64}) + .libcallFor({S128}) + .clampScalar(0, S64, S128) + .widenScalarToNextPow2(0); + } else { + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SDIV), opcode(GenericOpcode::G_UDIV), + opcode(GenericOpcode::G_SREM), opcode(GenericOpcode::G_UREM)}) + .libcallFor({S64, S128}) + .clampScalar(0, S64, S128) + .widenScalarToNextPow2(0); + } + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SDIVREM), opcode(GenericOpcode::G_UDIVREM)}) + .lower(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_ABS)).lower(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_ABDS), opcode(GenericOpcode::G_ABDU)}) + .minScalar(HasZbb, 0, S64) + .lower(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_UMAX), opcode(GenericOpcode::G_UMIN), + opcode(GenericOpcode::G_SMAX), opcode(GenericOpcode::G_SMIN)}) + .legalFor(HasZbb, {S64}) + .minScalar(HasZbb, 0, S64) + .lower(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SCMP), opcode(GenericOpcode::G_UCMP)}) + .lower(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_MEMCPY), opcode(GenericOpcode::G_MEMMOVE), + opcode(GenericOpcode::G_MEMSET)}) + .libcall(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_MEMCPY_INLINE)).lower(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_DYN_STACKALLOC), + opcode(GenericOpcode::G_STACKSAVE), + opcode(GenericOpcode::G_STACKRESTORE)}) + .lower(); + + getActionDefinitionsBuilder( + {opcode(GenericOpcode::G_FADD), opcode(GenericOpcode::G_FSUB), opcode(GenericOpcode::G_FMUL), + opcode(GenericOpcode::G_FDIV), opcode(GenericOpcode::G_FMA), opcode(GenericOpcode::G_FSQRT), + opcode(GenericOpcode::G_FMAXNUM), opcode(GenericOpcode::G_FMINNUM), + opcode(GenericOpcode::G_FMAXIMUMNUM), opcode(GenericOpcode::G_FMINIMUMNUM)}) + .legalFor(HasF, {S32}) + .legalFor(HasD, {S64}) + .libcallFor({S32, S64}) + .libcallFor({S128}); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_FNEG), opcode(GenericOpcode::G_FABS)}) + .legalFor(HasF, {S32}) + .legalFor(HasD, {S64}) + .lowerFor({S32, S64, S128}); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FREM)) + .libcallFor({S32, S64, S128}) + .minScalar(0, S32) + .scalarize(0); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FCOPYSIGN)) + .legalFor(HasF, {{S32, S32}}) + .legalFor(HasD, {{S64, S64}, {S32, S64}, {S64, S32}}) + .lower(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FPTRUNC)) + .legalFor(HasD, {{S32, S64}}) + .libcallFor({{S32, S64}, {S32, S128}, {S64, S128}}); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FPEXT)) + .legalFor(HasD, {{S64, S32}}) + .libcallFor({{S64, S32}, {S128, S32}, {S128, S64}}); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FCMP)) + .legalFor(HasF, {{S64, S32}}) + .legalFor(HasD, {{S64, S64}}) + .clampScalar(0, S64, S64) + .libcallFor({{S64, S32}, {S64, S64}, {S64, S128}}); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_IS_FPCLASS)) + .customFor(HasF, {{S1, S32}}) + .customFor(HasD, {{S1, S64}}) + .lower(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FCONSTANT)) + .legalFor(HasF, {S32}) + .legalFor(HasD, {S64}) + .customFor({S32, S64}) + .lowerFor({S64, S128}); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_FPTOSI), opcode(GenericOpcode::G_FPTOUI)}) + .legalFor(HasF, {{S64, S32}}) + .legalFor(HasD, {{S64, S64}}) + .customFor(HasF, {{S32, S32}}) + .customFor(HasD, {{S32, S64}}) + .widenScalarToNextPow2(0) + .minScalar(0, S32) + .libcallFor({{S32, S32}, + {S64, S32}, + {S32, S64}, + {S64, S64}, + {S32, S128}, + {S64, S128}, + {S128, S32}, + {S128, S64}, + {S128, S128}}); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_SITOFP), opcode(GenericOpcode::G_UITOFP)}) + .legalFor(HasF, {{S32, S64}}) + .legalFor(HasD, {{S64, S64}}) + .widenScalarToNextPow2(1) + .widenScalarIf( + [HasF, HasD](const LegalizationQuery &Query) { + if (Query.Types.size() < 2 || !Query.Types[0].isCategoryNeutralScalar() || + !Query.Types[1].isCategoryNeutralScalar() || + Query.Types[1].scalarBitWidth() >= 64) { + return false; + } + return (HasF && Query.Types[0].scalarBitWidth() == 32) || + (HasD && Query.Types[0].scalarBitWidth() == 64); + }, + [S64](const LegalizationQuery &) { return std::pair{std::size_t{1}, S64}; }) + .minScalar(1, S32) + .libcallFor({{S32, S32}, + {S64, S32}, + {S32, S64}, + {S64, S64}, + {S128, S32}, + {S128, S64}, + {S32, S128}, + {S64, S128}, + {S128, S128}}); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_FCEIL), opcode(GenericOpcode::G_FFLOOR), + opcode(GenericOpcode::G_FRINT), opcode(GenericOpcode::G_FNEARBYINT), + opcode(GenericOpcode::G_INTRINSIC_TRUNC), + opcode(GenericOpcode::G_INTRINSIC_ROUND), + opcode(GenericOpcode::G_INTRINSIC_ROUNDEVEN)}) + .libcallFor({S32, S64, S128}); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_FCOS), opcode(GenericOpcode::G_FSIN), + opcode(GenericOpcode::G_FTAN), opcode(GenericOpcode::G_FPOW), + opcode(GenericOpcode::G_FLOG), opcode(GenericOpcode::G_FLOG2), + opcode(GenericOpcode::G_FLOG10), opcode(GenericOpcode::G_FEXP), + opcode(GenericOpcode::G_FEXP2), opcode(GenericOpcode::G_FEXP10), + opcode(GenericOpcode::G_FACOS), opcode(GenericOpcode::G_FASIN), + opcode(GenericOpcode::G_FATAN), opcode(GenericOpcode::G_FATAN2), + opcode(GenericOpcode::G_FCOSH), opcode(GenericOpcode::G_FSINH), + opcode(GenericOpcode::G_FTANH), opcode(GenericOpcode::G_FMODF)}) + .libcallFor({S32, S64, S128}); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_FPOWI), opcode(GenericOpcode::G_FLDEXP)}) + .libcallFor({{S32, S32}, {S64, S32}, {S128, S32}}); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FCANONICALIZE)) + .legalFor(HasF, {S32}) + .legalFor(HasD, {S64}); + + getActionDefinitionsBuilder(opcode(GenericOpcode::G_VASTART)).customFor({P0}); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_VAARG)).clampScalar(0, S64, S64).lower(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_ATOMIC_CMPXCHG_WITH_SUCCESS)) + .lowerIf([S8, S16, S32, S64, P0](const LegalizationQuery &Query) { + return Query.Types.size() >= 3 && + (Query.Types[0] == S8 || Query.Types[0] == S16 || Query.Types[0] == S32 || + Query.Types[0] == S64) && + Query.Types[2] == P0; + }); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_ATOMIC_CMPXCHG)) + .legalFor(HasA, {{S64, P0}}) + .libcallFor(!HasA, {{S8, P0}, {S16, P0}, {S32, P0}, {S64, P0}}) + .clampScalar(0, S64, S64); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_ATOMICRMW_ADD)) + .legalFor(HasA, {{S64, P0}}) + .libcallFor(!HasA, {{S8, P0}, {S16, P0}, {S32, P0}, {S64, P0}}) + .clampScalar(0, S64, S64); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_ATOMICRMW_SUB)) + .libcallFor(!HasA, {{S8, P0}, {S16, P0}, {S32, P0}, {S64, P0}}) + .clampScalar(0, S64, S64) + .lower(); + getActionDefinitionsBuilder( + {opcode(GenericOpcode::G_ATOMICRMW_XCHG), opcode(GenericOpcode::G_ATOMICRMW_AND), + opcode(GenericOpcode::G_ATOMICRMW_OR), opcode(GenericOpcode::G_ATOMICRMW_XOR), + opcode(GenericOpcode::G_ATOMICRMW_MAX), opcode(GenericOpcode::G_ATOMICRMW_MIN), + opcode(GenericOpcode::G_ATOMICRMW_UMAX), opcode(GenericOpcode::G_ATOMICRMW_UMIN)}) + .legalFor(HasA, {{S64, P0}}) + .libcallFor(!HasA, {{S8, P0}, {S16, P0}, {S32, P0}, {S64, P0}}) + .clampScalar(0, S64, S64); + getActionDefinitionsBuilder( + {opcode(GenericOpcode::G_INTRINSIC), opcode(GenericOpcode::G_INTRINSIC_W_SIDE_EFFECTS)}) + .alwaysLegal(); + getActionDefinitionsBuilder(opcode(GenericOpcode::G_FENCE)).alwaysLegal(); + getActionDefinitionsBuilder({opcode(GenericOpcode::G_TRAP), opcode(GenericOpcode::G_DEBUGTRAP), + opcode(GenericOpcode::G_UBSANTRAP)}) + .alwaysLegal(); +} + +Expected<void> RISCVLegalizerInfo::legalizeBRJT(MachineInstr &Instruction, + MachineIRBuilder &MIRBuilder) const { + if (Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_BRJT requires table pointer, table index, and index operands"); + + const MachineOperand *TablePointerOperand = Instruction.operandAt(0); + const MachineOperand *TableIndexOperand = Instruction.operandAt(1); + const MachineOperand *IndexOperand = Instruction.operandAt(2); + const Register *TablePointer = + TablePointerOperand == nullptr ? nullptr : TablePointerOperand->getIf<Register>(); + const JumpTableOperand *TableIndex = + TableIndexOperand == nullptr ? nullptr : TableIndexOperand->getIf<JumpTableOperand>(); + const Register *Index = IndexOperand == nullptr ? nullptr : IndexOperand->getIf<Register>(); + if (TablePointer == nullptr || TableIndex == nullptr || Index == nullptr || + !TablePointer->isVirtual() || !Index->isVirtual()) { + return makeUnexpected( + ErrorCode::VerificationError, + "RISC-V G_BRJT requires virtual-register table pointer and index operands"); + } + + MachineFunction &Function = MIRBuilder.function(); + const MachineJumpTableEntry *TableEntry = Function.jumpTableInfo().table(TableIndex->Index); + const MachineJumpTableDesc *Table = TableEntry == nullptr ? nullptr : TableEntry->description(); + if (Table == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_BRJT references an undefined jump table"); + } + if (!std::has_single_bit(Table->EntrySize)) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_BRJT requires a power-of-two jump table entry size"); + } + + auto PointerType = Function.registerInfo().getType(*TablePointer); + if (!PointerType) + return std::unexpected(PointerType.error()); + auto IndexType = Function.registerInfo().getType(*Index); + if (!IndexType) + return std::unexpected(IndexType.error()); + if (!PointerType->isPointer() || !IndexType->isCategoryNeutralScalar()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_BRJT requires scalar pointer and integer index types"); + } + const std::optional<std::uint32_t> AddressSpace = PointerType->addressSpace(); + if (!AddressSpace) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_BRJT table pointer has no address space"); + } + + auto EntryType = LowLevelType::makeCategoryNeutralScalar( + static_cast<std::uint64_t>(Table->EntrySize) * std::uint64_t{8}); + if (!EntryType) + return std::unexpected(EntryType.error()); + auto EntryMemorySize = MemorySize::fixed(Table->EntrySize); + if (!EntryMemorySize) + return std::unexpected(EntryMemorySize.error()); + + MachineMemOperandDesc LoadDescription; + LoadDescription.Accesses = {MemoryAccessFlag::Load}; + LoadDescription.Size = *EntryMemorySize; + LoadDescription.MemoryType = *EntryType; + LoadDescription.Align = Table->Align; + LoadDescription.AddressSpace = *AddressSpace; + LoadDescription.Base = MemoryBase::jumpTablePseudo(); + auto LoadMemory = Function.createMemOperand(std::move(LoadDescription)); + if (!LoadMemory) + return std::unexpected(LoadMemory.error()); + + auto ShiftAmount = Function.registerInfo().createVirtualRegister(*IndexType); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + if (auto Built = MIRBuilder.buildConstant( + *ShiftAmount, + static_cast<std::int64_t>(std::countr_zero(static_cast<unsigned int>(Table->EntrySize)))); + !Built) { + return std::unexpected(Built.error()); + } + + auto ScaledIndex = Function.registerInfo().createVirtualRegister(*IndexType); + if (!ScaledIndex) + return std::unexpected(ScaledIndex.error()); + if (auto Built = MIRBuilder.buildShl(*ScaledIndex, *Index, *ShiftAmount); !Built) + return std::unexpected(Built.error()); + + auto EntryAddress = Function.registerInfo().createVirtualRegister(*PointerType); + if (!EntryAddress) + return std::unexpected(EntryAddress.error()); + auto AddressBuilder = MIRBuilder.buildInstr(opcode(GenericOpcode::G_PTR_ADD)); + if (!AddressBuilder) + return std::unexpected(AddressBuilder.error()); + if (auto Added = AddressBuilder->addDef(*EntryAddress); !Added) + return std::unexpected(Added.error()); + if (auto Added = AddressBuilder->addUse(*TablePointer); !Added) + return std::unexpected(Added.error()); + if (auto Added = AddressBuilder->addUse(*ScaledIndex); !Added) + return std::unexpected(Added.error()); + if (auto Built = AddressBuilder->build(); !Built) + return std::unexpected(Built.error()); + + Register Target; + switch (Table->Encoding) { + case JumpTableEntryEncoding::Custom32: { + auto LoadedOffset = Function.registerInfo().createVirtualRegister(*IndexType); + if (!LoadedOffset) + return std::unexpected(LoadedOffset.error()); + auto LoadBuilder = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SEXTLOAD)); + if (!LoadBuilder) + return std::unexpected(LoadBuilder.error()); + if (auto Added = LoadBuilder->addDef(*LoadedOffset); !Added) + return std::unexpected(Added.error()); + if (auto Added = LoadBuilder->addUse(*EntryAddress); !Added) + return std::unexpected(Added.error()); + if (auto Added = LoadBuilder->addMemOperand(**LoadMemory); !Added) + return std::unexpected(Added.error()); + if (auto Built = LoadBuilder->build(); !Built) + return std::unexpected(Built.error()); + + auto TargetPointer = Function.registerInfo().createVirtualRegister(*PointerType); + if (!TargetPointer) + return std::unexpected(TargetPointer.error()); + if (auto Built = MIRBuilder.buildIntToPtr(*TargetPointer, *LoadedOffset); !Built) + return std::unexpected(Built.error()); + Target = *TargetPointer; + break; + } + case JumpTableEntryEncoding::LabelDifference32: { + auto LoadedOffset = Function.registerInfo().createVirtualRegister(*IndexType); + if (!LoadedOffset) + return std::unexpected(LoadedOffset.error()); + auto LoadBuilder = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SEXTLOAD)); + if (!LoadBuilder) + return std::unexpected(LoadBuilder.error()); + if (auto Added = LoadBuilder->addDef(*LoadedOffset); !Added) + return std::unexpected(Added.error()); + if (auto Added = LoadBuilder->addUse(*EntryAddress); !Added) + return std::unexpected(Added.error()); + if (auto Added = LoadBuilder->addMemOperand(**LoadMemory); !Added) + return std::unexpected(Added.error()); + if (auto Built = LoadBuilder->build(); !Built) + return std::unexpected(Built.error()); + + auto TargetPointer = Function.registerInfo().createVirtualRegister(*PointerType); + if (!TargetPointer) + return std::unexpected(TargetPointer.error()); + auto TargetBuilder = MIRBuilder.buildInstr(opcode(GenericOpcode::G_PTR_ADD)); + if (!TargetBuilder) + return std::unexpected(TargetBuilder.error()); + if (auto Added = TargetBuilder->addDef(*TargetPointer); !Added) + return std::unexpected(Added.error()); + if (auto Added = TargetBuilder->addUse(*TablePointer); !Added) + return std::unexpected(Added.error()); + if (auto Added = TargetBuilder->addUse(*LoadedOffset); !Added) + return std::unexpected(Added.error()); + if (auto Built = TargetBuilder->build(); !Built) + return std::unexpected(Built.error()); + Target = *TargetPointer; + break; + } + case JumpTableEntryEncoding::BlockAddress: { + auto TargetPointer = Function.registerInfo().createVirtualRegister(*PointerType); + if (!TargetPointer) + return std::unexpected(TargetPointer.error()); + auto LoadBuilder = MIRBuilder.buildInstr(opcode(GenericOpcode::G_LOAD)); + if (!LoadBuilder) + return std::unexpected(LoadBuilder.error()); + if (auto Added = LoadBuilder->addDef(*TargetPointer); !Added) + return std::unexpected(Added.error()); + if (auto Added = LoadBuilder->addUse(*EntryAddress); !Added) + return std::unexpected(Added.error()); + if (auto Added = LoadBuilder->addMemOperand(**LoadMemory); !Added) + return std::unexpected(Added.error()); + if (auto Built = LoadBuilder->build(); !Built) + return std::unexpected(Built.error()); + Target = *TargetPointer; + break; + } + default: + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V G_BRJT has an unsupported jump table encoding"); + } + + auto BranchBuilder = MIRBuilder.buildInstr(opcode(GenericOpcode::G_BRINDIRECT)); + if (!BranchBuilder) + return std::unexpected(BranchBuilder.error()); + if (auto Added = BranchBuilder->addUse(Target); !Added) + return std::unexpected(Added.error()); + if (auto Built = BranchBuilder->build(); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> RISCVLegalizerInfo::legalizeVAStart(MachineInstr &Instruction, + MachineIRBuilder &MIRBuilder) const { + if (Instruction.operandCount() != 1 || Instruction.memOperands().size() != 1 || + Instruction.memOperands().front() == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_VASTART requires one pointer operand and one memory operand"); + } + + MachineFunction &Function = MIRBuilder.function(); + const RISCVFunctionInfo *FunctionInfo = Function.getTargetInfo<RISCVFunctionInfo>(); + if (FunctionInfo == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_VASTART requires RISC-V function information"); + + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr || !Destination->isVirtual()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_VASTART requires a virtual-register pointer operand"); + } + auto AddressType = Function.registerInfo().getType(*Destination); + if (!AddressType) + return std::unexpected(AddressType.error()); + + const std::int64_t LLVMFrameIndex = FunctionInfo->varArgsFrameIndex(); + const bool IsFixedObject = LLVMFrameIndex < 0; + const std::uint64_t ObjectOrdinal = IsFixedObject + ? static_cast<std::uint64_t>(-LLVMFrameIndex - 1) + : static_cast<std::uint64_t>(LLVMFrameIndex); + std::optional<FrameIndex> VarArgsFrameIndex; + std::uint64_t CurrentOrdinal = 0; + for (const StackObject &Object : Function.frameInfo().objects()) { + if (Object.isTombstone() || Object.isFixed() != IsFixedObject) + continue; + if (CurrentOrdinal == ObjectOrdinal) { + VarArgsFrameIndex = Object.index(); + break; + } + ++CurrentOrdinal; + } + if (!VarArgsFrameIndex) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_VASTART references a missing varargs frame object"); + } + + auto VarArgsAddress = Function.registerInfo().createVirtualRegister(*AddressType); + if (!VarArgsAddress) + return std::unexpected(VarArgsAddress.error()); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + auto FrameIndexBuilder = MIRBuilder.buildInstr(opcode(GenericOpcode::G_FRAME_INDEX)); + if (!FrameIndexBuilder) + return std::unexpected(FrameIndexBuilder.error()); + if (auto Added = FrameIndexBuilder->addDef(*VarArgsAddress); !Added) + return std::unexpected(Added.error()); + if (auto Added = FrameIndexBuilder->addFrameIndex(*VarArgsFrameIndex); !Added) + return std::unexpected(Added.error()); + if (auto Built = FrameIndexBuilder->build(); !Built) + return std::unexpected(Built.error()); + + auto StoreBuilder = MIRBuilder.buildInstr(opcode(GenericOpcode::G_STORE)); + if (!StoreBuilder) + return std::unexpected(StoreBuilder.error()); + if (auto Added = StoreBuilder->addUse(*VarArgsAddress); !Added) + return std::unexpected(Added.error()); + if (auto Added = StoreBuilder->addUse(*Destination); !Added) + return std::unexpected(Added.error()); + if (auto Added = StoreBuilder->addMemOperand(*Instruction.memOperands().front()); !Added) + return std::unexpected(Added.error()); + if (auto Built = StoreBuilder->build(); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> RISCVLegalizerInfo::legalizeCustom(LegalizerHelper &Helper, + MachineInstr &Instruction, + const LegalizationDecision &Decision) const { + if (Decision.Action != LegalizationAction::Custom) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V custom legalizer received a non-custom action"); + + MachineFunction &Function = Helper.MIRBuilder.function(); + const TargetDescription &Target = Helper.MIRBuilder.target(); + if (Instruction.opcode() == opcode(GenericOpcode::G_BRJT)) + return legalizeBRJT(Instruction, Helper.MIRBuilder); + if (Instruction.opcode() == opcode(GenericOpcode::G_VASTART)) + return legalizeVAStart(Instruction, Helper.MIRBuilder); + if (Instruction.opcode() == opcode(GenericOpcode::G_ADD) || + Instruction.opcode() == opcode(GenericOpcode::G_SUB)) { + auto XLenType = LowLevelType::makeCategoryNeutralScalar(64); + if (!XLenType) + return std::unexpected(XLenType.error()); + + Helper.Observer.changingInstr(Instruction); + if (auto Widened = Helper.widenScalarSrc(Instruction, *XLenType, 1, GenericOpcode::G_ANYEXT); + !Widened) + return std::unexpected(Widened.error()); + if (auto Widened = Helper.widenScalarSrc(Instruction, *XLenType, 2, GenericOpcode::G_ANYEXT); + !Widened) + return std::unexpected(Widened.error()); + + auto ALUDestination = Function.registerInfo().createVirtualRegister(*XLenType); + if (!ALUDestination) + return std::unexpected(ALUDestination.error()); + auto SignExtendedDestination = Function.registerInfo().createVirtualRegister(*XLenType); + if (!SignExtendedDestination) + return std::unexpected(SignExtendedDestination.error()); + + MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V word arithmetic requires a register destination"); + const Register OriginalDestination = *Destination; + if (auto Point = Helper.MIRBuilder.setInsertPointAfter(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = + Helper.MIRBuilder.buildSExtInReg(*SignExtendedDestination, *ALUDestination, 32); + !Built) + return std::unexpected(Built.error()); + if (auto Built = Helper.MIRBuilder.buildTrunc(OriginalDestination, *SignExtendedDestination); + !Built) + return std::unexpected(Built.error()); + if (auto Changed = DestinationOperand->setRegister(*ALUDestination); !Changed) + return std::unexpected(Changed.error()); + Helper.Observer.changedInstr(Instruction); + return {}; + } + if (Instruction.opcode() == opcode(GenericOpcode::G_SEXT_INREG)) { + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *SizeOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const std::int64_t *Size = + SizeOperand == nullptr ? nullptr : SizeOperand->getIf<std::int64_t>(); + if (Destination == nullptr || Size == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_SEXT_INREG has invalid operands"); + auto DestinationType = Function.registerInfo().getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (DestinationType->minimumBitWidth() == 64 && *Size == 32) + return {}; + if (Subtarget.features().has(RISCVExtension::Zbb) && (*Size == 8 || *Size == 16)) + return {}; + return Helper.lower(Instruction, LegalizationDecision{ + .Action = LegalizationAction::Lower, + .TypeIndex = 0, + .NewType = {}, + }); + } + if (Instruction.opcode() == opcode(GenericOpcode::G_ASHR) || + Instruction.opcode() == opcode(GenericOpcode::G_LSHR) || + Instruction.opcode() == opcode(GenericOpcode::G_SHL)) { + auto XLenType = LowLevelType::makeCategoryNeutralScalar(64); + if (!XLenType) + return std::unexpected(XLenType.error()); + const MachineOperand *ShiftOperand = Instruction.operandAt(2); + const Register *ShiftAmount = + ShiftOperand == nullptr ? nullptr : ShiftOperand->getIf<Register>(); + if (ShiftAmount == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V shift legalization requires a register shift amount"); + + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (getIConstantVRegValWithLookThrough(*ShiftAmount, Function.registerInfo())) { + GenericOpcode ExtensionOpcode = GenericOpcode::G_ANYEXT; + if (Generic == GenericOpcode::G_ASHR) + ExtensionOpcode = GenericOpcode::G_SEXT; + else if (Generic == GenericOpcode::G_LSHR) + ExtensionOpcode = GenericOpcode::G_ZEXT; + + Helper.Observer.changingInstr(Instruction); + if (auto Widened = Helper.widenScalarSrc(Instruction, *XLenType, 1, ExtensionOpcode); + !Widened) + return std::unexpected(Widened.error()); + if (auto Widened = Helper.widenScalarSrc(Instruction, *XLenType, 2, GenericOpcode::G_ZEXT); + !Widened) + return std::unexpected(Widened.error()); + if (auto Widened = Helper.widenScalarDst(Instruction, *XLenType); !Widened) + return std::unexpected(Widened.error()); + Helper.Observer.changedInstr(Instruction); + return {}; + } + + Helper.Observer.changingInstr(Instruction); + if (auto Widened = Helper.widenScalarSrc(Instruction, *XLenType, 1, GenericOpcode::G_ANYEXT); + !Widened) + return std::unexpected(Widened.error()); + if (auto Widened = Helper.widenScalarSrc(Instruction, *XLenType, 2, GenericOpcode::G_ANYEXT); + !Widened) + return std::unexpected(Widened.error()); + if (auto Widened = Helper.widenScalarDst(Instruction, *XLenType); !Widened) + return std::unexpected(Widened.error()); + auto WordOpcode = getRISCVWOpcode(Generic); + if (!WordOpcode) + return std::unexpected(WordOpcode.error()); + if (auto Changed = Instruction.setOpcode(*WordOpcode); !Changed) + return std::unexpected(Changed.error()); + Helper.Observer.changedInstr(Instruction); + return {}; + } + if (Instruction.opcode() == opcode(GenericOpcode::G_SDIV) || + Instruction.opcode() == opcode(GenericOpcode::G_UDIV) || + Instruction.opcode() == opcode(GenericOpcode::G_UREM) || + Instruction.opcode() == opcode(GenericOpcode::G_ROTL) || + Instruction.opcode() == opcode(GenericOpcode::G_ROTR)) { + auto XLenType = LowLevelType::makeCategoryNeutralScalar(64); + if (!XLenType) + return std::unexpected(XLenType.error()); + + Helper.Observer.changingInstr(Instruction); + if (auto Widened = Helper.widenScalarSrc(Instruction, *XLenType, 1, GenericOpcode::G_ANYEXT); + !Widened) + return std::unexpected(Widened.error()); + if (auto Widened = Helper.widenScalarSrc(Instruction, *XLenType, 2, GenericOpcode::G_ANYEXT); + !Widened) + return std::unexpected(Widened.error()); + if (auto Widened = Helper.widenScalarDst(Instruction, *XLenType); !Widened) + return std::unexpected(Widened.error()); + auto WordOpcode = getRISCVWOpcode(static_cast<GenericOpcode>(Instruction.opcode().value())); + if (!WordOpcode) + return std::unexpected(WordOpcode.error()); + if (auto Changed = Instruction.setOpcode(*WordOpcode); !Changed) + return std::unexpected(Changed.error()); + Helper.Observer.changedInstr(Instruction); + return {}; + } + if (Instruction.opcode() == opcode(GenericOpcode::G_CTLZ) || + Instruction.opcode() == opcode(GenericOpcode::G_CTTZ) || + Instruction.opcode() == opcode(GenericOpcode::G_CTLS)) { + auto XLenType = LowLevelType::makeCategoryNeutralScalar(64); + if (!XLenType) + return std::unexpected(XLenType.error()); + + Helper.Observer.changingInstr(Instruction); + if (auto Widened = Helper.widenScalarSrc(Instruction, *XLenType, 1, GenericOpcode::G_ANYEXT); + !Widened) { + return std::unexpected(Widened.error()); + } + if (auto Widened = Helper.widenScalarDst(Instruction, *XLenType); !Widened) + return std::unexpected(Widened.error()); + auto WordOpcode = getRISCVWOpcode(static_cast<GenericOpcode>(Instruction.opcode().value())); + if (!WordOpcode) + return std::unexpected(WordOpcode.error()); + if (auto Changed = Instruction.setOpcode(*WordOpcode); !Changed) + return std::unexpected(Changed.error()); + Helper.Observer.changedInstr(Instruction); + return {}; + } + if (Instruction.opcode() == opcode(GenericOpcode::G_FPTOSI) || + Instruction.opcode() == opcode(GenericOpcode::G_FPTOUI)) { + auto XLenType = LowLevelType::makeCategoryNeutralScalar(64); + if (!XLenType) + return std::unexpected(XLenType.error()); + + Helper.Observer.changingInstr(Instruction); + if (auto Widened = Helper.widenScalarDst(Instruction, *XLenType); !Widened) + return std::unexpected(Widened.error()); + auto WordOpcode = getRISCVWOpcode(static_cast<GenericOpcode>(Instruction.opcode().value())); + if (!WordOpcode) + return std::unexpected(WordOpcode.error()); + if (auto Changed = Instruction.setOpcode(*WordOpcode); !Changed) + return std::unexpected(Changed.error()); + if (auto RoundingMode = Instruction.addOperand(MachineOperand::immediate(1)); !RoundingMode) + return std::unexpected(RoundingMode.error()); + Helper.Observer.changedInstr(Instruction); + return {}; + } + if (Instruction.opcode() == opcode(GenericOpcode::G_IS_FPCLASS)) { + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *SourceOperand = Instruction.operandAt(1); + const MachineOperand *MaskOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Destination == nullptr || Source == nullptr || MaskOperand == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_IS_FPCLASS has invalid operands"); + auto Mask = signedImmediate(*MaskOperand); + if (!Mask) + return std::unexpected(Mask.error()); + if (*Mask < 0 || *Mask > 0x3ff) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_IS_FPCLASS class mask must fit ten bits"); + + auto XLenType = LowLevelType::makeCategoryNeutralScalar(64); + if (!XLenType) + return std::unexpected(XLenType.error()); + const std::uint64_t ClassMask = static_cast<std::uint64_t>(*Mask); + const std::uint64_t RISCVClassMask = (ClassMask >> 2U) | ((ClassMask & 0x3U) << 8U); + + auto FClass = Function.registerInfo().createVirtualRegister(*XLenType); + auto MaskConstant = Function.registerInfo().createVirtualRegister(*XLenType); + auto Zero = Function.registerInfo().createVirtualRegister(*XLenType); + auto And = Function.registerInfo().createVirtualRegister(*XLenType); + if (!FClass) + return std::unexpected(FClass.error()); + if (!MaskConstant) + return std::unexpected(MaskConstant.error()); + if (!Zero) + return std::unexpected(Zero.error()); + if (!And) + return std::unexpected(And.error()); + if (auto Point = Helper.MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = Helper.MIRBuilder.buildConstant(*MaskConstant, + static_cast<std::int64_t>(RISCVClassMask)); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = Helper.MIRBuilder.buildConstant(*Zero, 0); !Built) + return std::unexpected(Built.error()); + if (auto Built = Helper.MIRBuilder.buildInstr(instructionOpcode(InstructionOpcode::G_FCLASS), + {*FClass}, {*Source}); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = Helper.MIRBuilder.buildAnd(*And, *FClass, *MaskConstant); !Built) + return std::unexpected(Built.error()); + if (auto Built = + Helper.MIRBuilder.buildICmp(IntegerPredicate::NotEqual, *Destination, *And, *Zero); + !Built) { + return std::unexpected(Built.error()); + } + return Instruction.eraseFromParent(); + } + if (Instruction.opcode() == opcode(GenericOpcode::G_ANYEXT) || + Instruction.opcode() == opcode(GenericOpcode::G_SEXT) || + Instruction.opcode() == opcode(GenericOpcode::G_ZEXT)) { + return foldConstantExtension(Instruction, Function, + static_cast<GenericOpcode>(Instruction.opcode().value())); + } + if (Instruction.opcode() == opcode(GenericOpcode::G_CONSTANT)) { + const MachineOperand *Immediate = Instruction.operandAt(1); + const std::optional<LowLevelType> Type = resultType(Function, Instruction); + if (Immediate != nullptr && Type && Type->isCategoryNeutralScalar() && + Type->scalarBitWidth() == 64) { + auto Value = signedImmediate(*Immediate); + if (Value && shouldUseRV64IntegerConstantPool(*Value, Subtarget.features())) { + Helper.Observer.changingInstr(Instruction); + auto Lowered = lowerConstantToPool(Instruction, Function, Target); + if (!Lowered) + return std::unexpected(Lowered.error()); + Helper.Observer.changedInstr(Instruction); + return {}; + } + return {}; + } + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V constant custom legalization is unavailable"); + } + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V legalizer has no lowering rule for this instruction"); +} + +Expected<bool> RISCVLegalizerInfo::legalizeIntrinsic(LegalizerHelper &Helper, + MachineInstr &Instruction) const { + const MachineOperand *IntrinsicMachineOperand = Instruction.operandAt(0); + const auto *Intrinsic = IntrinsicMachineOperand == nullptr + ? nullptr + : IntrinsicMachineOperand->getIf<rcc::IntrinsicOperand>(); + if (Intrinsic == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V intrinsic legalization requires an intrinsic operand"); + if (Intrinsic->ID != intrinsicID(GenericIntrinsic::VACopy)) + return false; + if (Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::VerificationError, + "llvm.va_copy requires destination and source operands"); + + const MachineOperand *DestinationOperand = Instruction.operandAt(1); + const MachineOperand *SourceOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Destination == nullptr || Source == nullptr || !Destination->isVirtual() || + !Source->isVirtual()) { + return makeUnexpected(ErrorCode::VerificationError, + "llvm.va_copy requires virtual-register pointer operands"); + } + + MachineFunction &Function = Helper.MIRBuilder.function(); + auto DestinationType = Function.registerInfo().getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + auto SourceType = Function.registerInfo().getType(*Source); + if (!SourceType) + return std::unexpected(SourceType.error()); + if (!DestinationType->isPointer() || !SourceType->isPointer() || + *DestinationType != *SourceType || DestinationType->scalarBitWidth() == 0) { + return makeUnexpected(ErrorCode::VerificationError, + "llvm.va_copy operands must have the same scalar pointer type"); + } + + const auto AddressSpace = DestinationType->addressSpace(); + if (!AddressSpace) + return makeUnexpected(ErrorCode::VerificationError, + "llvm.va_copy pointer type has no address space"); + auto Layout = DataLayout::parse(TargetDataLayout); + if (!Layout) + return std::unexpected(Layout.error()); + const GenericConstantType PointerType = GenericConstantType::pointer(*AddressSpace); + auto PointerWidth = Layout->getPointerSizeInBits(*AddressSpace); + if (!PointerWidth) + return std::unexpected(PointerWidth.error()); + if (*PointerWidth != DestinationType->scalarBitWidth()) + return makeUnexpected(ErrorCode::VerificationError, + "llvm.va_copy pointer type disagrees with the target data layout"); + auto ABIAlignment = Layout->getABITypeAlign(PointerType); + if (!ABIAlignment) + return std::unexpected(ABIAlignment.error()); + auto Size = MemorySize::fixed((static_cast<std::uint64_t>(*PointerWidth) + 7U) / 8U); + auto Alignment = rcc::Alignment::create(*ABIAlignment); + if (!Size || !Alignment) + return makeUnexpected(ErrorCode::InternalError, + "llvm.va_copy pointer type has an invalid memory layout"); + + MachineMemOperandDesc LoadDescription; + LoadDescription.Accesses = {MemoryAccessFlag::Load}; + LoadDescription.Size = *Size; + LoadDescription.MemoryType = *DestinationType; + LoadDescription.Align = *Alignment; + auto LoadMemory = Function.createMemOperand(std::move(LoadDescription)); + if (!LoadMemory) + return std::unexpected(LoadMemory.error()); + + MachineMemOperandDesc StoreDescription; + StoreDescription.Accesses = {MemoryAccessFlag::Store}; + StoreDescription.Size = *Size; + StoreDescription.MemoryType = *DestinationType; + StoreDescription.Align = *Alignment; + auto StoreMemory = Function.createMemOperand(std::move(StoreDescription)); + if (!StoreMemory) + return std::unexpected(StoreMemory.error()); + + auto Temporary = Function.registerInfo().createVirtualRegister(*DestinationType); + if (!Temporary) + return std::unexpected(Temporary.error()); + + if (auto Point = Helper.MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto LoadBuilder = Helper.MIRBuilder.buildInstr(opcode(GenericOpcode::G_LOAD)); + if (!LoadBuilder) + return std::unexpected(LoadBuilder.error()); + if (auto Added = LoadBuilder->addDef(*Temporary); !Added) + return std::unexpected(Added.error()); + if (auto Added = LoadBuilder->addUse(*Source); !Added) + return std::unexpected(Added.error()); + if (auto Added = LoadBuilder->addMemOperand(**LoadMemory); !Added) + return std::unexpected(Added.error()); + if (auto Built = LoadBuilder->build(); !Built) + return std::unexpected(Built.error()); + + auto StoreBuilder = Helper.MIRBuilder.buildInstr(opcode(GenericOpcode::G_STORE)); + if (!StoreBuilder) + return std::unexpected(StoreBuilder.error()); + if (auto Added = StoreBuilder->addUse(*Temporary); !Added) + return std::unexpected(Added.error()); + if (auto Added = StoreBuilder->addUse(*Destination); !Added) + return std::unexpected(Added.error()); + if (auto Added = StoreBuilder->addMemOperand(**StoreMemory); !Added) + return std::unexpected(Added.error()); + if (auto Built = StoreBuilder->build(); !Built) + return std::unexpected(Built.error()); + + if (auto Removed = Instruction.eraseFromParent(); !Removed) + return std::unexpected(Removed.error()); + return true; +} + +bool RISCVLegalizerInfo::isSExtCheaperThanZExt(LowLevelType SourceType, + LowLevelType DestinationType) const { + return SourceType.isCategoryNeutralScalar() && DestinationType.isCategoryNeutralScalar() && + SourceType.scalarBitWidth() == 32 && DestinationType.scalarBitWidth() == 64; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h b/backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h new file mode 100644 index 0000000..36d7486 --- /dev/null +++ b/backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_LIB_TARGET_RISCV_GISEL_RISCVLEGALIZERINFO_H +#define RCC_LIB_TARGET_RISCV_GISEL_RISCVLEGALIZERINFO_H + +#include "rcc/CodeGen/GlobalISel/LegalizerInfo.h" + +namespace rcc { +class MachineIRBuilder; +} // namespace rcc + +namespace rcc::riscv { + +class RISCVSubtarget; + +class RISCVLegalizerInfo final : public LegalizerInfo { +public: + explicit RISCVLegalizerInfo(const RISCVSubtarget &Subtarget); + [[nodiscard]] Expected<void> legalizeCustom(LegalizerHelper &Helper, MachineInstr &Instruction, + const LegalizationDecision &Decision) const override; + [[nodiscard]] Expected<bool> legalizeIntrinsic(LegalizerHelper &Helper, + MachineInstr &Instruction) const override; + [[nodiscard]] bool isSExtCheaperThanZExt(LowLevelType SourceType, + LowLevelType DestinationType) const override; + +private: + [[nodiscard]] Expected<void> legalizeBRJT(MachineInstr &Instruction, + MachineIRBuilder &MIRBuilder) const; + [[nodiscard]] Expected<void> legalizeVAStart(MachineInstr &Instruction, + MachineIRBuilder &MIRBuilder) const; + + const RISCVSubtarget &Subtarget; +}; + +} // namespace rcc::riscv + +#endif // RCC_LIB_TARGET_RISCV_GISEL_RISCVLEGALIZERINFO_H diff --git a/backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.cpp b/backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.cpp new file mode 100644 index 0000000..c299ccb --- /dev/null +++ b/backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.cpp @@ -0,0 +1,388 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCVRegisterBankInfo.h" + +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" + +#include <cstdint> +#include <optional> +#include <ranges> +#include <string_view> +#include <unordered_set> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] bool isScalarGPRType(LowLevelType Type) noexcept { + return (Type.isPointer() && Type.addressSpace() == 0 && Type.scalarBitWidth() == 64) || + (Type.isCategoryNeutralScalar() && Type.scalarBitWidth() >= 1 && + Type.scalarBitWidth() <= 64); +} + +[[nodiscard]] constexpr RegisterBankID gprBank() noexcept { + return RegisterBankID::fromValue(static_cast<std::uint32_t>(RegisterBank::GPR)); +} + +[[nodiscard]] constexpr RegisterBankID fprBank() noexcept { + return RegisterBankID::fromValue(static_cast<std::uint32_t>(RegisterBank::FPR)); +} + +[[nodiscard]] std::optional<std::size_t> operandIndex(const MachineInstr &Instruction, + const MachineOperand &Operand) noexcept { + for (std::size_t Index = 0; Index < Instruction.operandCount(); ++Index) { + if (Instruction.operandAt(Index) == &Operand) + return Index; + } + return std::nullopt; +} + +[[nodiscard]] std::optional<RegisterBankID> +physicalRegisterBank(Register Reg, const TargetDescription &Target) noexcept { + const std::optional<PhysicalRegister> 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]] bool isFloatingGenericOpcode(GenericOpcode OpcodeValue) noexcept { + switch (OpcodeValue) { + case GenericOpcode::G_FRAME_INDEX: + case GenericOpcode::G_FREEZE: + case GenericOpcode::G_FENCE: + case GenericOpcode::G_FSHL: + case GenericOpcode::G_FSHR: + return false; + default: + return getGenericOpcodeName(OpcodeValue).starts_with("G_F"); + } +} + +[[nodiscard]] std::optional<RegisterBankID> +requiredBankForOperand(const MachineInstr &Instruction, const MachineOperand &Operand, + const TargetDescription &Target) { + const std::optional<std::size_t> Index = operandIndex(Instruction, Operand); + if (!Index) + return std::nullopt; + + if (const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction.opcode()); + Descriptor != nullptr && *Index < Descriptor->Operands.size() && + Descriptor->Operands[*Index].RegBank) { + return Descriptor->Operands[*Index].RegBank; + } + + if (Instruction.opcode() == opcode(GenericOpcode::COPY)) { + if (!Operand.isDef()) + return std::nullopt; + for (const MachineOperand &Other : Instruction.operands()) { + if (&Other == &Operand) + continue; + const Register *OtherReg = Other.getIf<Register>(); + if (OtherReg != nullptr && Other.isUse() && OtherReg->isPhysical()) + return physicalRegisterBank(*OtherReg, Target); + } + return std::nullopt; + } + + if (!isGenericOpcode(Instruction.opcode())) + return std::nullopt; + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + switch (Generic) { + case GenericOpcode::G_FPEXTLOAD: + case GenericOpcode::G_FPTRUNCSTORE: + return *Index == 0 ? fprBank() : gprBank(); + case GenericOpcode::G_FPTOSI: + case GenericOpcode::G_FPTOUI: + case GenericOpcode::G_FPTOSI_SAT: + case GenericOpcode::G_FPTOUI_SAT: + case GenericOpcode::G_LROUND: + case GenericOpcode::G_LLROUND: + return *Index == 0 ? gprBank() : fprBank(); + case GenericOpcode::G_SITOFP: + case GenericOpcode::G_UITOFP: + return *Index == 0 ? fprBank() : gprBank(); + case GenericOpcode::G_FCMP: + case GenericOpcode::G_IS_FPCLASS: + return *Index == 0 ? gprBank() : fprBank(); + case GenericOpcode::G_FPOWI: + case GenericOpcode::G_FLDEXP: + return *Index < 2 ? fprBank() : gprBank(); + case GenericOpcode::G_FFREXP: + return *Index == 1 ? gprBank() : fprBank(); + case GenericOpcode::G_LOAD: + case GenericOpcode::G_SEXTLOAD: + case GenericOpcode::G_ZEXTLOAD: + return *Index == 1 ? std::optional<RegisterBankID>(gprBank()) : std::nullopt; + case GenericOpcode::G_STORE: + return *Index == 1 ? std::optional<RegisterBankID>(gprBank()) : std::nullopt; + case GenericOpcode::G_SELECT: + return *Index == 1 ? std::optional<RegisterBankID>(gprBank()) : std::nullopt; + case GenericOpcode::G_IMPLICIT_DEF: + case GenericOpcode::G_PHI: + return std::nullopt; + default: + return isFloatingGenericOpcode(Generic) ? fprBank() : gprBank(); + } +} + +void appendConnectedRegisters(const MachineInstr &Instruction, const MachineOperand &Operand, + std::vector<Register> &Pending) { + const std::optional<std::size_t> Index = operandIndex(Instruction, Operand); + if (!Index) + return; + const bool IsPhi = Instruction.opcode() == opcode(GenericOpcode::G_PHI); + if (!IsPhi) + return; + for (std::size_t OtherIndex = 0; OtherIndex < Instruction.operandCount(); ++OtherIndex) { + if (OtherIndex == *Index) + continue; + const MachineOperand *Other = Instruction.operandAt(OtherIndex); + const Register *OtherReg = Other == nullptr ? nullptr : Other->getIf<Register>(); + if (OtherReg != nullptr && OtherReg->isVirtual()) + Pending.push_back(*OtherReg); + } +} + +[[nodiscard]] Expected<std::optional<RegisterBankID>> +requiredBankForRegister(const MachineFunction &Function, Register Reg, + const TargetDescription &Target) { + std::vector<Register> Pending{Reg}; + std::unordered_set<Register> Visited; + std::optional<RegisterBankID> Required; + while (!Pending.empty()) { + const Register Current = Pending.back(); + Pending.pop_back(); + if (!Visited.insert(Current).second) + continue; + for (const MachineOperand &Operand : Function.registerInfo().regOperands(Current)) { + const MachineInstr *Instruction = Operand.parent(); + if (Instruction == nullptr) + continue; + const std::optional<RegisterBankID> OperandBank = + requiredBankForOperand(*Instruction, Operand, Target); + if (OperandBank) { + if (Required && *Required != *OperandBank) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V virtual register has conflicting register-bank uses"); + } + Required = *OperandBank; + } + appendConnectedRegisters(*Instruction, Operand, Pending); + } + } + return Required; +} + +[[nodiscard]] bool hasFloatingCopyUse(const MachineFunction &Function, Register Reg, + const TargetDescription &Target) { + for (const MachineOperand &Operand : Function.registerInfo().regOperands(Reg)) { + const MachineInstr *Instruction = Operand.parent(); + if (!Operand.isUse() || Instruction == nullptr || + Instruction->opcode() != opcode(GenericOpcode::COPY)) + continue; + for (const MachineOperand &Other : Instruction->operands()) { + const Register *OtherReg = Other.getIf<Register>(); + if (!Other.isDef() || OtherReg == nullptr || !OtherReg->isPhysical()) + continue; + if (physicalRegisterBank(*OtherReg, Target) == fprBank()) + return true; + } + } + return false; +} + +[[nodiscard]] Expected<RegisterClassID> requiredRegisterClass(const MachineFunction &Function, + Register Reg, + const TargetDescription &Target) { + if (!Reg.isVirtual()) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V register-class selection requires a virtual register"); + auto Info = Function.registerInfo().virtualRegisterInfo(Reg); + if (!Info) + return std::unexpected(Info.error()); + if (!Info->get().Type) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V selection requires a type for every virtual register"); + } + const VirtualRegisterInfo &RegisterInfo = Info->get(); + const LowLevelType Type = RegisterInfo.Type.value_or(LowLevelType{}); + RegisterClassID RequiredClass; + if (RegisterInfo.Bank && *RegisterInfo.Bank == fprBank()) { + if (Type.scalarBitWidth() != 32 && Type.scalarBitWidth() != 64) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V RV64GC has no floating register class for type '" + + Type.toString() + "'"); + } + RequiredClass = + Type.scalarBitWidth() == 32 + ? RegisterClassID::fromValue(static_cast<std::uint32_t>(RegisterClass::FPR32)) + : RegisterClassID::fromValue(static_cast<std::uint32_t>(RegisterClass::FPR64)); + } else if (isScalarGPRType(Type)) { + RequiredClass = RegisterClassID::fromValue(static_cast<std::uint32_t>(RegisterClass::GPR)); + } else { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V RV64GC has no register class for type '" + Type.toString() + "'"); + } + + if (Target.registers().registerClass(RequiredClass) == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V target description is missing a required register class"); + } + if (RegisterInfo.Bank && !Target.registers().bankCoversClass(*RegisterInfo.Bank, RequiredClass)) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V virtual register bank cannot cover its required class"); + } + if (RegisterInfo.RegClass && + !Target.registers().isSubclass(*RegisterInfo.RegClass, RequiredClass)) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V virtual register has a conflicting register class"); + } + return RequiredClass; +} + +} // namespace + +Expected<RegisterBankSelection> +RISCVRegisterBankInfo::selectBank(const MachineFunction &Function, Register Reg, + const VirtualRegisterInfo &Info, const MachineInstr *, + const TargetDescription &Target) const { + if (!Info.Type) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V register-bank selection requires a virtual-register type"); + } + + auto Required = requiredBankForRegister(Function, Reg, Target); + if (!Required) + return std::unexpected(Required.error()); + const bool IsConstrained = Required->has_value() || Info.Bank.has_value(); + const RegisterBankID Selected = Required->value_or(Info.Bank.value_or(gprBank())); + if (Info.Bank && Required->has_value() && *Info.Bank != Selected) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V virtual register has a conflicting register bank"); + } + return RegisterBankSelection{.Bank = Selected, .IsConstrained = IsConstrained}; +} + +Expected<RegisterBankInstructionMapping> +RISCVRegisterBankInfo::getInstructionMapping(const MachineInstr &Instruction, + const MachineFunction &Function, + const TargetDescription &Target) const { + if (Instruction.opcode() != opcode(GenericOpcode::G_SELECT)) + return RegisterBankInstructionMapping{}; + if (Instruction.operandCount() != 4) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_SELECT register-bank mapping requires four operands"); + } + + RegisterBankInstructionMapping Mapping(Instruction.operandCount()); + Mapping[1] = gprBank(); + unsigned FloatingConstraints = 0; + + const Register *Result = Instruction.operandAt(0)->getIf<Register>(); + if (Result == nullptr || !Result->isVirtual()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_SELECT result must be a virtual register"); + } + auto ResultRequirement = requiredBankForRegister(Function, *Result, Target); + if (!ResultRequirement) + return std::unexpected(ResultRequirement.error()); + if (*ResultRequirement == fprBank() || hasFloatingCopyUse(Function, *Result, Target)) + ++FloatingConstraints; + + for (const std::size_t Index : {std::size_t{2}, std::size_t{3}}) { + const Register *Source = Instruction.operandAt(Index)->getIf<Register>(); + if (Source == nullptr || !Source->isVirtual()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V G_SELECT sources must be virtual registers"); + } + auto SourceInfo = Function.registerInfo().virtualRegisterInfo(*Source); + if (!SourceInfo) + return std::unexpected(SourceInfo.error()); + if (SourceInfo->get().Bank == fprBank()) + ++FloatingConstraints; + } + + const RegisterBankID ValueBank = FloatingConstraints >= 2 ? fprBank() : gprBank(); + Mapping[0] = ValueBank; + Mapping[2] = ValueBank; + Mapping[3] = ValueBank; + return Mapping; +} + +Expected<void> RISCVRegisterBankInfo::constrainSelectedInstruction( + MachineInstr &Instruction, MachineFunction &Function, const TargetDescription &Target) const { + std::vector<std::pair<Register, RegisterClassID>> PlannedClasses; + for (const MachineOperand &Operand : Instruction.operands()) { + const Register *Reg = Operand.getIf<Register>(); + if (Reg == nullptr || !Reg->isVirtual()) + continue; + auto Required = requiredRegisterClass(Function, *Reg, Target); + if (!Required) + return std::unexpected(Required.error()); + if (std::ranges::find_if(PlannedClasses, [Reg](const auto &Entry) { + return Entry.first == *Reg; + }) == PlannedClasses.end()) + PlannedClasses.emplace_back(*Reg, *Required); + } + for (const auto &[Reg, Required] : PlannedClasses) { + auto Info = Function.registerInfo().virtualRegisterInfo(Reg); + if (!Info) + return std::unexpected(Info.error()); + if (Info->get().RegClass) + continue; + auto SetClass = Function.registerInfo().setRegisterClass(Reg, Required); + if (!SetClass) + return std::unexpected(SetClass.error()); + } + return {}; +} + +Expected<void> RISCVRegisterBankInfo::constrainRegister(MachineFunction &Function, Register Reg, + const TargetDescription &Target) const { + auto Info = Function.registerInfo().virtualRegisterInfo(Reg); + if (!Info) + return std::unexpected(Info.error()); + auto Bank = selectBank(Function, Reg, Info->get(), nullptr, Target); + if (!Bank) + return std::unexpected(Bank.error()); + auto RequiredClass = requiredRegisterClass(Function, Reg, Target); + if (!RequiredClass) + return std::unexpected(RequiredClass.error()); + if (!Info->get().Bank) { + auto SetBank = Function.registerInfo().setRegisterBank(Reg, Bank->Bank); + if (!SetBank) + return std::unexpected(SetBank.error()); + } + if (!Info->get().RegClass) { + auto SetClass = Function.registerInfo().setRegisterClass(Reg, *RequiredClass); + if (!SetClass) + return std::unexpected(SetClass.error()); + } + return {}; +} + +Expected<void> RISCVRegisterBankInfo::validateInstruction(const MachineInstr &Instruction, + const MachineFunction &Function, + const TargetDescription &Target) const { + for (const MachineOperand &Operand : Instruction.operands()) { + const Register *Reg = Operand.getIf<Register>(); + if (Reg == nullptr || !Reg->isVirtual()) + continue; + auto Required = requiredRegisterClass(Function, *Reg, Target); + if (!Required) + return std::unexpected(Required.error()); + } + return {}; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.h b/backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.h new file mode 100644 index 0000000..69eb539 --- /dev/null +++ b/backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_LIB_TARGET_RISCV_GISEL_RISCVREGISTERBANKINFO_H +#define RCC_LIB_TARGET_RISCV_GISEL_RISCVREGISTERBANKINFO_H + +#include "rcc/CodeGen/RegisterBankInfo.h" + +namespace rcc::riscv { + +class RISCVRegisterBankInfo final : public RegisterBankInfo { +public: + [[nodiscard]] Expected<RegisterBankSelection> + selectBank(const MachineFunction &Function, Register Reg, const VirtualRegisterInfo &Info, + const MachineInstr *Instruction, const TargetDescription &Target) const override; + [[nodiscard]] Expected<RegisterBankInstructionMapping> + getInstructionMapping(const MachineInstr &Instruction, const MachineFunction &Function, + const TargetDescription &Target) const override; + [[nodiscard]] Expected<void> constrainSelectedInstruction(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target) const; + [[nodiscard]] Expected<void> constrainRegister(MachineFunction &Function, Register Reg, + const TargetDescription &Target) const; + [[nodiscard]] Expected<void> validateInstruction(const MachineInstr &Instruction, + const MachineFunction &Function, + const TargetDescription &Target) const override; +}; + +} // namespace rcc::riscv + +#endif // RCC_LIB_TARGET_RISCV_GISEL_RISCVREGISTERBANKINFO_H |
