diff options
Diffstat (limited to 'backend/lib/Target')
70 files changed, 26044 insertions, 0 deletions
diff --git a/backend/lib/Target/CMakeLists.txt b/backend/lib/Target/CMakeLists.txt new file mode 100644 index 0000000..f0c3ff7 --- /dev/null +++ b/backend/lib/Target/CMakeLists.txt @@ -0,0 +1,38 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +add_library(RCCBackendTarget TargetMachine.cpp) +add_library(RCCBackend::Target ALIAS RCCBackendTarget) + +rcc_backend_configure_target(RCCBackendTarget) + +target_include_directories( + RCCBackendTarget + PUBLIC + "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/include>" + "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" +) +target_link_libraries(RCCBackendTarget PUBLIC RCCBackend::CodeGen) + +set_target_properties( + RCCBackendTarget + PROPERTIES + EXPORT_NAME Target + OUTPUT_NAME RCCBackendTarget +) + +install( + TARGETS RCCBackendTarget + EXPORT RCCBackendTargets + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) + +rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/TargetMachine.cpp" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/TargetMachine.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/TargetOptions.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Support/TargetSelect.h" +) + +add_subdirectory(RISCV) diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt new file mode 100644 index 0000000..dd11f3d --- /dev/null +++ b/backend/lib/Target/RISCV/CMakeLists.txt @@ -0,0 +1,258 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +set( + RCCBackendRISCVDescriptionSources + "${CMAKE_CURRENT_SOURCE_DIR}/RISCV.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterInfo.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallingConv.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoBase.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoI.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoM.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoA.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoF.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoD.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoC.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoZcmp.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoZb.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfoPseudo.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVGISelInteger.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVGISelAddressing.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVGISelControlFlow.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVGISelShift.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVGISelFloating.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVGISelAtomic.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVGISelCombiner.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMacroFusion.rccdesc" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCPseudoLowering.rccdesc" +) +set(RCCBackendRISCVTableGenArguments) +foreach(RCCBackendRISCVDescriptionSource IN LISTS RCCBackendRISCVDescriptionSources) + list(APPEND RCCBackendRISCVTableGenArguments --input "${RCCBackendRISCVDescriptionSource}") +endforeach() +set( + RCCBackendRISCVGeneratedRegisterHeader + "${RCC_BACKEND_BINARY_DIR}/generated/include/rcc/Target/RISCV/RISCVRegisters.h" +) +set( + RCCBackendRISCVGeneratedInstructionHeader + "${RCC_BACKEND_BINARY_DIR}/generated/include/rcc/Target/RISCV/RISCVInstructions.h" +) +set( + RCCBackendRISCVGeneratedTables + "${RCC_BACKEND_BINARY_DIR}/generated/lib/Target/RISCV/RISCVGenTargetDescription.inc" +) +set( + RCCBackendRISCVGeneratedGlobalISel + "${RCC_BACKEND_BINARY_DIR}/generated/lib/Target/RISCV/RISCVGenGlobalISel.inc" +) +set( + RCCBackendRISCVGeneratedGlobalISelCombiner + "${RCC_BACKEND_BINARY_DIR}/generated/lib/Target/RISCV/RISCVGenGlobalISelCombiner.inc" +) +set( + RCCBackendRISCVGeneratedMacroFusion + "${RCC_BACKEND_BINARY_DIR}/generated/lib/Target/RISCV/RISCVGenMacroFusion.inc" +) +set( + RCCBackendRISCVGeneratedMCPseudoLowering + "${RCC_BACKEND_BINARY_DIR}/generated/lib/Target/RISCV/RISCVGenMCPseudoLowering.inc" +) + +add_custom_command( + OUTPUT + "${RCCBackendRISCVGeneratedRegisterHeader}" + "${RCCBackendRISCVGeneratedInstructionHeader}" + "${RCCBackendRISCVGeneratedTables}" + "${RCCBackendRISCVGeneratedGlobalISel}" + "${RCCBackendRISCVGeneratedGlobalISelCombiner}" + "${RCCBackendRISCVGeneratedMacroFusion}" + "${RCCBackendRISCVGeneratedMCPseudoLowering}" + COMMAND + "$<TARGET_FILE:RCCBackendTableGenTool>" + ${RCCBackendRISCVTableGenArguments} + --register-header "${RCCBackendRISCVGeneratedRegisterHeader}" + --instruction-header "${RCCBackendRISCVGeneratedInstructionHeader}" + --tables "${RCCBackendRISCVGeneratedTables}" + --global-isel "${RCCBackendRISCVGeneratedGlobalISel}" + --global-isel-combiner "${RCCBackendRISCVGeneratedGlobalISelCombiner}" + --macro-fusion "${RCCBackendRISCVGeneratedMacroFusion}" + --mc-pseudo-lowering "${RCCBackendRISCVGeneratedMCPseudoLowering}" + DEPENDS RCCBackendTableGenTool ${RCCBackendRISCVDescriptionSources} + COMMENT "Generating RCC RV64GC target tables" + VERBATIM +) + +add_custom_target( + RCCBackendRISCVGeneratedTablesTarget + DEPENDS + "${RCCBackendRISCVGeneratedRegisterHeader}" + "${RCCBackendRISCVGeneratedInstructionHeader}" + "${RCCBackendRISCVGeneratedTables}" + "${RCCBackendRISCVGeneratedGlobalISel}" + "${RCCBackendRISCVGeneratedGlobalISelCombiner}" + "${RCCBackendRISCVGeneratedMacroFusion}" + "${RCCBackendRISCVGeneratedMCPseudoLowering}" +) + +set_source_files_properties( + "${RCCBackendRISCVGeneratedRegisterHeader}" + "${RCCBackendRISCVGeneratedInstructionHeader}" + "${RCCBackendRISCVGeneratedTables}" + "${RCCBackendRISCVGeneratedGlobalISel}" + "${RCCBackendRISCVGeneratedGlobalISelCombiner}" + "${RCCBackendRISCVGeneratedMacroFusion}" + "${RCCBackendRISCVGeneratedMCPseudoLowering}" + PROPERTIES GENERATED TRUE HEADER_FILE_ONLY TRUE +) + +add_library( + RCCBackendRISCV + GISel/RISCVCallLowering.cpp + GISel/RISCVInstructionSelector.cpp + GISel/RISCVGISelCombiner.cpp + GISel/RISCVLegalizerInfo.cpp + GISel/RISCVRegisterBankInfo.cpp + RISCVInstrInfo.cpp + RISCVAsmPrinter.cpp + RISCVAssemblyPrinter.cpp + RISCVFrameLowering.cpp + RISCVFinalizeISel.cpp + RISCVFunctionInfo.cpp + RISCVImmediateMaterialization.cpp + RISCVInstPrinter.cpp + RISCVAsmBackend.cpp + RISCVCallingConv.cpp + RISCVMCCodeEmitter.cpp + RISCVObjectFile.cpp + RISCVMCTargetLowering.cpp + RISCVMachineCombiner.cpp + RISCVMachineMemoryFolding.cpp + RISCVMakeCompressible.cpp + RISCVMergeBaseOffset.cpp + RISCVMoveMerger.cpp + RISCVOptWInstrs.cpp + RISCVOptimizationPipeline.cpp + RISCVOutliner.cpp + RISCVPipeline.cpp + RISCVPseudoExpansion.cpp + RISCVPushPopOptimizer.cpp + RISCVRegisterAllocation.cpp + RISCVScheduling.cpp + RISCVSubtarget.cpp + RISCVTarget.cpp + RISCVTargetMachine.cpp + RISCVTargetConstant.cpp + RISCVTargetObjectFile.cpp + "${RCCBackendRISCVGeneratedRegisterHeader}" + "${RCCBackendRISCVGeneratedInstructionHeader}" + "${RCCBackendRISCVGeneratedTables}" + "${RCCBackendRISCVGeneratedGlobalISel}" + "${RCCBackendRISCVGeneratedGlobalISelCombiner}" + "${RCCBackendRISCVGeneratedMacroFusion}" + "${RCCBackendRISCVGeneratedMCPseudoLowering}" +) +add_library(RCCBackend::RISCV ALIAS RCCBackendRISCV) +add_dependencies(RCCBackendRISCV RCCBackendRISCVGeneratedTablesTarget) + +rcc_backend_configure_target(RCCBackendRISCV) + +target_include_directories( + RCCBackendRISCV + PUBLIC + "$<BUILD_INTERFACE:${RCC_BACKEND_SOURCE_DIR}/include>" + "$<BUILD_INTERFACE:${RCC_BACKEND_BINARY_DIR}/generated/include>" + "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>" + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" + "${RCC_BACKEND_BINARY_DIR}/generated/lib/Target/RISCV" +) +target_link_libraries( + RCCBackendRISCV + PUBLIC + RCCBackend::AsmPrinter + RCCBackend::CodeGen + RCCBackend::MC + RCCBackend::Target +) + +set_target_properties( + RCCBackendRISCV + PROPERTIES + EXPORT_NAME RISCV + OUTPUT_NAME RCCBackendRISCV +) + +install( + TARGETS RCCBackendRISCV + EXPORT RCCBackendTargets + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" +) +install( + FILES + "${RCCBackendRISCVGeneratedRegisterHeader}" + "${RCCBackendRISCVGeneratedInstructionHeader}" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/rcc/Target/RISCV" +) + +rcc_backend_register_format_files( + "${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVCallLowering.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVCallLowering.h" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallingConv.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallingConv.h" + "${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVInstructionSelector.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVGISelCombiner.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVLegalizerInfo.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVLegalizerInfo.h" + "${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVRegisterBankInfo.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/GISel/RISCVRegisterBankInfo.h" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCV.h" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfo.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAsmPrinter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAsmPrinter.h" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAssemblyPrinter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLowering.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVFinalizeISel.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVFunctionInfo.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVImmediateMaterialization.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVImmediateMaterialization.h" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstPrinter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVAsmBackend.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCCodeEmitter.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVObjectFile.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCTargetLowering.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineCombiner.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMachineMemoryFolding.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMakeCompressible.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMergeBaseOffset.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVMoveMerger.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVOptWInstrs.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVOptimizationPipeline.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVOutliner.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVPipeline.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVPseudoExpansion.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVPushPopOptimizer.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVRegisterAllocation.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVScheduling.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVSubtarget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTarget.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetMachine.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetConstant.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetObjectFile.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVTargetObjectFile.h" + "${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstrInfo.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVAssemblyPrinter.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVInstPrinter.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVAsmBackend.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVFixupKinds.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVFunctionInfo.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVMCCodeEmitter.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVObjectFile.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVPipeline.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVSubtarget.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVMCTargetLowering.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTarget.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTargetConstant.h" + "${RCC_BACKEND_SOURCE_DIR}/include/rcc/Target/RISCV/RISCVTargetRelocationKinds.h" +) 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 diff --git a/backend/lib/Target/RISCV/RISCV.h b/backend/lib/Target/RISCV/RISCV.h new file mode 100644 index 0000000..0b4139a --- /dev/null +++ b/backend/lib/Target/RISCV/RISCV.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_LIB_TARGET_RISCV_RISCV_H +#define RCC_LIB_TARGET_RISCV_RISCV_H + +#include "rcc/CodeGen/FinalizeISel.h" +#include "rcc/CodeGen/GlobalISel/GIMatchTableExecutor.h" +#include "rcc/CodeGen/MachineCombinerPattern.h" +#include "rcc/CodeGen/MachineOptimizationPipeline.h" +#include "rcc/CodeGen/TargetFrameLowering.h" +#include "rcc/CodeGen/TargetMemoryFolding.h" +#include "rcc/CodeGen/TargetPseudoExpansion.h" +#include "rcc/CodeGen/TargetRegisterAllocationInfo.h" +#include "rcc/CodeGen/TargetSchedulingModel.h" +#include "rcc/Target/RISCV/RISCVSubtarget.h" + +namespace rcc { + +class InstructionSelector; +class MachineFunctionPass; + +namespace riscv { + +class RISCVRegisterBankInfo; +class RISCVSubtarget; + +[[nodiscard]] InstructionSelector * +createRISCVInstructionSelector(const RISCVSubtarget &Subtarget, + const RISCVRegisterBankInfo &RegisterBanks); +[[nodiscard]] const TargetRegisterAllocationInfo ®isterAllocationInfo() noexcept; +[[nodiscard]] std::unique_ptr<TargetFrameLowering> +createFrameLowering(const RISCVSubtarget &Subtarget); +[[nodiscard]] std::unique_ptr<TargetSchedulingHooks> +createSchedulingHooks(const RISCVSubtargetFeatures &Features); +[[nodiscard]] Expected<TargetSchedulingModel> +createSchedulingModel(const TargetSchedulingHooks &Hooks); +[[nodiscard]] const TargetMachineCombinerInfo &machineCombinerInfo() noexcept; +[[nodiscard]] const GIMatchTable &giselCombinerTable() noexcept; +[[nodiscard]] const GIMatchTableExecutor &giselCombinerExecutor() noexcept; +[[nodiscard]] const TargetMemoryFoldingInfo &memoryFoldingInfo() noexcept; +[[nodiscard]] const TargetPseudoExpansionInfo &pseudoExpansionInfo() noexcept; +[[nodiscard]] const TargetFinalizeISelInfo &finalizeISelInfo() noexcept; +[[nodiscard]] std::unique_ptr<MachineFunctionPass> +createMoveMergePass(const RISCVSubtarget &Subtarget); +[[nodiscard]] std::unique_ptr<MachineFunctionPass> +createMakeCompressiblePass(const RISCVSubtarget &Subtarget); +[[nodiscard]] std::unique_ptr<MachineFunctionPass> +createPushPopOptimizationPass(const RISCVSubtarget &Subtarget); +[[nodiscard]] std::unique_ptr<MachineFunctionPass> +createOptWInstrsPass(const RISCVSubtarget &Subtarget); +[[nodiscard]] std::unique_ptr<MachineFunctionPass> createMergeBaseOffsetPass(); +[[nodiscard]] MachineOptimizationPipelineTargetInfo +optimizationPipelineInfo(const RISCVSubtarget &Subtarget, bool EmitSchedulerDebugOutput = false, + bool EmitFrameDebugOutput = false) noexcept; + +} // namespace riscv +} // namespace rcc + +#endif // RCC_LIB_TARGET_RISCV_RISCV_H diff --git a/backend/lib/Target/RISCV/RISCV.rccdesc b/backend/lib/Target/RISCV/RISCV.rccdesc new file mode 100644 index 0000000..7623afd --- /dev/null +++ b/backend/lib/Target/RISCV/RISCV.rccdesc @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +schema: rcc-target-description-v2 +target: + namespace: riscv + name: RISCV + triple: riscv64-unknown-linux-gnu + cpu: generic-rv64gc + abi: lp64d + data_layout: e-m:e-p:64:64-i64:64-i128:128-n32:64-S128 + features: [i, m, a, f, d, c, zicsr, zifencei] diff --git a/backend/lib/Target/RISCV/RISCVAsmBackend.cpp b/backend/lib/Target/RISCV/RISCVAsmBackend.cpp new file mode 100644 index 0000000..e118d79 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVAsmBackend.cpp @@ -0,0 +1,405 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVAsmBackend.h" + +#include "rcc/Target/RISCV/RISCVFixupKinds.h" +#include "rcc/Target/RISCV/RISCVInstPrinter.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" + +#include <cstddef> +#include <cstdint> +#include <limits> +#include <string> + +namespace rcc::riscv { +namespace { + +enum class RISCVRelocation : std::uint32_t { + Relocation32 = 1, + Relocation64 = 2, + Branch = 16, + JAL = 17, + CallPLT = 19, + GOTHigh20 = 20, + PCRelativeHigh20 = 23, + PCRelativeLow12I = 24, + PCRelativeLow12S = 25, + High20 = 26, + Low12I = 27, + Low12S = 28, + Add8 = 33, + Add16 = 34, + Add32 = 35, + Add64 = 36, + Subtract8 = 37, + Subtract16 = 38, + Subtract32 = 39, + Subtract64 = 40, + RVCBranch = 44, + RVCJump = 45, + PCRelative32 = 57, +}; + +[[nodiscard]] bool isTargetKind(mc::MCFixupKind Kind, RISCVFixupKind TargetKind) noexcept { + return Kind == fixupKind(TargetKind); +} + +[[nodiscard]] Expected<void> requireStorage(std::span<std::uint8_t> Data, std::uint64_t Offset, + std::uint8_t Size) { + if (Offset > Data.size() || Size > Data.size() - static_cast<std::size_t>(Offset)) { + return makeUnexpected(ErrorCode::FixupApplicationFailed, + "RISC-V fixup exceeds its encoded fragment"); + } + return {}; +} + +void writeLittleEndian(std::span<std::uint8_t> Data, std::uint64_t Offset, std::uint64_t Value, + std::uint8_t Size) noexcept { + for (std::uint8_t Index = 0; Index < Size; ++Index) + Data[static_cast<std::size_t>(Offset) + Index] = + static_cast<std::uint8_t>(Value >> (Index * 8U)); +} + +[[nodiscard]] std::uint32_t readWord(std::span<const std::uint8_t> Data, + std::uint64_t Offset) noexcept { + std::uint32_t Result = 0; + for (std::uint8_t Index = 0; Index < 4; ++Index) + Result |= static_cast<std::uint32_t>(Data[static_cast<std::size_t>(Offset) + Index]) + << (Index * 8U); + return Result; +} + +[[nodiscard]] std::uint16_t readHalf(std::span<const std::uint8_t> Data, + std::uint64_t Offset) noexcept { + return static_cast<std::uint16_t>(Data[static_cast<std::size_t>(Offset)]) | + static_cast<std::uint16_t>(Data[static_cast<std::size_t>(Offset) + 1U] << 8U); +} + +[[nodiscard]] bool fitsSigned(std::int64_t Value, unsigned Bits) noexcept { + const std::int64_t Minimum = -(std::int64_t{1} << (Bits - 1U)); + const std::int64_t Maximum = (std::int64_t{1} << (Bits - 1U)) - 1; + return Value >= Minimum && Value <= Maximum; +} + +[[nodiscard]] Expected<void> requireDisplacement(std::int64_t Value, unsigned Bits, + std::string_view Name) { + if ((Value & 1) != 0 || !fitsSigned(Value, Bits)) { + return makeUnexpected(ErrorCode::FixupApplicationFailed, + std::string(Name) + " displacement is out of range or unaligned"); + } + return {}; +} + +[[nodiscard]] std::uint32_t encodeBranchImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint32_t>(Value); + return ((Bits >> 12U) & 0x1U) << 31U | ((Bits >> 5U) & 0x3fU) << 25U | + ((Bits >> 1U) & 0xfU) << 8U | ((Bits >> 11U) & 0x1U) << 7U; +} + +[[nodiscard]] std::uint32_t encodeJALImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint32_t>(Value); + return ((Bits >> 20U) & 0x1U) << 31U | ((Bits >> 1U) & 0x3ffU) << 21U | + ((Bits >> 11U) & 0x1U) << 20U | ((Bits >> 12U) & 0xffU) << 12U; +} + +[[nodiscard]] std::uint16_t encodeRVCJumpImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint16_t>(Value); + return static_cast<std::uint16_t>(((Bits >> 11U) & 1U) << 12U | ((Bits >> 4U) & 1U) << 11U | + ((Bits >> 8U) & 3U) << 9U | ((Bits >> 10U) & 1U) << 8U | + ((Bits >> 6U) & 1U) << 7U | ((Bits >> 7U) & 1U) << 6U | + ((Bits >> 1U) & 7U) << 3U | ((Bits >> 5U) & 1U) << 2U); +} + +[[nodiscard]] std::uint16_t encodeRVCBranchImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint16_t>(Value); + return static_cast<std::uint16_t>(((Bits >> 8U) & 1U) << 12U | ((Bits >> 3U) & 3U) << 10U | + ((Bits >> 6U) & 3U) << 5U | ((Bits >> 1U) & 3U) << 3U | + ((Bits >> 5U) & 1U) << 2U); +} + +[[nodiscard]] Expected<void> requireRelocationTargetKind(const mc::MCValue &Value, + std::uint32_t Expected, + std::string_view Name) { + if (Value.TargetKind != Expected) { + return makeUnexpected(ErrorCode::RelocationFailed, + std::string(Name) + " has an incompatible target expression"); + } + return {}; +} + +} // namespace + +Expected<mc::MCFixupKindInfo> RISCVAsmBackend::fixupKindInfo(mc::MCFixupKind Kind) const { + switch (Kind) { + case mc::fixup_kind::Data1: + return mc::MCFixupKindInfo{.StorageSize = 1, .PCRelative = false}; + case mc::fixup_kind::Data2: + return mc::MCFixupKindInfo{.StorageSize = 2, .PCRelative = false}; + case mc::fixup_kind::Data4: + return mc::MCFixupKindInfo{.StorageSize = 4, .PCRelative = false}; + case mc::fixup_kind::Data8: + return mc::MCFixupKindInfo{.StorageSize = 8, .PCRelative = false}; + case mc::fixup_kind::PCRelative4: + return mc::MCFixupKindInfo{.StorageSize = 4, .PCRelative = true}; + default: + break; + } + if (isTargetKind(Kind, RISCVFixupKind::RVCJump) || + isTargetKind(Kind, RISCVFixupKind::RVCBranch)) { + return mc::MCFixupKindInfo{.StorageSize = 2, .PCRelative = true}; + } + if (Kind >= fixupKind(RISCVFixupKind::High20) && Kind <= fixupKind(RISCVFixupKind::CallPLT)) { + const bool PCRelative = Kind >= fixupKind(RISCVFixupKind::PCRelativeHigh20); + return mc::MCFixupKindInfo{.StorageSize = 4, .PCRelative = PCRelative}; + } + return makeUnexpected(ErrorCode::InvalidArgument, "unknown RISC-V fixup kind"); +} + +Expected<void> RISCVAsmBackend::applyFixup(mc::MCFixupKind Kind, std::span<std::uint8_t> Data, + std::uint64_t Offset, std::int64_t Value) const { + auto Info = fixupKindInfo(Kind); + if (!Info) + return std::unexpected(Info.error()); + auto Storage = requireStorage(Data, Offset, Info->StorageSize); + if (!Storage) + return std::unexpected(Storage.error()); + + if (Kind == mc::fixup_kind::Data1 || Kind == mc::fixup_kind::Data2 || + Kind == mc::fixup_kind::Data4 || Kind == mc::fixup_kind::Data8 || + Kind == mc::fixup_kind::PCRelative4) { + writeLittleEndian(Data, Offset, static_cast<std::uint64_t>(Value), Info->StorageSize); + return {}; + } + + if (Info->StorageSize == 2) { + std::uint16_t Instruction = readHalf(Data, Offset); + if (isTargetKind(Kind, RISCVFixupKind::RVCJump)) { + auto Range = requireDisplacement(Value, 12, "RISC-V compressed jump"); + if (!Range) + return std::unexpected(Range.error()); + Instruction = static_cast<std::uint16_t>((Instruction & ~std::uint16_t{0x1ffc}) | + encodeRVCJumpImmediate(Value)); + } else { + auto Range = requireDisplacement(Value, 9, "RISC-V compressed branch"); + if (!Range) + return std::unexpected(Range.error()); + Instruction = static_cast<std::uint16_t>((Instruction & ~std::uint16_t{0x1c7c}) | + encodeRVCBranchImmediate(Value)); + } + writeLittleEndian(Data, Offset, Instruction, 2); + return {}; + } + + std::uint32_t Instruction = readWord(Data, Offset); + if (isTargetKind(Kind, RISCVFixupKind::High20) || + isTargetKind(Kind, RISCVFixupKind::PCRelativeHigh20) || + isTargetKind(Kind, RISCVFixupKind::GOTPCRelativeHigh20) || + isTargetKind(Kind, RISCVFixupKind::CallPLT)) { + const std::uint64_t Adjusted = static_cast<std::uint64_t>(Value) + 0x800U; + const std::uint32_t High = static_cast<std::uint32_t>(Adjusted >> 12U); + Instruction = (Instruction & 0xfffU) | (High << 12U); + } else if (isTargetKind(Kind, RISCVFixupKind::Low12I) || + isTargetKind(Kind, RISCVFixupKind::PCRelativeLow12I)) { + Instruction = (Instruction & 0x000fffffU) | (static_cast<std::uint32_t>(Value) & 0xfffU) << 20U; + } else if (isTargetKind(Kind, RISCVFixupKind::Low12S) || + isTargetKind(Kind, RISCVFixupKind::PCRelativeLow12S)) { + const std::uint32_t Low = static_cast<std::uint32_t>(Value) & 0xfffU; + Instruction = (Instruction & ~std::uint32_t{0xfe000f80}) | ((Low >> 5U) & 0x7fU) << 25U | + (Low & 0x1fU) << 7U; + } else if (isTargetKind(Kind, RISCVFixupKind::JAL)) { + auto Range = requireDisplacement(Value, 21, "RISC-V JAL"); + if (!Range) + return std::unexpected(Range.error()); + Instruction = (Instruction & 0xfffU) | encodeJALImmediate(Value); + } else if (isTargetKind(Kind, RISCVFixupKind::Branch)) { + auto Range = requireDisplacement(Value, 13, "RISC-V branch"); + if (!Range) + return std::unexpected(Range.error()); + Instruction = (Instruction & ~std::uint32_t{0xfe000f80}) | encodeBranchImmediate(Value); + } else { + return makeUnexpected(ErrorCode::FixupApplicationFailed, + "RISC-V fixup cannot be applied to an instruction"); + } + writeLittleEndian(Data, Offset, Instruction, 4); + return {}; +} + +Expected<std::uint32_t> RISCVAsmBackend::relocationType(const mc::MCFixup &Fixup, + const mc::MCValue &Value) const { + const auto Relocation = [](RISCVRelocation Type) { return static_cast<std::uint32_t>(Type); }; + switch (Fixup.Kind) { + case mc::fixup_kind::Data4: + return Relocation(RISCVRelocation::Relocation32); + case mc::fixup_kind::Data8: + return Relocation(RISCVRelocation::Relocation64); + case mc::fixup_kind::PCRelative4: + return Relocation(RISCVRelocation::PCRelative32); + default: + break; + } + + if (isTargetKind(Fixup.Kind, RISCVFixupKind::PCRelativeHigh20)) { + auto Kind = requireRelocationTargetKind( + Value, static_cast<std::uint32_t>(RISCVMCExprKind::PCRelativeHigh), "R_RISCV_PCREL_HI20"); + if (!Kind) + return std::unexpected(Kind.error()); + return Relocation(RISCVRelocation::PCRelativeHigh20); + } + if (isTargetKind(Fixup.Kind, RISCVFixupKind::GOTPCRelativeHigh20)) { + auto Kind = requireRelocationTargetKind( + Value, static_cast<std::uint32_t>(RISCVMCExprKind::GOTPCRelativeHigh), "R_RISCV_GOT_HI20"); + if (!Kind) + return std::unexpected(Kind.error()); + return Relocation(RISCVRelocation::GOTHigh20); + } + if (isTargetKind(Fixup.Kind, RISCVFixupKind::PCRelativeLow12I) || + isTargetKind(Fixup.Kind, RISCVFixupKind::PCRelativeLow12S)) { + auto Kind = requireRelocationTargetKind( + Value, static_cast<std::uint32_t>(RISCVMCExprKind::PCRelativeLow), "R_RISCV_PCREL_LO12"); + if (!Kind) + return std::unexpected(Kind.error()); + return Relocation(isTargetKind(Fixup.Kind, RISCVFixupKind::PCRelativeLow12I) + ? RISCVRelocation::PCRelativeLow12I + : RISCVRelocation::PCRelativeLow12S); + } + if (isTargetKind(Fixup.Kind, RISCVFixupKind::CallPLT)) { + auto Kind = requireRelocationTargetKind( + Value, static_cast<std::uint32_t>(RISCVMCExprKind::CallPLT), "R_RISCV_CALL_PLT"); + if (!Kind) + return std::unexpected(Kind.error()); + return Relocation(RISCVRelocation::CallPLT); + } + if (isTargetKind(Fixup.Kind, RISCVFixupKind::High20)) { + if (Value.TargetKind != 0) { + auto Kind = requireRelocationTargetKind( + Value, static_cast<std::uint32_t>(RISCVMCExprKind::AbsoluteHigh), "R_RISCV_HI20"); + if (!Kind) + return std::unexpected(Kind.error()); + } + return Relocation(RISCVRelocation::High20); + } + if (isTargetKind(Fixup.Kind, RISCVFixupKind::Low12I) || + isTargetKind(Fixup.Kind, RISCVFixupKind::Low12S)) { + if (Value.TargetKind != 0) { + auto Kind = requireRelocationTargetKind( + Value, static_cast<std::uint32_t>(RISCVMCExprKind::AbsoluteLow), "R_RISCV_LO12"); + if (!Kind) + return std::unexpected(Kind.error()); + } + return Relocation(isTargetKind(Fixup.Kind, RISCVFixupKind::Low12I) ? RISCVRelocation::Low12I + : RISCVRelocation::Low12S); + } + if (Value.TargetKind != 0) { + return makeUnexpected(ErrorCode::RelocationFailed, + "RISC-V relocation has an unsupported target expression"); + } + if (isTargetKind(Fixup.Kind, RISCVFixupKind::JAL)) + return Relocation(RISCVRelocation::JAL); + if (isTargetKind(Fixup.Kind, RISCVFixupKind::Branch)) + return Relocation(RISCVRelocation::Branch); + if (isTargetKind(Fixup.Kind, RISCVFixupKind::RVCJump)) + return Relocation(RISCVRelocation::RVCJump); + if (isTargetKind(Fixup.Kind, RISCVFixupKind::RVCBranch)) + return Relocation(RISCVRelocation::RVCBranch); + return makeUnexpected(ErrorCode::RelocationFailed, "RISC-V fixup has no ELF relocation type"); +} + +Expected<std::vector<mc::MCFixupRelocation>> +RISCVAsmBackend::relocationsForFixup(const mc::MCFixup &Fixup, const mc::MCValue &Value) const { + if (!Value.SubtractSymbol) + return mc::MCAsmBackend::relocationsForFixup(Fixup, Value); + if (!Value.AddSymbol || Value.TargetKind != 0) { + return makeUnexpected(ErrorCode::RelocationFailed, + "RISC-V symbol difference has an invalid relocation expression"); + } + + RISCVRelocation AddType; + RISCVRelocation SubtractType; + switch (Fixup.Kind) { + case mc::fixup_kind::Data1: + AddType = RISCVRelocation::Add8; + SubtractType = RISCVRelocation::Subtract8; + break; + case mc::fixup_kind::Data2: + AddType = RISCVRelocation::Add16; + SubtractType = RISCVRelocation::Subtract16; + break; + case mc::fixup_kind::Data4: + AddType = RISCVRelocation::Add32; + SubtractType = RISCVRelocation::Subtract32; + break; + case mc::fixup_kind::Data8: + AddType = RISCVRelocation::Add64; + SubtractType = RISCVRelocation::Subtract64; + break; + default: + return makeUnexpected(ErrorCode::RelocationFailed, + "RISC-V fixup cannot encode a symbol difference"); + } + return std::vector<mc::MCFixupRelocation>{ + {.Type = static_cast<std::uint32_t>(AddType), + .Symbol = Value.AddSymbol, + .Addend = Value.Addend}, + {.Type = static_cast<std::uint32_t>(SubtractType), + .Symbol = Value.SubtractSymbol, + .Addend = 0}, + }; +} + +bool RISCVAsmBackend::mayNeedRelaxation(const mc::MCInst &Instruction) const noexcept { + const auto Opcode = static_cast<InstructionOpcode>(Instruction.opcode().value()); + return Opcode == InstructionOpcode::C_J || Opcode == InstructionOpcode::C_BEQZ || + Opcode == InstructionOpcode::C_BNEZ; +} + +Expected<bool> RISCVAsmBackend::fixupNeedsRelaxation(const mc::MCFixup &Fixup, + std::int64_t Value) const { + if (isTargetKind(Fixup.Kind, RISCVFixupKind::RVCJump)) + return !fitsSigned(Value, 12) || (Value & 1) != 0; + if (isTargetKind(Fixup.Kind, RISCVFixupKind::RVCBranch)) + return !fitsSigned(Value, 9) || (Value & 1) != 0; + return false; +} + +Expected<mc::MCInst> RISCVAsmBackend::relaxInstruction(const mc::MCInst &Instruction) const { + const auto Opcode = static_cast<InstructionOpcode>(Instruction.opcode().value()); + const bool IsJump = Opcode == InstructionOpcode::C_J; + const bool IsBranch = Opcode == InstructionOpcode::C_BEQZ || Opcode == InstructionOpcode::C_BNEZ; + const std::size_t ExpectedOperands = IsJump ? 1U : 2U; + if ((!IsJump && !IsBranch) || Instruction.operands().size() != ExpectedOperands) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V MC relaxation received an unsupported instruction"); + } + + const mc::MCOperand &Target = Instruction.operands().back(); + if (Target.expressionValue() == nullptr) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V MC relaxation requires a symbolic target"); + } + const InstructionOpcode RelaxedOpcode = + IsJump + ? InstructionOpcode::JAL + : (Opcode == InstructionOpcode::C_BEQZ ? InstructionOpcode::BEQ : InstructionOpcode::BNE); + mc::MCInst Result(mc::MCOpcode::fromValue(static_cast<std::uint32_t>(RelaxedOpcode))); + Result.setFlags(Instruction.flags()); + Result.setSourceIdentity(Instruction.sourceIdentity()); + if (IsJump) { + Result.addOperand(mc::MCOperand::registerOperand( + mc::MCRegister::fromValue(static_cast<std::uint32_t>(PhysicalRegisterID::X0)))); + } else { + Result.addOperand(Instruction.operands().front()); + Result.addOperand(mc::MCOperand::registerOperand( + mc::MCRegister::fromValue(static_cast<std::uint32_t>(PhysicalRegisterID::X0)))); + } + Result.addOperand(Target); + return Result; +} + +std::uint32_t RISCVAsmBackend::objectFlags() const noexcept { + constexpr std::uint32_t RVC = 1; + constexpr std::uint32_t FloatABIDouble = 4; + return RVC | FloatABIDouble; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVAsmPrinter.cpp b/backend/lib/Target/RISCV/RISCVAsmPrinter.cpp new file mode 100644 index 0000000..095151c --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVAsmPrinter.cpp @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCVAsmPrinter.h" + +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" +#include "rcc/CodeGen/MachineMCContext.h" +#include "rcc/CodeGen/TargetConstant.h" +#include "rcc/MC/MCContext.h" +#include "rcc/MC/MCExpr.h" +#include "rcc/MC/MCStreamer.h" +#include "rcc/MC/MCSymbol.h" +#include "rcc/Target/RISCV/RISCVTargetRelocationKinds.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <ranges> +#include <span> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] Expected<std::uint8_t> targetRelocationSize(TargetRelocationKindID Kind) { + if (Kind == targetRelocationKind(RISCVTargetRelocationKind::Absolute32)) + return 4; + if (Kind == targetRelocationKind(RISCVTargetRelocationKind::Absolute64)) + return 8; + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC target constant has an unknown relocation kind"); +} + +[[nodiscard]] Expected<mc::MCExprRef> addOffset(mc::MCExprRef Expression, std::int64_t Offset) { + if (Offset == 0) + return Expression; + auto Result = mc::MCExpr::binary(mc::MCBinaryOperator::Add, std::move(Expression), + mc::MCExpr::constant(Offset)); + if (!Result) + return std::unexpected(Result.error()); + return *Result; +} + +[[nodiscard]] Expected<void> emitBytes(std::span<const std::byte> Bytes, mc::MCStreamer &Streamer) { + if (Bytes.empty()) + return {}; + std::vector<std::uint8_t> Storage; + Storage.reserve(Bytes.size()); + for (const std::byte Byte : Bytes) + Storage.push_back(std::to_integer<std::uint8_t>(Byte)); + return Streamer.emitBytes(Storage); +} + +} // namespace + +Expected<void> RISCVAsmPrinter::emitStartOfAsmFile() { + auto Attribute = streamer().emitDirective("attribute", "arch, \"rv64gc\""); + if (!Attribute) + return std::unexpected(Attribute.error()); + return streamer().emitDirective("attribute", "stack_align, 16"); +} + +Expected<void> RISCVAsmPrinter::emitEndOfAsmFile() { + mc::MCSectionDesc Note{ + .Name = ".note.GNU-stack", + .Kind = mc::MCSectionKind::Custom, + .Flags = {}, + .Alignment = 1, + .EntrySize = 0, + .Group = {}, + .UniqueID = std::nullopt, + }; + auto Section = machineContext().context().getOrCreateSection(std::move(Note)); + if (!Section) + return std::unexpected(Section.error()); + return streamer().switchSection(std::move(*Section)); +} + +Expected<void> RISCVAsmPrinter::emitTargetConstant(const TargetConstant &Constant) { + auto Emission = Constant.emit(); + if (!Emission) + return std::unexpected(Emission.error()); + auto DeclaredKind = Constant.relocationKind(); + if (!DeclaredKind) + return std::unexpected(DeclaredKind.error()); + if (Emission->Fixups.empty() != !DeclaredKind->has_value()) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RV64GC target constant relocation declaration is inconsistent"); + } + if (Emission->Fixups.empty()) + return emitBytes(Emission->Bytes, streamer()); + + std::vector<const TargetConstantFixup *> Fixups; + Fixups.reserve(Emission->Fixups.size()); + for (const TargetConstantFixup &Fixup : Emission->Fixups) { + if (Fixup.Kind != **DeclaredKind) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RV64GC target constant contains mixed relocation kinds"); + } + Fixups.push_back(&Fixup); + } + std::ranges::sort(Fixups, {}, [](const TargetConstantFixup *Fixup) { return Fixup->Offset; }); + + std::uint64_t Cursor = 0; + for (const TargetConstantFixup *Fixup : Fixups) { + auto Size = targetRelocationSize(Fixup->Kind); + if (!Size) + return std::unexpected(Size.error()); + if (Fixup->Offset < Cursor || Fixup->Offset > Emission->Bytes.size() || + *Size > Emission->Bytes.size() - static_cast<std::size_t>(Fixup->Offset)) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RV64GC target constant relocation storage is invalid"); + } + auto RawBytes = emitBytes(std::span<const std::byte>(Emission->Bytes) + .subspan(static_cast<std::size_t>(Cursor), + static_cast<std::size_t>(Fixup->Offset - Cursor)), + streamer()); + if (!RawBytes) + return std::unexpected(RawBytes.error()); + + auto Symbol = machineContext().symbol(Fixup->Symbol); + if (!Symbol) + return std::unexpected(Symbol.error()); + auto Expression = mc::MCExpr::symbol(std::move(*Symbol)); + if (!Expression) + return std::unexpected(Expression.error()); + auto WithAddend = addOffset(std::move(*Expression), Fixup->Addend); + if (!WithAddend) + return std::unexpected(WithAddend.error()); + auto Value = streamer().emitValue(std::move(*WithAddend), *Size); + if (!Value) + return std::unexpected(Value.error()); + Cursor = Fixup->Offset + *Size; + } + return emitBytes( + std::span<const std::byte>(Emission->Bytes).subspan(static_cast<std::size_t>(Cursor)), + streamer()); +} + +Expected<void> RISCVAsmPrinter::emitTargetJumpTable(const MachineFunction &Function, + const MachineJumpTableEntry &Entry) { + const MachineJumpTableDesc *Description = Entry.description(); + if (Description == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, "jump-table entry is not live"); + if (Description->Encoding == JumpTableEntryEncoding::Target) { + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC jump table has an unknown target encoding"); + } + + mc::MCSymbolRef BaseSymbol; + if (Description->Encoding == JumpTableEntryEncoding::GPRelative32BlockAddress || + Description->Encoding == JumpTableEntryEncoding::GPRelative64BlockAddress) { + auto GlobalPointer = machineContext().symbol("__global_pointer$"); + if (!GlobalPointer) + return std::unexpected(GlobalPointer.error()); + BaseSymbol = std::move(*GlobalPointer); + } else if (Description->Encoding == JumpTableEntryEncoding::Custom32) { + BaseSymbol = machineContext().jumpTableSymbol(Function, Entry.index()); + } + + for (const MachineBasicBlock *TargetBlock : Description->Targets) { + if (TargetBlock == nullptr) + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "jump table has a null target"); + const mc::MCSymbolRef TargetSymbol = machineContext().blockSymbol(*TargetBlock); + if (!TargetSymbol) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "jump-table target has no MC symbol"); + } + auto Expression = mc::MCExpr::symbol(TargetSymbol); + if (!Expression) + return std::unexpected(Expression.error()); + if (BaseSymbol) { + auto Base = mc::MCExpr::symbol(BaseSymbol); + if (!Base) + return std::unexpected(Base.error()); + Expression = mc::MCExpr::binary(mc::MCBinaryOperator::Subtract, std::move(*Expression), + std::move(*Base)); + if (!Expression) + return std::unexpected(Expression.error()); + } + auto Value = streamer().emitValue(std::move(*Expression), Description->EntrySize); + if (!Value) + return std::unexpected(Value.error()); + } + return {}; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVAsmPrinter.h b/backend/lib/Target/RISCV/RISCVAsmPrinter.h new file mode 100644 index 0000000..f74e569 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVAsmPrinter.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_RISCV_RISCVASMPRINTER_H +#define RCC_TARGET_RISCV_RISCVASMPRINTER_H + +#include "rcc/CodeGen/AsmPrinter.h" + +namespace rcc::riscv { + +class RISCVAsmPrinter final : public AsmPrinter { +public: + RISCVAsmPrinter(const TargetDescription &Target, const TargetMCInstLowering &Lowering, + const TargetLoweringObjectFile &ObjectFile, DataLayout Layout, + MachineMCContext &Context, mc::MCStreamer &Streamer, + AsmPrinterOptions Options = {}) noexcept + : AsmPrinter(Target, Lowering, ObjectFile, std::move(Layout), Context, Streamer, Options) {} + +private: + [[nodiscard]] Expected<void> emitStartOfAsmFile() override; + [[nodiscard]] Expected<void> emitEndOfAsmFile() override; + [[nodiscard]] Expected<void> emitTargetConstant(const TargetConstant &Constant) override; + [[nodiscard]] Expected<void> emitTargetJumpTable(const MachineFunction &Function, + const MachineJumpTableEntry &Entry) override; +}; + +} // namespace rcc::riscv + +#endif // RCC_TARGET_RISCV_RISCVASMPRINTER_H diff --git a/backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp b/backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp new file mode 100644 index 0000000..85f4617 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVAssemblyPrinter.h" + +#include "RISCVAsmPrinter.h" +#include "RISCVTargetObjectFile.h" +#include "rcc/CodeGen/AsmPrinter.h" +#include "rcc/CodeGen/CompilationUnit.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineMCContext.h" +#include "rcc/IR/DataLayout.h" +#include "rcc/MC/MCContext.h" +#include "rcc/MC/MCStreamer.h" +#include "rcc/Target/RISCV/RISCVMCTargetLowering.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <string> +#include <utility> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] AsmPrinterOptions printerOptions(AssemblyEmissionOptions Options) noexcept { + return AsmPrinterOptions{ + .EmitFunctionDirectives = Options.EmitFunctionDirectives, + .EmitCFI = Options.EmitCFI, + .EmitBlockLabels = Options.EmitBlockLabels, + .EmitInstructionSymbols = true, + .VerifyInput = Options.VerifyInput, + }; +} + +} // namespace + +Expected<std::string> emitAssembly(const CompilationUnit &Unit, const TargetDescription &Target, + AssemblyEmissionOptions Options) { + mc::MCContext MCContext; + MachineMCContext MachineContext(MCContext); + RISCVMCTargetLowering Lowering(Target); + RISCVELFTargetObjectFile ObjectFile; + auto Layout = DataLayout::parse(TargetDataLayout); + if (!Layout) + return std::unexpected(Layout.error()); + mc::AsmTextStreamer Streamer(Lowering.instructionPrinter()); + RISCVAsmPrinter Printer(Target, Lowering, ObjectFile, std::move(*Layout), MachineContext, + Streamer, printerOptions(Options)); + auto Emitted = Printer.emitCompilationUnit(Unit); + if (!Emitted) + return std::unexpected(Emitted.error()); + return Streamer.takeOutput(); +} + +Expected<std::string> emitAssembly(const MachineFunction &Function, const TargetDescription &Target, + AssemblyEmissionOptions Options) { + mc::MCContext MCContext; + MachineMCContext MachineContext(MCContext); + RISCVMCTargetLowering Lowering(Target); + RISCVELFTargetObjectFile ObjectFile; + auto Layout = DataLayout::parse(TargetDataLayout); + if (!Layout) + return std::unexpected(Layout.error()); + mc::AsmTextStreamer Streamer(Lowering.instructionPrinter()); + RISCVAsmPrinter Printer(Target, Lowering, ObjectFile, std::move(*Layout), MachineContext, + Streamer, printerOptions(Options)); + auto Emitted = Printer.emitFunction(Function); + if (!Emitted) + return std::unexpected(Emitted.error()); + return Streamer.takeOutput(); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVCallingConv.cpp b/backend/lib/Target/RISCV/RISCVCallingConv.cpp new file mode 100644 index 0000000..26243f1 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVCallingConv.cpp @@ -0,0 +1,507 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCVCallingConv.h" + +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <algorithm> +#include <cstdint> +#include <limits> +#include <optional> +#include <ranges> + +namespace rcc::riscv { +namespace { + +[[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; +} + +[[nodiscard]] std::uint64_t valueSizeBytes(LowLevelType Type) noexcept { + return (static_cast<std::uint64_t>(Type.scalarBitWidth()) + 7U) / 8U; +} + +[[nodiscard]] bool isSupportedFloat(const ABIValue &Value) noexcept { + return Value.Category == ABIValueCategory::FloatingPoint && + (Value.Type.scalarBitWidth() == 32 || Value.Type.scalarBitWidth() == 64); +} + +[[nodiscard]] Expected<PhysicalRegister> floatingRegisterView(PhysicalRegister DoubleView, + const ABIValue &Value) { + if (!isSupportedFloat(Value)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V floating-register view requires f32 or f64"); + } + if (Value.Type.scalarBitWidth() == 64) + return DoubleView; + + constexpr std::uint32_t FirstDouble = static_cast<std::uint32_t>(PhysicalRegisterID::F0_D); + constexpr std::uint32_t LastDouble = static_cast<std::uint32_t>(PhysicalRegisterID::F31_D); + constexpr std::uint32_t FirstSingle = static_cast<std::uint32_t>(PhysicalRegisterID::F0_F); + const std::uint32_t Number = DoubleView.number(); + if (Number < FirstDouble || Number > LastDouble) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V ABI floating register is not an FPR64 view"); + } + return PhysicalRegister::create(FirstSingle + Number - FirstDouble); +} + +[[nodiscard]] bool requiresIntegerTransport(const ABIValue &Value) noexcept { + return !isSupportedFloat(Value) || Value.Flags.has(ABIValueFlag::ByVal) || + Value.Flags.has(ABIValueFlag::StructReturn) || Value.Flags.has(ABIValueFlag::Indirect); +} + +[[nodiscard]] Expected<void> verifyABIValue(const ABIValue &Value, bool RequireRegister) { + if (RequireRegister && Value.Value.isNoRegister()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V ABI value requires a source or destination register"); + } + if (!Value.Type.isValid() || !Value.Type.isScalarValue() || Value.Type.scalarBitWidth() > 64) { + return makeUnexpected( + ErrorCode::Unsupported, + "RISC-V ABI accepts explicitly classified integer, pointer, f32, and f64 scalar fragments"); + } + if (Value.Category == ABIValueCategory::Invalid || Value.Category >= ABIValueCategory::Count) { + return makeUnexpected(ErrorCode::InvalidArgument, "RISC-V ABI value category is invalid"); + } + if (Value.Category == ABIValueCategory::FloatingPoint && !isSupportedFloat(Value)) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V LP64 ABIs only support f32 and f64 floating fragments"); + } + if ((Value.Category == ABIValueCategory::Pointer) != Value.Type.isPointer()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V ABI pointer category must match its pointer LowLevelType"); + } + if (Value.Type.isPointer() && Value.Type.scalarBitWidth() != 64) { + return makeUnexpected(ErrorCode::Unsupported, "RISC-V LP64 ABIs require 64-bit pointers"); + } + if (Value.Category == ABIValueCategory::Integer && Value.Type.scalarBitWidth() < 64) { + if (Value.Extension == ABIExtensionKind::None) { + return makeUnexpected(ErrorCode::InvalidArgument, + "narrow RISC-V ABI integers require an explicit extension kind"); + } + } else if (Value.Extension != ABIExtensionKind::None) { + return makeUnexpected(ErrorCode::InvalidArgument, + "only narrow integer ABI fragments may carry an extension kind"); + } + if (Value.Extension >= ABIExtensionKind::Count || Value.FragmentCount == 0 || + Value.FragmentIndex >= Value.FragmentCount || Value.FragmentCount > 2) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V ABI fragment coordinates or extension are invalid"); + } + + const bool IsByVal = Value.Flags.has(ABIValueFlag::ByVal); + const bool IsSRet = Value.Flags.has(ABIValueFlag::StructReturn); + const bool IsIndirect = Value.Flags.has(ABIValueFlag::Indirect); + const unsigned SpecialFlagCount = static_cast<unsigned>(IsByVal) + static_cast<unsigned>(IsSRet) + + static_cast<unsigned>(IsIndirect); + if (SpecialFlagCount > 1) { + return makeUnexpected(ErrorCode::InvalidArgument, + "byval, sret, and indirect ABI flags are mutually exclusive"); + } + if ((IsByVal || IsSRet || IsIndirect) && + (!Value.Type.isPointer() || Value.FragmentCount != 1 || Value.FragmentIndex != 0 || + Value.FragmentOffsetBytes != 0)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "byval, sret, and indirect values require one explicit pointer fragment"); + } + if (IsByVal) { + if (Value.ByValSizeBytes == 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "byval arguments require a nonzero copy size"); + } + } else if (Value.ByValSizeBytes != 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "non-byval values cannot carry a byval copy size"); + } + return {}; +} + +} // namespace + +[[nodiscard]] Expected<std::size_t> verifyValueSequence(std::span<const ABIValue> Values, + bool RequireRegisters) { + std::size_t ExpectedOriginal = 0; + std::size_t Index = 0; + while (Index < Values.size()) { + const ABIValue &First = Values[Index]; + auto ValueVerification = verifyABIValue(First, RequireRegisters); + if (!ValueVerification) + return std::unexpected(ValueVerification.error()); + if (First.OriginalValue != ExpectedOriginal || First.FragmentIndex != 0 || + Index + First.FragmentCount > Values.size()) { + return makeUnexpected( + ErrorCode::InvalidArgument, + "RISC-V ABI values must form contiguous, zero-based original-value groups"); + } + + std::uint64_t PreviousEnd = 0; + for (std::size_t Fragment = 0; Fragment < First.FragmentCount; ++Fragment) { + const ABIValue &Value = Values[Index + Fragment]; + auto FragmentVerification = verifyABIValue(Value, RequireRegisters); + if (!FragmentVerification) + return std::unexpected(FragmentVerification.error()); + if (Value.OriginalValue != First.OriginalValue || Value.FragmentIndex != Fragment || + Value.FragmentCount != First.FragmentCount || + Value.OriginalAlignment != First.OriginalAlignment || Value.Flags != First.Flags || + Value.ByValSizeBytes != First.ByValSizeBytes || + Value.ByValAlignment != First.ByValAlignment || Value.FragmentOffsetBytes < PreviousEnd) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V ABI fragments of one value have inconsistent metadata"); + } + PreviousEnd = + static_cast<std::uint64_t>(Value.FragmentOffsetBytes) + valueSizeBytes(Value.Type); + } + Index += First.FragmentCount; + ++ExpectedOriginal; + } + return ExpectedOriginal; +} + +namespace { + +[[nodiscard]] CCAssignmentFlags assignmentFlags(const ABIValue &Value, bool IsVariadic, + bool IsBitcast) { + CCAssignmentFlags Result; + if (IsVariadic) + Result.set(CCAssignmentFlag::Variadic); + if (Value.Flags.has(ABIValueFlag::ByVal)) + Result.set(CCAssignmentFlag::ByVal); + if (Value.Flags.has(ABIValueFlag::StructReturn)) + Result.set(CCAssignmentFlag::StructReturn); + if (Value.Flags.has(ABIValueFlag::Indirect)) + Result.set(CCAssignmentFlag::Indirect); + if (Value.Flags.has(ABIValueFlag::InRegister)) + Result.set(CCAssignmentFlag::InRegister); + if (IsBitcast) + Result.set(CCAssignmentFlag::Bitcast); + return Result; +} + +[[nodiscard]] CCLocationKind locationKind(const ABIValue &Value, bool IsRegister) noexcept { + if (Value.Flags.has(ABIValueFlag::Indirect)) + return CCLocationKind::Indirect; + return IsRegister ? CCLocationKind::Register : CCLocationKind::Stack; +} + +struct AssignmentState final { + std::size_t GPR = 0; + std::size_t FPR = 0; + std::uint64_t StackOffset = 0; + bool VariadicStackOnly = false; +}; + +[[nodiscard]] Expected<CCAssignment> +registerAssignment(const ABIValue &Value, std::size_t ValueIndex, PhysicalRegister Location, + LowLevelType LocationType, bool IsVariadic, bool IsBitcast) { + auto Align = + alignment(std::min<std::uint64_t>(Value.OriginalAlignment.bytes(), ABIStackAlignmentBytes)); + if (!Align) + return std::unexpected(Align.error()); + return CCAssignment{ + .ValueIndex = ValueIndex, + .OriginalValue = Value.Value, + .OriginalValueIndex = Value.OriginalValue, + .FragmentIndex = Value.FragmentIndex, + .FragmentCount = Value.FragmentCount, + .ValueType = Value.Type, + .LocationType = LocationType, + .LocationKind = locationKind(Value, true), + .LocationRegister = Location, + .StackOffsetBytes = std::nullopt, + .LocationSizeBytes = valueSizeBytes(LocationType), + .StackSlotSizeBytes = XLenBytes, + .LocationAlignment = *Align, + .Extension = Value.Extension, + .CustomFlags = assignmentFlags(Value, IsVariadic, IsBitcast), + }; +} + +[[nodiscard]] Expected<CCAssignment> stackAssignment(const ABIValue &Value, std::size_t ValueIndex, + std::uint64_t StackOffset, + LowLevelType LocationType, bool IsVariadic, + std::uint64_t StackAlignment) { + auto Align = alignment(StackAlignment); + if (!Align) + return std::unexpected(Align.error()); + return CCAssignment{ + .ValueIndex = ValueIndex, + .OriginalValue = Value.Value, + .OriginalValueIndex = Value.OriginalValue, + .FragmentIndex = Value.FragmentIndex, + .FragmentCount = Value.FragmentCount, + .ValueType = Value.Type, + .LocationType = LocationType, + .LocationKind = locationKind(Value, false), + .LocationRegister = std::nullopt, + .StackOffsetBytes = StackOffset, + .LocationSizeBytes = valueSizeBytes(LocationType), + .StackSlotSizeBytes = XLenBytes, + .LocationAlignment = *Align, + .Extension = Value.Extension, + .CustomFlags = assignmentFlags(Value, IsVariadic, false), + }; +} + +} // namespace + +[[nodiscard]] Expected<ArgumentAssignmentResult> +assignArguments(std::span<const ABIValue> Values, std::size_t FixedArgumentCount, bool IsVariadic, + const CallingConventionInfo &Convention) { + auto I64 = LowLevelType::makeCategoryNeutralScalar(64); + auto StackAlign = alignment(Convention.StackArgumentAlignmentBytes); + if (!I64 || !StackAlign) + return makeUnexpected(ErrorCode::InternalError, "RISC-V ABI primitive type facts are invalid"); + + ArgumentAssignmentResult Result; + AssignmentState State; + State.StackOffset = Convention.ShadowSpaceBytes; + Result.Assignments.reserve(Values.size()); + + std::size_t Index = 0; + while (Index < Values.size()) { + const ABIValue &First = Values[Index]; + const std::size_t GroupSize = First.FragmentCount; + const bool VariadicArgument = IsVariadic && First.OriginalValue >= FixedArgumentCount; + bool HasPreferredFloat = false; + std::size_t RequiredGPRs = 0; + std::size_t RequiredFPRs = 0; + for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) { + const ABIValue &Value = Values[Index + Fragment]; + if (!VariadicArgument && !requiresIntegerTransport(Value)) { + HasPreferredFloat = true; + ++RequiredFPRs; + } else { + ++RequiredGPRs; + } + } + + const bool CanUsePreferredAggregate = + HasPreferredFloat && + State.GPR + RequiredGPRs <= Convention.IntegerArgumentRegisters.size() && + State.FPR + RequiredFPRs <= Convention.FloatingArgumentRegisters.size(); + if (CanUsePreferredAggregate) { + for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) { + const ABIValue &Value = Values[Index + Fragment]; + Expected<CCAssignment> Assignment = makeUnexpected( + ErrorCode::InternalError, "RISC-V ABI argument assignment was not initialized"); + if (requiresIntegerTransport(Value)) { + Assignment = registerAssignment(Value, Index + Fragment, + Convention.IntegerArgumentRegisters[State.GPR++], *I64, + false, false); + } else { + auto View = + floatingRegisterView(Convention.FloatingArgumentRegisters[State.FPR++], Value); + if (!View) + return std::unexpected(View.error()); + Assignment = registerAssignment(Value, Index + Fragment, *View, Value.Type, false, false); + } + if (!Assignment) + return std::unexpected(Assignment.error()); + Result.Assignments.push_back(*Assignment); + } + Index += GroupSize; + continue; + } + + const bool RequiresAlignedPair = + VariadicArgument && GroupSize == 2 && First.OriginalAlignment.bytes() >= 16; + if (RequiresAlignedPair && State.GPR % 2 != 0 && + State.GPR < Convention.IntegerArgumentRegisters.size()) { + ++State.GPR; + } + bool ForceEntireGroupToStack = State.VariadicStackOnly; + if (RequiresAlignedPair && State.GPR + GroupSize > Convention.IntegerArgumentRegisters.size()) { + ForceEntireGroupToStack = true; + State.GPR = Convention.IntegerArgumentRegisters.size(); + } + + bool FirstStackFragment = true; + for (std::size_t Fragment = 0; Fragment < GroupSize; ++Fragment) { + const ABIValue &Value = Values[Index + Fragment]; + const bool HasGPR = + !ForceEntireGroupToStack && State.GPR < Convention.IntegerArgumentRegisters.size(); + if (HasGPR) { + const bool Bitcast = isSupportedFloat(Value); + auto Assignment = registerAssignment(Value, Index + Fragment, + Convention.IntegerArgumentRegisters[State.GPR++], *I64, + VariadicArgument, Bitcast); + if (!Assignment) + return std::unexpected(Assignment.error()); + Result.Assignments.push_back(*Assignment); + continue; + } + + if (Value.Flags.has(ABIValueFlag::InRegister)) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V inreg argument cannot be assigned to the stack"); + } + std::uint64_t RequiredAlignment = XLenBytes; + if (FirstStackFragment && ForceEntireGroupToStack) { + RequiredAlignment = std::min<std::uint64_t>( + std::max<std::uint64_t>(XLenBytes, First.OriginalAlignment.bytes()), + ABIStackAlignmentBytes); + } + auto AlignedOffset = alignTo(State.StackOffset, RequiredAlignment); + if (!AlignedOffset) + return std::unexpected(AlignedOffset.error()); + State.StackOffset = *AlignedOffset; + const LowLevelType LocationType = isSupportedFloat(Value) ? Value.Type : *I64; + auto Assignment = stackAssignment(Value, Index + Fragment, State.StackOffset, LocationType, + VariadicArgument, RequiredAlignment); + if (!Assignment) + return std::unexpected(Assignment.error()); + Result.Assignments.push_back(*Assignment); + if (State.StackOffset > std::numeric_limits<std::uint64_t>::max() - XLenBytes) { + return makeUnexpected(ErrorCode::Unsupported, "RISC-V argument area overflows uint64"); + } + State.StackOffset += XLenBytes; + FirstStackFragment = false; + if (VariadicArgument) + State.VariadicStackOnly = true; + } + Index += GroupSize; + } + + auto FinalSize = alignTo(State.StackOffset, Convention.StackArgumentAlignmentBytes); + if (!FinalSize) + return std::unexpected(FinalSize.error()); + Result.StackArea = StackArgumentArea{*FinalSize, Convention.ShadowSpaceBytes, *StackAlign}; + Result.FirstUnallocatedGPR = State.GPR; + return Result; +} + +[[nodiscard]] Expected<std::vector<CCAssignment>> +assignReturns(std::span<const ABIValue> Values, bool RequireRegisters, + const CallingConventionInfo &Convention) { + auto Sequence = verifyValueSequence(Values, RequireRegisters); + if (!Sequence) + return std::unexpected(Sequence.error()); + if (Values.size() > 2) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V direct returns are limited to two explicit fragments"); + } + auto I64 = LowLevelType::makeCategoryNeutralScalar(64); + if (!I64) + return std::unexpected(I64.error()); + + std::vector<CCAssignment> Result; + Result.reserve(Values.size()); + std::size_t GPR = 0; + std::size_t FPR = 0; + for (std::size_t Index = 0; Index < Values.size(); ++Index) { + const ABIValue &Value = Values[Index]; + if (Value.Flags.rawValue() != 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "return fragments cannot carry argument-only ABI flags"); + } + const bool UseFloatingRegister = + isSupportedFloat(Value) && FPR < Convention.FloatingReturnRegisters.size(); + Expected<CCAssignment> Assignment = makeUnexpected( + ErrorCode::Unsupported, "RISC-V calling convention has no return register available"); + if (UseFloatingRegister) { + auto View = floatingRegisterView(Convention.FloatingReturnRegisters[FPR++], Value); + if (!View) + return std::unexpected(View.error()); + Assignment = registerAssignment(Value, Index, *View, Value.Type, false, false); + } else if (GPR < Convention.IntegerReturnRegisters.size()) { + Assignment = registerAssignment(Value, Index, Convention.IntegerReturnRegisters[GPR++], *I64, + false, isSupportedFloat(Value)); + } + if (!Assignment) + return std::unexpected(Assignment.error()); + Result.push_back(*Assignment); + } + return Result; +} + +[[nodiscard]] bool hasStructReturn(std::span<const ABIValue> Arguments) noexcept { + return std::ranges::any_of( + Arguments, [](const ABIValue &Value) { return Value.Flags.has(ABIValueFlag::StructReturn); }); +} + +namespace { + +[[nodiscard]] bool equivalentReturnLocations(std::span<const CCAssignment> Left, + std::span<const CCAssignment> Right) noexcept { + if (Left.size() != Right.size()) + return false; + for (std::size_t Index = 0; Index < Left.size(); ++Index) { + if (Left[Index].ValueType != Right[Index].ValueType || + Left[Index].LocationType != Right[Index].LocationType || + Left[Index].LocationKind != Right[Index].LocationKind || + Left[Index].LocationRegister != Right[Index].LocationRegister || + Left[Index].Extension != Right[Index].Extension || + Left[Index].CustomFlags != Right[Index].CustomFlags) { + return false; + } + } + return true; +} + +} // namespace + +[[nodiscard]] Expected<std::string> +tailCallRejection(const CallLoweringRequest &Request, const ArgumentAssignmentResult &Arguments, + std::span<const CCAssignment> Results, + std::span<const ByValCopyAssignment> ByValCopies, + const CallingConventionInfo &Convention) { + if (!Convention.SupportsTailCalls) + return std::string("the calling convention does not support tail calls"); + if (!Request.TailContext) + return std::string("tail-call context is missing"); + const TailCallContext &Context = *Request.TailContext; + if (Context.CallerConvention != Request.Convention) + return std::string("caller and callee calling conventions differ"); + if (Context.CallerIsVariadic != Request.IsVariadic || + (Request.IsVariadic && Context.CallerFixedArgumentCount != Request.FixedArgumentCount)) { + return std::string("caller and callee variadic signatures differ"); + } + if (Context.CallerHasDynamicStackAllocation) + return std::string("the caller has a dynamic stack allocation"); + if (!ByValCopies.empty()) + return std::string("tail calls cannot create byval copies"); + if (Context.CallerUsesStructReturn != hasStructReturn(Request.Arguments)) + return std::string("caller and callee sret conventions differ"); + if (Arguments.StackArea.SizeBytes > Context.ReusableIncomingArgumentBytes) + return std::string("outgoing stack arguments exceed the reusable incoming area"); + if (std::ranges::any_of(Request.Results, + [](const ABIValue &Value) { return !Value.Value.isNoRegister(); })) { + return std::string("tail-call result signatures must not request local result copies"); + } + auto CallerReturns = assignReturns(Context.CallerReturns, false, Convention); + if (!CallerReturns) + return std::unexpected(CallerReturns.error()); + if (!equivalentReturnLocations(Results, *CallerReturns)) + return std::string("caller and callee return locations differ"); + return std::string{}; +} + +[[nodiscard]] Expected<const CallingConventionInfo *> +riscvConvention(CallingConventionID Convention) { + CallingConvention TargetConvention = CallingConvention::None; + if (Convention == callingConventionID(CallingConvention::LP64D)) + TargetConvention = CallingConvention::LP64D; + else if (Convention == callingConventionID(CallingConvention::LP64)) + TargetConvention = CallingConvention::LP64; + else + return makeUnexpected(ErrorCode::Unsupported, "RISC-V backend only supports LP64 and LP64D"); + const CallingConventionInfo *Info = callingConvention(TargetConvention); + if (Info == nullptr) + return makeUnexpected(ErrorCode::InternalError, + "RISC-V calling-convention target facts are unavailable"); + return Info; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVCallingConv.h b/backend/lib/Target/RISCV/RISCVCallingConv.h new file mode 100644 index 0000000..ba2dcf2 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVCallingConv.h @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_LIB_TARGET_RISCV_RISCVCALLINGCONV_H +#define RCC_LIB_TARGET_RISCV_RISCVCALLINGCONV_H + +#include "rcc/CodeGen/TargetCallLowering.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" + +#include <cstddef> +#include <cstdint> +#include <span> +#include <string> +#include <vector> + +namespace rcc::riscv { + +struct CallingConventionInfo; + +inline constexpr std::uint64_t XLenBytes = 8; +inline constexpr std::uint64_t ABIStackAlignmentBytes = 16; + +[[nodiscard]] constexpr CallingConventionID +callingConventionID(CallingConvention Convention) noexcept { + return CallingConventionID::fromValue(static_cast<std::uint32_t>(Convention)); +} + +struct ArgumentAssignmentResult final { + std::vector<CCAssignment> Assignments; + StackArgumentArea StackArea; + std::size_t FirstUnallocatedGPR = 0; +}; + +[[nodiscard]] Expected<std::size_t> verifyValueSequence(std::span<const ABIValue> Values, + bool RequireRegisters); +[[nodiscard]] Expected<ArgumentAssignmentResult> +assignArguments(std::span<const ABIValue> Values, std::size_t FixedArgumentCount, bool IsVariadic, + const CallingConventionInfo &Convention); +[[nodiscard]] Expected<std::vector<CCAssignment>> +assignReturns(std::span<const ABIValue> Values, bool RequireRegisters, + const CallingConventionInfo &Convention); +[[nodiscard]] bool hasStructReturn(std::span<const ABIValue> Arguments) noexcept; +[[nodiscard]] Expected<std::string> +tailCallRejection(const CallLoweringRequest &Request, const ArgumentAssignmentResult &Arguments, + std::span<const CCAssignment> Results, + std::span<const ByValCopyAssignment> ByValCopies, + const CallingConventionInfo &Convention); +[[nodiscard]] Expected<const CallingConventionInfo *> +riscvConvention(CallingConventionID Convention); + +} // namespace rcc::riscv + +#endif // RCC_LIB_TARGET_RISCV_RISCVCALLINGCONV_H diff --git a/backend/lib/Target/RISCV/RISCVCallingConv.rccdesc b/backend/lib/Target/RISCV/RISCVCallingConv.rccdesc new file mode 100644 index 0000000..b222023 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVCallingConv.rccdesc @@ -0,0 +1,33 @@ +calling_conventions: + - + id: 2 + symbol: LP64 + name: lp64 + integer_register_class: GPR + floating_register_class: FPR64 + integer_argument_registers: [X10, X11, X12, X13, X14, X15, X16, X17] + floating_argument_registers: [] + integer_return_registers: [X10, X11] + floating_return_registers: [] + preserved_mask: CSR_ILP32_LP64 + stack_alignment_bytes: 16 + stack_argument_alignment_bytes: 16 + shadow_space_bytes: 0 + supports_varargs: true + supports_tail_calls: true + - + id: 1 + symbol: LP64D + name: lp64d + integer_register_class: GPR + floating_register_class: FPR64 + integer_argument_registers: [X10, X11, X12, X13, X14, X15, X16, X17] + floating_argument_registers: [F10_D, F11_D, F12_D, F13_D, F14_D, F15_D, F16_D, F17_D] + integer_return_registers: [X10, X11] + floating_return_registers: [F10_D, F11_D] + preserved_mask: CSR_ILP32D_LP64D + stack_alignment_bytes: 16 + stack_argument_alignment_bytes: 16 + shadow_space_bytes: 0 + supports_varargs: true + supports_tail_calls: true diff --git a/backend/lib/Target/RISCV/RISCVFinalizeISel.cpp b/backend/lib/Target/RISCV/RISCVFinalizeISel.cpp new file mode 100644 index 0000000..e2d99b3 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVFinalizeISel.cpp @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "RISCVInstrInfo.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineCFGUpdater.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" + +#include <cstddef> +#include <cstdint> +#include <optional> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +struct SelectOperands final { + Register Destination; + Register Left; + Register Right; + std::int64_t Condition = 0; + Register TrueValue; + Register FalseValue; +}; + +struct SelectSequence final { + std::vector<MachineInstr *> Selects; + std::vector<MachineInstr *> DebugValues; + MachineInstr *LastSelect = nullptr; +}; + +[[nodiscard]] bool isSelectPseudo(Opcode Value) noexcept { + return Value == instructionOpcode(InstructionOpcode::Select_GPR_Using_CC_GPR) || + Value == instructionOpcode(InstructionOpcode::Select_FPR32_Using_CC_GPR) || + Value == instructionOpcode(InstructionOpcode::Select_FPR64_Using_CC_GPR); +} + +[[nodiscard]] Expected<SelectOperands> selectOperands(const MachineInstr &Instruction) { + if (!isSelectPseudo(Instruction.opcode()) || Instruction.operandCount() < 6U) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V custom select has an invalid opcode or operand count"); + } + + const auto RegisterAt = [&Instruction](std::size_t Index) -> std::optional<Register> { + const MachineOperand *Operand = Instruction.operandAt(Index); + if (Operand == nullptr) + return std::nullopt; + return Operand->registerValue(); + }; + const auto Destination = RegisterAt(0); + const auto Left = RegisterAt(1); + const auto Right = RegisterAt(2); + const MachineOperand *ConditionOperand = Instruction.operandAt(3); + const auto *Condition = + ConditionOperand == nullptr ? nullptr : ConditionOperand->getIf<std::int64_t>(); + const auto TrueValue = RegisterAt(4); + const auto FalseValue = RegisterAt(5); + if (!Destination || !Left || !Right || Condition == nullptr || !TrueValue || !FalseValue) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V custom select has malformed operands"); + } + if (*Condition < static_cast<std::int64_t>(BranchConditionCode::Equal) || + *Condition > static_cast<std::int64_t>(BranchConditionCode::UnsignedGreaterEqual)) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V custom select has an invalid condition code"); + } + return SelectOperands{*Destination, *Left, *Right, *Condition, *TrueValue, *FalseValue}; +} + +[[nodiscard]] bool containsRegister(const std::vector<Register> &Registers, + Register Value) noexcept { + for (const Register Candidate : Registers) + if (Candidate == Value) + return true; + return false; +} + +[[nodiscard]] bool instructionUsesAny(const MachineInstr &Instruction, + const std::vector<Register> &Registers) noexcept { + for (const MachineOperand &Operand : Instruction.operands()) { + const std::optional<Register> Reg = Operand.registerValue(); + if (Reg && Operand.isUse() && containsRegister(Registers, *Reg)) + return true; + } + return false; +} + +void collectAdjacentDebugValues(MachineInstr &Select, Register Destination, + std::vector<MachineInstr *> &Result) { + for (MachineInstr *Instruction = Select.next(); + Instruction != nullptr && Instruction->isDebugInstr(); Instruction = Instruction->next()) { + const std::vector<Register> DestinationList{Destination}; + if (instructionUsesAny(*Instruction, DestinationList)) + Result.push_back(Instruction); + } +} + +[[nodiscard]] bool stopsSelectSequence(const MachineInstr &Instruction, + const InstructionDescriptor &Descriptor, + const std::vector<Register> &SelectDestinations) noexcept { + const InstructionFlags Flags = Descriptor.Flags; + if (Flags.has(InstructionFlag::UnmodeledSideEffects) || + Flags.has(InstructionFlag::HasSideEffects) || Flags.has(InstructionFlag::MayLoad) || + Flags.has(InstructionFlag::MayStore) || Flags.has(InstructionFlag::UsesCustomInserter) || + !Instruction.memOperands().empty() || Instruction.flags().has(MachineInstrFlag::FrameSetup) || + Instruction.flags().has(MachineInstrFlag::FrameDestroy) || + Instruction.opcode() == opcode(GenericOpcode::INLINEASM) || + Instruction.opcode() == opcode(GenericOpcode::INLINEASM_BR)) { + return true; + } + return instructionUsesAny(Instruction, SelectDestinations); +} + +[[nodiscard]] Expected<SelectSequence> collectSelectSequence(MachineInstr &First, + const TargetDescription &Target) { + auto FirstOperands = selectOperands(First); + if (!FirstOperands) + return std::unexpected(FirstOperands.error()); + + SelectSequence Result; + std::vector<Register> SelectDestinations{FirstOperands->Destination}; + for (MachineInstr *Instruction = &First; Instruction != nullptr; + Instruction = Instruction->next()) { + if (Instruction->isDebugInstr()) + continue; + if (isSelectPseudo(Instruction->opcode())) { + auto Operands = selectOperands(*Instruction); + if (!Operands) + return std::unexpected(Operands.error()); + if (Operands->Left != FirstOperands->Left || Operands->Right != FirstOperands->Right || + Operands->Condition != FirstOperands->Condition || + containsRegister(SelectDestinations, Operands->TrueValue) || + containsRegister(SelectDestinations, Operands->FalseValue)) { + break; + } + Result.Selects.push_back(Instruction); + Result.LastSelect = Instruction; + collectAdjacentDebugValues(*Instruction, Operands->Destination, Result.DebugValues); + if (!containsRegister(SelectDestinations, Operands->Destination)) + SelectDestinations.push_back(Operands->Destination); + continue; + } + + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); + if (Descriptor == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V select expansion encountered an unknown opcode"); + } + if (stopsSelectSequence(*Instruction, *Descriptor, SelectDestinations)) + break; + } + if (Result.Selects.empty() || Result.LastSelect == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V select sequence did not contain its anchor instruction"); + } + return Result; +} + +[[nodiscard]] Expected<Opcode> conditionalBranchOpcode(std::int64_t Condition) { + switch (static_cast<BranchConditionCode>(Condition)) { + case BranchConditionCode::Equal: + return instructionOpcode(InstructionOpcode::BEQ); + case BranchConditionCode::NotEqual: + return instructionOpcode(InstructionOpcode::BNE); + case BranchConditionCode::SignedLessThan: + return instructionOpcode(InstructionOpcode::BLT); + case BranchConditionCode::SignedGreaterEqual: + return instructionOpcode(InstructionOpcode::BGE); + case BranchConditionCode::UnsignedLessThan: + return instructionOpcode(InstructionOpcode::BLTU); + case BranchConditionCode::UnsignedGreaterEqual: + return instructionOpcode(InstructionOpcode::BGEU); + } + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V select has an invalid condition code"); +} + +[[nodiscard]] Expected<MachineInstr *> +buildConditionalBranch(MachineFunction &Function, const TargetDescription &Target, + MachineBasicBlock &Block, MachineBasicBlock &Destination, + const SelectOperands &Operands, const MachineInstr &Source) { + auto BranchOpcode = conditionalBranchOpcode(Operands.Condition); + if (!BranchOpcode) + return std::unexpected(BranchOpcode.error()); + MachineIRBuilder Builder(Function, Target); + if (auto Point = Builder.setInsertPoint(Block); !Point) + return std::unexpected(Point.error()); + auto Pending = Builder.buildInstr(*BranchOpcode); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Added = Pending->addUse(Operands.Left); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(Operands.Right); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addBasicBlock(Destination); !Added) + return std::unexpected(Added.error()); + if (Source.debugLocation()) { + if (auto Location = Pending->setDebugLocation(*Source.debugLocation()); !Location) + return std::unexpected(Location.error()); + } + return Pending->build(); +} + +[[nodiscard]] Expected<MachineInstr *> +buildPhi(MachineFunction &Function, const TargetDescription &Target, MachineBasicBlock &Tail, + MachineInstr *InsertBefore, const SelectOperands &Operands, MachineBasicBlock &Head, + MachineBasicBlock &IfFalse, const MachineInstr &Source) { + MachineIRBuilder Builder(Function, Target); + Expected<void> Point = InsertBefore == nullptr ? Builder.setInsertPoint(Tail) + : Builder.setInsertPointBefore(*InsertBefore); + if (!Point) + return std::unexpected(Point.error()); + auto Pending = Builder.buildInstr(opcode(GenericOpcode::PHI)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Added = Pending->addDef(Operands.Destination); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(Operands.TrueValue); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addBasicBlock(Head); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(Operands.FalseValue); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addBasicBlock(IfFalse); !Added) + return std::unexpected(Added.error()); + if (Source.debugLocation()) { + if (auto Location = Pending->setDebugLocation(*Source.debugLocation()); !Location) + return std::unexpected(Location.error()); + } + return Pending->build(); +} + +[[nodiscard]] Expected<void> insertBlocksAfter(MachineFunction &Function, MachineBasicBlock &Head, + MachineBasicBlock &First, + MachineBasicBlock &Second) { + MachineBasicBlock *Following = Head.next(); + if (Following == nullptr) { + if (auto Inserted = Function.appendBlock(First); !Inserted) + return std::unexpected(Inserted.error()); + return Function.appendBlock(Second); + } + if (auto Inserted = Function.insertBlockBefore(*Following, First); !Inserted) + return std::unexpected(Inserted.error()); + return Function.insertBlockBefore(*Following, Second); +} + +[[nodiscard]] Expected<void> moveInstructionToEnd(MachineInstr &Instruction, + MachineBasicBlock &Destination) { + if (auto Removed = Instruction.eraseFromParent(); !Removed) + return std::unexpected(Removed.error()); + return Destination.appendInstruction(Instruction); +} + +[[nodiscard]] Expected<MachineBasicBlock *> emitSelectSequence(MachineInstr &First, + MachineBasicBlock &Head, + MachineFunction &Function, + const TargetDescription &Target) { + auto Sequence = collectSelectSequence(First, Target); + if (!Sequence) + return std::unexpected(Sequence.error()); + auto FirstOperands = selectOperands(First); + if (!FirstOperands) + return std::unexpected(FirstOperands.error()); + + auto TailResult = Function.createBasicBlock(); + if (!TailResult) + return std::unexpected(TailResult.error()); + auto IfFalseResult = Function.createBasicBlock(); + if (!IfFalseResult) + return std::unexpected(IfFalseResult.error()); + MachineBasicBlock *Tail = *TailResult; + MachineBasicBlock *IfFalse = *IfFalseResult; + if (auto Inserted = insertBlocksAfter(Function, Head, *IfFalse, *Tail); !Inserted) + return std::unexpected(Inserted.error()); + const std::uint32_t CallFrameSize = Head.attributes().callFrameSize(); + IfFalse->attributes().setCallFrameSize(CallFrameSize); + Tail->attributes().setCallFrameSize(CallFrameSize); + + for (MachineInstr *DebugValue : Sequence->DebugValues) { + if (auto Moved = moveInstructionToEnd(*DebugValue, *Tail); !Moved) + return std::unexpected(Moved.error()); + } + for (MachineInstr *Instruction = Sequence->LastSelect->next(); Instruction != nullptr;) { + MachineInstr *Next = Instruction->next(); + if (auto Moved = moveInstructionToEnd(*Instruction, *Tail); !Moved) + return std::unexpected(Moved.error()); + Instruction = Next; + } + + MachineCFGUpdater CFG = Function.cfgUpdater(); + if (auto Transferred = CFG.transferSuccessors(Head, *Tail); !Transferred) + return std::unexpected(Transferred.error()); + auto Half = BranchProbability::fromFraction(1U, 2U); + if (!Half) + return std::unexpected(Half.error()); + if (auto Added = CFG.addSuccessor(Head, *IfFalse, *Half); !Added) + return std::unexpected(Added.error()); + if (auto Added = CFG.addSuccessor(Head, *Tail, *Half); !Added) + return std::unexpected(Added.error()); + if (auto Added = CFG.addSuccessor(*IfFalse, *Tail, BranchProbability::one()); !Added) + return std::unexpected(Added.error()); + + auto Branch = buildConditionalBranch(Function, Target, Head, *Tail, *FirstOperands, First); + if (!Branch) + return std::unexpected(Branch.error()); + + MachineInstr *PhiInsertBefore = Tail->firstInstruction(); + for (MachineInstr *Select : Sequence->Selects) { + auto Operands = selectOperands(*Select); + if (!Operands) + return std::unexpected(Operands.error()); + auto Phi = + buildPhi(Function, Target, *Tail, PhiInsertBefore, *Operands, Head, *IfFalse, *Select); + if (!Phi) + return std::unexpected(Phi.error()); + } + for (MachineInstr *Select : Sequence->Selects) { + if (auto Removed = Select->eraseFromParent(); !Removed) + return std::unexpected(Removed.error()); + } + Function.properties().clear(MachineFunctionProperty::NoPHIs); + return Tail; +} + +[[nodiscard]] MachineInstr *nextNonDebug(MachineInstr &Instruction) noexcept { + MachineInstr *Next = Instruction.next(); + while (Next != nullptr && Next->isDebugInstr()) + Next = Next->next(); + return Next; +} + +[[nodiscard]] bool isCascadedFloatingSelect(MachineInstr &First, MachineInstr *Second) noexcept { + if (First.opcode() == instructionOpcode(InstructionOpcode::Select_GPR_Using_CC_GPR) || + Second == nullptr || Second->opcode() != First.opcode()) { + return false; + } + const MachineOperand *DestinationOperand = First.operandAt(0); + const MachineOperand *FalseOperand = Second->operandAt(5); + const auto Destination = + DestinationOperand == nullptr ? std::nullopt : DestinationOperand->registerValue(); + const auto FalseValue = FalseOperand == nullptr ? std::nullopt : FalseOperand->registerValue(); + return Destination && FalseValue && *Destination == *FalseValue && + FalseOperand->registerFlags().has(RegisterOperandFlag::Kill); +} + +[[nodiscard]] Expected<MachineInstr *> +buildCascadedPhi(MachineFunction &Function, const TargetDescription &Target, + MachineBasicBlock &Sink, const SelectOperands &FirstOperands, + const SelectOperands &SecondOperands, MachineBasicBlock &Head, + MachineBasicBlock &FirstPath, MachineBasicBlock &SecondPath, + const MachineInstr &Source) { + MachineIRBuilder Builder(Function, Target); + Expected<void> Point = Sink.firstInstruction() == nullptr + ? Builder.setInsertPoint(Sink) + : Builder.setInsertPointBefore(*Sink.firstInstruction()); + if (!Point) + return std::unexpected(Point.error()); + auto Pending = Builder.buildInstr(opcode(GenericOpcode::PHI)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Added = Pending->addDef(SecondOperands.Destination); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(SecondOperands.TrueValue); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addBasicBlock(Head); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(FirstOperands.TrueValue); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addBasicBlock(FirstPath); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(FirstOperands.FalseValue); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addBasicBlock(SecondPath); !Added) + return std::unexpected(Added.error()); + if (Source.debugLocation()) { + if (auto Location = Pending->setDebugLocation(*Source.debugLocation()); !Location) + return std::unexpected(Location.error()); + } + return Pending->build(); +} + +[[nodiscard]] Expected<MachineBasicBlock *> +emitCascadedSelect(MachineInstr &First, MachineInstr &Second, MachineBasicBlock &Head, + MachineFunction &Function, const TargetDescription &Target) { + auto FirstOperands = selectOperands(First); + if (!FirstOperands) + return std::unexpected(FirstOperands.error()); + auto SecondOperands = selectOperands(Second); + if (!SecondOperands) + return std::unexpected(SecondOperands.error()); + + auto FirstPathResult = Function.createBasicBlock(); + if (!FirstPathResult) + return std::unexpected(FirstPathResult.error()); + auto SecondPathResult = Function.createBasicBlock(); + if (!SecondPathResult) + return std::unexpected(SecondPathResult.error()); + auto SinkResult = Function.createBasicBlock(); + if (!SinkResult) + return std::unexpected(SinkResult.error()); + MachineBasicBlock *FirstPath = *FirstPathResult; + MachineBasicBlock *SecondPath = *SecondPathResult; + MachineBasicBlock *Sink = *SinkResult; + MachineBasicBlock *Following = Head.next(); + const auto Insert = [&](MachineBasicBlock &Block) -> Expected<void> { + return Following == nullptr ? Function.appendBlock(Block) + : Function.insertBlockBefore(*Following, Block); + }; + if (auto Inserted = Insert(*FirstPath); !Inserted) + return std::unexpected(Inserted.error()); + if (auto Inserted = Insert(*SecondPath); !Inserted) + return std::unexpected(Inserted.error()); + if (auto Inserted = Insert(*Sink); !Inserted) + return std::unexpected(Inserted.error()); + const std::uint32_t CallFrameSize = Head.attributes().callFrameSize(); + FirstPath->attributes().setCallFrameSize(CallFrameSize); + SecondPath->attributes().setCallFrameSize(CallFrameSize); + Sink->attributes().setCallFrameSize(CallFrameSize); + + for (MachineInstr *Instruction = First.next(); Instruction != nullptr;) { + MachineInstr *Next = Instruction->next(); + if (auto Moved = moveInstructionToEnd(*Instruction, *Sink); !Moved) + return std::unexpected(Moved.error()); + Instruction = Next; + } + + MachineCFGUpdater CFG = Function.cfgUpdater(); + if (auto Transferred = CFG.transferSuccessors(Head, *Sink); !Transferred) + return std::unexpected(Transferred.error()); + auto Half = BranchProbability::fromFraction(1U, 2U); + if (!Half) + return std::unexpected(Half.error()); + if (auto Added = CFG.addSuccessor(Head, *FirstPath, *Half); !Added) + return std::unexpected(Added.error()); + if (auto Added = CFG.addSuccessor(Head, *Sink, *Half); !Added) + return std::unexpected(Added.error()); + if (auto Added = CFG.addSuccessor(*FirstPath, *SecondPath, *Half); !Added) + return std::unexpected(Added.error()); + if (auto Added = CFG.addSuccessor(*FirstPath, *Sink, *Half); !Added) + return std::unexpected(Added.error()); + if (auto Added = CFG.addSuccessor(*SecondPath, *Sink, BranchProbability::one()); !Added) + return std::unexpected(Added.error()); + + auto FirstBranch = + buildConditionalBranch(Function, Target, *FirstPath, *Sink, *FirstOperands, First); + if (!FirstBranch) + return std::unexpected(FirstBranch.error()); + auto SecondBranch = buildConditionalBranch(Function, Target, Head, *Sink, *SecondOperands, First); + if (!SecondBranch) + return std::unexpected(SecondBranch.error()); + auto Phi = buildCascadedPhi(Function, Target, *Sink, *FirstOperands, *SecondOperands, Head, + *FirstPath, *SecondPath, First); + if (!Phi) + return std::unexpected(Phi.error()); + + if (auto Removed = First.eraseFromParent(); !Removed) + return std::unexpected(Removed.error()); + if (auto Removed = Second.eraseFromParent(); !Removed) + return std::unexpected(Removed.error()); + Function.properties().clear(MachineFunctionProperty::NoPHIs); + return Sink; +} + +class RISCVFinalizeISelInfo final : public TargetFinalizeISelInfo { +public: + [[nodiscard]] Expected<MachineBasicBlock *> + emitInstrWithCustomInserter(MachineInstr &Instruction, MachineBasicBlock &Block, + MachineFunction &Function, + const TargetDescription &Target) const override { + if (!isSelectPseudo(Instruction.opcode())) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V has no custom inserter for the requested opcode"); + } + MachineInstr *Second = nextNonDebug(Instruction); + if (isCascadedFloatingSelect(Instruction, Second)) + return emitCascadedSelect(Instruction, *Second, Block, Function, Target); + return emitSelectSequence(Instruction, Block, Function, Target); + } +}; + +} // namespace + +const TargetFinalizeISelInfo &finalizeISelInfo() noexcept { + static const RISCVFinalizeISelInfo Info; + return Info; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVFrameLowering.cpp b/backend/lib/Target/RISCV/RISCVFrameLowering.cpp new file mode 100644 index 0000000..02520a7 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVFrameLowering.cpp @@ -0,0 +1,2739 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "GISel/RISCVCallLowering.h" +#include "RISCV.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineCFIInfo.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/MachineOperand.h" +#include "rcc/CodeGen/RegisterMask.h" +#include "rcc/CodeGen/RegisterScavenger.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVFunctionInfo.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <algorithm> +#include <array> +#include <bit> +#include <cstddef> +#include <cstdint> +#include <iostream> +#include <iterator> +#include <limits> +#include <memory> +#include <optional> +#include <ranges> +#include <span> +#include <unordered_set> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +constexpr std::uint64_t StackAlignmentBytes = 16; +constexpr std::int64_t MaxSigned11 = 1023; +constexpr std::int64_t MinSigned12 = -2048; +constexpr std::int64_t MaxSigned12 = 2047; + +[[nodiscard]] constexpr bool fitsSigned12(std::int64_t Value) noexcept { + return Value >= MinSigned12 && Value <= MaxSigned12; +} + +[[nodiscard]] constexpr bool hasStackObjectFlag(const StackObjectDesc &Object, + CallStackObjectFlag Flag) noexcept { + return (Object.TargetFlags & static_cast<std::uint64_t>(Flag)) != 0; +} + +[[nodiscard]] constexpr bool isOutgoingCallObject(const StackObjectDesc &Object) noexcept { + return Object.Kind == StackObjectKind::Target && + (hasStackObjectFlag(Object, CallStackObjectFlag::OutgoingArgument) || + hasStackObjectFlag(Object, CallStackObjectFlag::ByValCopy)); +} + +[[nodiscard]] Expected<std::int64_t> checkedAdd(std::int64_t Left, std::int64_t Right) { + std::int64_t Result = 0; + if (__builtin_add_overflow(Left, Right, &Result)) + return makeUnexpected(ErrorCode::FrameLayoutFailed, "RISC-V frame offset overflows int64"); + return Result; +} + +[[nodiscard]] Expected<std::uint64_t> alignTo(std::uint64_t Value, std::uint64_t Align) { + if (Align == 0 || !std::has_single_bit(Align)) + return makeUnexpected(ErrorCode::InvalidArgument, "RISC-V frame alignment is invalid"); + const std::uint64_t Mask = Align - 1U; + if (Value > std::numeric_limits<std::uint64_t>::max() - Mask) + return makeUnexpected(ErrorCode::FrameLayoutFailed, "RISC-V frame size overflows uint64"); + return (Value + Mask) & ~Mask; +} + +[[nodiscard]] Expected<std::int64_t> immediateValue(const MachineOperand &Operand) { + if (const auto *Immediate = Operand.getIf<std::int64_t>()) + return *Immediate; + const auto *Integer = Operand.getIf<IntegerImmediateOperand>(); + if (Integer == nullptr || Integer->Value.bitWidth() == 0) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V frame operand requires an integer immediate"); + } + if (Integer->Value.bitWidth() > 64) { + return makeUnexpected(ErrorCode::Unsupported, "RISC-V frame immediate must fit in 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); +} + +struct CallFrameRequirement final { + std::int64_t Amount = 0; + std::int64_t PreparedBytes = 0; + std::int64_t AlignedAmount = 0; +}; + +[[nodiscard]] Expected<CallFrameRequirement> callFrameRequirement(const MachineInstr &Instruction) { + if (Instruction.operandCount() < 2) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call-frame pseudo requires amount and prepared-byte operands"); + } + for (std::size_t Index = 2; Index < Instruction.operandCount(); ++Index) { + const MachineOperand *Operand = Instruction.operandAt(Index); + if (Operand == nullptr || !Operand->isRegister() || !Operand->isImplicit()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call-frame pseudo has an unexpected explicit operand"); + } + } + auto Amount = immediateValue(*Instruction.operandAt(0)); + auto PreparedBytes = immediateValue(*Instruction.operandAt(1)); + if (!Amount || !PreparedBytes || *Amount < 0 || *PreparedBytes < 0) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call-frame amount or prepared-byte count is invalid"); + } + auto Aligned = alignTo(static_cast<std::uint64_t>(*Amount), StackAlignmentBytes); + if (!Aligned || *Aligned > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, "RISC-V call-frame amount is too large"); + } + return CallFrameRequirement{*Amount, *PreparedBytes, static_cast<std::int64_t>(*Aligned)}; +} + +[[nodiscard]] Expected<std::int64_t> callFrameAmount(const MachineInstr &Instruction) { + auto Requirement = callFrameRequirement(Instruction); + if (!Requirement) + return std::unexpected(Requirement.error()); + return Requirement->AlignedAmount; +} + +[[nodiscard]] bool hasVariableObject(const MachineFunction &Function) noexcept { + return std::ranges::any_of(Function.frameInfo().objects(), [](const StackObject &Object) { + return !Object.isTombstone() && !Object.description()->IsDead && Object.isVariableSized(); + }); +} + +[[nodiscard]] std::uint64_t requiredStackAlignment(const MachineFunction &Function) noexcept { + std::uint64_t Result = StackAlignmentBytes; + Result = + std::max(Result, Function.frameInfo().state().MaxAlignment.value_or(Alignment{}).bytes()); + for (const StackObject &Object : Function.frameInfo().objects()) { + if (!Object.isTombstone() && !Object.description()->IsDead) + Result = std::max(Result, Object.description()->Align.bytes()); + } + return Result; +} + +[[nodiscard]] bool requiresStackRealignment(const MachineFunction &Function) noexcept { + return requiredStackAlignment(Function) > StackAlignmentBytes; +} + +[[nodiscard]] bool requiresFramePointer(const MachineFunction &Function) noexcept { + const MachineFrameState &State = Function.frameInfo().state(); + if (State.IsFrameAddressTaken || State.HasOpaqueSPAdjustment || hasVariableObject(Function) || + requiresStackRealignment(Function)) + return true; + if (Function.frameInfo().maxCallFrameSize().value_or(0) > + static_cast<std::uint64_t>(MaxSigned12)) { + return true; + } + switch (State.FramePointerPolicy) { + case MachineFramePointerPolicy::All: + return true; + case MachineFramePointerPolicy::NonLeaf: + case MachineFramePointerPolicy::NonLeafNoReserve: + return State.HasCalls; + case MachineFramePointerPolicy::None: + case MachineFramePointerPolicy::Reserved: + case MachineFramePointerPolicy::Count: + return false; + } + return false; +} + +[[nodiscard]] bool requiresBasePointer(const MachineFunction &Function) noexcept { + const MachineFrameInfo &Frame = Function.frameInfo(); + const bool HasReservedCallFrame = + !hasVariableObject(Function) && !Frame.state().HasOpaqueSPAdjustment; + return requiresStackRealignment(Function) && + (hasVariableObject(Function) || + (!HasReservedCallFrame && Frame.maxCallFrameSize().value_or(0) != 0)); +} + +[[nodiscard]] Expected<void> validateFrameRegisterUses(const MachineFunction &Function, + const TargetDescription &Target) { + std::vector<PhysicalRegister> FrameRegisters; + if (requiresFramePointer(Function)) { + auto FP = physicalRegister(PhysicalRegisterID::X8); + if (!FP) + return std::unexpected(FP.error()); + FrameRegisters.push_back(*FP); + } + if (requiresBasePointer(Function)) { + auto BP = physicalRegister(PhysicalRegisterID::X9); + if (!BP) + return std::unexpected(BP.error()); + FrameRegisters.push_back(*BP); + } + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + for (const MachineOperand &Operand : Instruction->operands()) { + const Register *Reg = Operand.getIf<Register>(); + if (Reg == nullptr || !Reg->isPhysical()) + continue; + const auto Physical = Reg->physicalRegister(); + if (Physical && std::ranges::any_of(FrameRegisters, [&](PhysicalRegister FrameReg) { + return Target.registers().regsOverlap(*Physical, FrameReg); + })) { + return makeUnexpected( + ErrorCode::VerificationError, + "RISC-V post-allocation code uses a register reserved for frame lowering"); + } + } + } + } + return {}; +} + +[[nodiscard]] bool hasReservedCallFrame(const MachineFunction &Function) noexcept { + const MachineFrameState &State = Function.frameInfo().state(); + return !hasVariableObject(Function) && !State.HasOpaqueSPAdjustment; +} + +[[nodiscard]] Expected<void> computeMaxCallFrameSize(MachineFunction &Function) { + std::uint64_t Maximum = Function.frameInfo().maxCallFrameSize().value_or(0); + if (Maximum == std::numeric_limits<std::uint32_t>::max()) + Maximum = 0; + bool SawPseudo = false; + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + std::optional<CallFrameRequirement> Active; + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const bool IsDown = + Instruction->opcode() == instructionOpcode(InstructionOpcode::ADJCALLSTACKDOWN); + const bool IsUp = + Instruction->opcode() == instructionOpcode(InstructionOpcode::ADJCALLSTACKUP); + if (!IsDown && !IsUp) + continue; + SawPseudo = true; + auto Requirement = callFrameRequirement(*Instruction); + if (!Requirement) + return std::unexpected(Requirement.error()); + if (IsDown) { + if (Active) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call-frame sequences must not be nested"); + } + Active = *Requirement; + Maximum = std::max(Maximum, static_cast<std::uint64_t>(Requirement->Amount)); + continue; + } + if (!Active) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call-frame destroy has no matching setup"); + } + if (Active->Amount != Requirement->Amount || + Active->PreparedBytes != Requirement->PreparedBytes) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call-frame setup and destroy requirements differ"); + } + Active.reset(); + } + if (Active) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call-frame setup has no matching destroy"); + } + } + if (!SawPseudo && !Function.frameInfo().maxCallFrameSize()) + return {}; + return Function.frameInfo().setMaxCallFrameSize(Maximum); +} + +constexpr std::array LP64SaveOrder{ + PhysicalRegisterID::X1, PhysicalRegisterID::X8, PhysicalRegisterID::X9, + PhysicalRegisterID::X18, PhysicalRegisterID::X19, PhysicalRegisterID::X20, + PhysicalRegisterID::X21, PhysicalRegisterID::X22, PhysicalRegisterID::X23, + PhysicalRegisterID::X24, PhysicalRegisterID::X25, PhysicalRegisterID::X26, + PhysicalRegisterID::X27, +}; + +constexpr std::array LP64DSaveOrder{ + PhysicalRegisterID::X1, PhysicalRegisterID::X8, PhysicalRegisterID::X9, + PhysicalRegisterID::X18, PhysicalRegisterID::X19, PhysicalRegisterID::X20, + PhysicalRegisterID::X21, PhysicalRegisterID::X22, PhysicalRegisterID::X23, + PhysicalRegisterID::X24, PhysicalRegisterID::X25, PhysicalRegisterID::X26, + PhysicalRegisterID::X27, PhysicalRegisterID::F8_D, PhysicalRegisterID::F9_D, + PhysicalRegisterID::F18_D, PhysicalRegisterID::F19_D, PhysicalRegisterID::F20_D, + PhysicalRegisterID::F21_D, PhysicalRegisterID::F22_D, PhysicalRegisterID::F23_D, + PhysicalRegisterID::F24_D, PhysicalRegisterID::F25_D, PhysicalRegisterID::F26_D, + PhysicalRegisterID::F27_D, +}; + +constexpr std::array SaveRestoreOrder{ + PhysicalRegisterID::X1, PhysicalRegisterID::X8, PhysicalRegisterID::X9, + PhysicalRegisterID::X18, PhysicalRegisterID::X19, PhysicalRegisterID::X20, + PhysicalRegisterID::X21, PhysicalRegisterID::X22, PhysicalRegisterID::X23, + PhysicalRegisterID::X24, PhysicalRegisterID::X25, PhysicalRegisterID::X26, + PhysicalRegisterID::X27, +}; + +struct ZcmpPushPopInfo final { + std::size_t RegisterCount = 0; + std::int64_t RegisterListEncoding = 0; + std::uint64_t FrameSize = 0; +}; + +[[nodiscard]] std::span<const PhysicalRegisterID> saveOrder(RISCVABI ABI) noexcept { + return ABI == RISCVABI::LP64D ? std::span<const PhysicalRegisterID>(LP64DSaveOrder) + : std::span<const PhysicalRegisterID>(LP64SaveOrder); +} + +[[nodiscard]] std::optional<std::size_t> saveRestoreRegisterIndex(PhysicalRegister Reg) noexcept { + for (std::size_t Index = 0; Index < SaveRestoreOrder.size(); ++Index) { + const auto Candidate = physicalRegister(SaveRestoreOrder[Index]); + if (Candidate && *Candidate == Reg) + return Index; + } + return std::nullopt; +} + +[[nodiscard]] bool zcmpPushPopAllowed(const MachineFunction &Function, + const RISCVSubtargetFeatures &Features) noexcept { + if (!Features.has(RISCVExtension::Zcmp) || requiresFramePointer(Function)) + return false; + const auto *Info = Function.getTargetInfo<RISCVFunctionInfo>(); + return Info == nullptr || Info->varArgsSaveSize() == 0; +} + +[[nodiscard]] std::optional<ZcmpPushPopInfo> +zcmpPushPopInfo(const MachineFunction &Function, std::span<const PhysicalRegister> SavedRegisters, + const RISCVSubtargetFeatures &Features) noexcept { + if (!zcmpPushPopAllowed(Function, Features)) + return std::nullopt; + std::size_t RegisterCount = 0; + for (const PhysicalRegister Reg : SavedRegisters) { + const auto Index = saveRestoreRegisterIndex(Reg); + if (Index) + RegisterCount = std::max(RegisterCount, *Index + 1U); + } + if (RegisterCount == 0) + return std::nullopt; + if (RegisterCount == 12) + RegisterCount = 13; + const std::uint64_t Bytes = static_cast<std::uint64_t>(RegisterCount) * 8U; + return ZcmpPushPopInfo{ + .RegisterCount = RegisterCount, + .RegisterListEncoding = + RegisterCount == 13 ? 15 : static_cast<std::int64_t>(RegisterCount + 3U), + .FrameSize = (Bytes + StackAlignmentBytes - 1U) & ~(StackAlignmentBytes - 1U), + }; +} + +[[nodiscard]] bool zcmpManagesRegister(const ZcmpPushPopInfo &Info, PhysicalRegister Reg) noexcept { + const auto Index = saveRestoreRegisterIndex(Reg); + return Index && *Index < Info.RegisterCount; +} + +[[nodiscard]] Expected<void> +completeZcmpRegisterList(const MachineFunction &Function, const RISCVSubtargetFeatures &Features, + std::vector<PhysicalRegister> &SavedRegisters) { + if (!zcmpPushPopAllowed(Function, Features)) + return {}; + auto X26 = physicalRegister(PhysicalRegisterID::X26); + auto X27 = physicalRegister(PhysicalRegisterID::X27); + if (!X26 || !X27) + return std::unexpected(!X26 ? X26.error() : X27.error()); + if (std::ranges::find(SavedRegisters, *X26) == SavedRegisters.end() || + std::ranges::find(SavedRegisters, *X27) != SavedRegisters.end()) { + return {}; + } + const auto Position = std::ranges::find_if(SavedRegisters, [](PhysicalRegister Reg) { + const auto Index = saveRestoreRegisterIndex(Reg); + return !Index; + }); + SavedRegisters.insert(Position, *X27); + return {}; +} + +[[nodiscard]] std::optional<std::size_t> +saveRestoreLibCallID(std::span<const PhysicalRegister> SavedRegisters, + const RISCVSubtargetFeatures &Features) noexcept { + if (!Features.has(RISCVSubtargetOption::SaveRestore)) + return std::nullopt; + std::optional<std::size_t> Result; + for (const PhysicalRegister Reg : SavedRegisters) { + const auto Index = saveRestoreRegisterIndex(Reg); + if (Index) + Result = std::max(Result.value_or(0), *Index); + } + return Result; +} + +[[nodiscard]] std::uint64_t saveRestoreFrameSize(std::span<const PhysicalRegister> SavedRegisters, + const RISCVSubtargetFeatures &Features) noexcept { + const auto ID = saveRestoreLibCallID(SavedRegisters, Features); + if (!ID) + return 0; + const std::uint64_t Bytes = (static_cast<std::uint64_t>(*ID) + 1U) * 8U; + return (Bytes + StackAlignmentBytes - 1U) & ~(StackAlignmentBytes - 1U); +} + +[[nodiscard]] Expected<std::vector<PhysicalRegister>> +modifiedCalleeSavedRegisters(const MachineFunction &Function, const TargetDescription &Target, + RISCVABI ABI) { + std::unordered_set<PhysicalRegister> Modified; + for (const StackObject &Object : Function.frameInfo().objects()) { + if (!Object.isTombstone() && !Object.description()->IsDead && + Object.description()->CalleeSavedRegister) { + Modified.insert(*Object.description()->CalleeSavedRegister); + } + } + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + for (const MachineOperand &Operand : Instruction->operands()) { + if (const auto *Mask = Operand.getIf<RegisterMaskOperand>(); Mask != nullptr) { + if (!Mask->Mask) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V callee-save analysis found a null register mask"); + } + for (const PhysicalRegisterID ID : saveOrder(ABI)) { + auto Reg = physicalRegister(ID); + if (!Reg) + return std::unexpected(Reg.error()); + if (Mask->Mask->clobbers(*Reg)) + Modified.insert(*Reg); + } + } + const Register *Reg = Operand.getIf<Register>(); + if (Reg == nullptr || !Reg->isPhysical() || !Operand.isDef()) + continue; + const auto Physical = Reg->physicalRegister(); + if (Physical) + Modified.insert(*Physical); + } + } + } + + if (requiresFramePointer(Function)) { + auto RA = physicalRegister(PhysicalRegisterID::X1); + auto FP = physicalRegister(PhysicalRegisterID::X8); + if (!RA || !FP) + return std::unexpected(!RA ? RA.error() : FP.error()); + Modified.insert(*RA); + Modified.insert(*FP); + } + if (requiresBasePointer(Function)) { + auto BP = physicalRegister(PhysicalRegisterID::X9); + if (!BP) + return std::unexpected(BP.error()); + Modified.insert(*BP); + } + + std::vector<PhysicalRegister> Result; + for (const PhysicalRegisterID ID : saveOrder(ABI)) { + auto Reg = physicalRegister(ID); + if (!Reg) + return std::unexpected(Reg.error()); + if (Modified.contains(*Reg)) + Result.push_back(*Reg); + } + for (const PhysicalRegister Reg : Result) { + if (Target.registers().physicalRegister(Reg) == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V callee-save register is absent from target facts"); + } + } + return Result; +} + +[[nodiscard]] Expected<const MachineMemOperand *> +createStackMemory(MachineFunction &Function, FrameIndex Index, MemoryAccessFlag Access) { + auto Size = MemorySize::fixed(8); + auto Align = Alignment::create(8); + auto Type = LowLevelType::makeCategoryNeutralScalar(64); + if (!Size || !Align || !Type) + return makeUnexpected(ErrorCode::InternalError, "RISC-V spill facts are invalid"); + MachineMemOperandDesc Description; + Description.Accesses = {Access}; + Description.Size = *Size; + Description.MemoryType = *Type; + Description.Align = *Align; + Description.Base = MemoryBase::frameIndex(Index); + return Function.createMemOperand(std::move(Description)); +} + +[[nodiscard]] Expected<const MachineMemOperand *> +createResolvedStackMemory(MachineFunction &Function, MemoryAccessFlag Access, std::int64_t Offset) { + auto Size = MemorySize::fixed(8); + auto Align = Alignment::create(8); + auto Type = LowLevelType::makeCategoryNeutralScalar(64); + if (!Size || !Align || !Type) + return makeUnexpected(ErrorCode::InternalError, "RISC-V spill facts are invalid"); + MachineMemOperandDesc Description; + Description.Accesses = {Access}; + Description.Size = *Size; + Description.MemoryType = *Type; + Description.Align = *Align; + Description.Base = MemoryBase::stack(); + Description.Offset = Offset; + return Function.createMemOperand(std::move(Description)); +} + +[[nodiscard]] Expected<void> replaceImmediate(MachineInstr &Instruction, std::size_t Index, + std::int64_t Value) { + const MachineOperand *Old = Instruction.operandAt(Index); + if (Old == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V frame instruction lacks its offset operand"); + const std::uint32_t TargetFlags = Old->targetFlags(); + auto Erased = Instruction.eraseOperand(Index); + if (!Erased) + return std::unexpected(Erased.error()); + return Instruction.insertOperand(Index, MachineOperand::immediate(Value, TargetFlags)); +} + +[[nodiscard]] Expected<void> replaceWithRegister(MachineInstr &Instruction, std::size_t Index, + PhysicalRegister Reg) { + const MachineOperand *Old = Instruction.operandAt(Index); + if (Old == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V frame instruction lacks its frame operand"); + const std::uint32_t TargetFlags = Old->targetFlags(); + auto Replacement = MachineOperand::registerOperand(Register::fromPhysical(Reg), + {RegisterOperandFlag::Use}, {}, TargetFlags); + if (!Replacement) + return std::unexpected(Replacement.error()); + auto Erased = Instruction.eraseOperand(Index); + if (!Erased) + return std::unexpected(Erased.error()); + return Instruction.insertOperand(Index, std::move(*Replacement)); +} + +[[nodiscard]] Expected<void> setFrameFlag(MachineInstrBuilder &Builder, + std::optional<MachineInstrFlag> Flag) { + if (!Flag) + return {}; + return Builder.setFlags({*Flag}); +} + +[[nodiscard]] Expected<DwarfRegisterNumber> dwarfRegister(const TargetDescription &Target, + PhysicalRegister Reg) { + const auto Number = Target.mirText().dwarfRegister(Reg); + if (!Number) { + return makeUnexpected(ErrorCode::InternalError, "RISC-V frame register has no DWARF mapping"); + } + return DwarfRegisterNumber::fromValue(*Number); +} + +[[nodiscard]] Expected<void> emitCFI(MachineIRBuilder &Builder, MachineCFIInstruction Instruction, + MachineInstrFlag Flag) { + auto Index = Builder.function().cfiInfo().addInstruction(std::move(Instruction)); + if (!Index) + return std::unexpected(Index.error()); + auto Pending = Builder.buildInstr(rcc::opcode(GenericOpcode::CFI_INSTRUCTION)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Result = Pending->addCFIInstruction(*Index); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Pending, Flag); !Result) + return std::unexpected(Result.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] Expected<void> emitSavedRegisterCFI(MachineIRBuilder &Builder, + const TargetDescription &Target, + PhysicalRegister Reg, std::int64_t Offset, + MachineInstrFlag Flag) { + auto DwarfReg = dwarfRegister(Target, Reg); + if (!DwarfReg) + return std::unexpected(DwarfReg.error()); + return emitCFI(Builder, MachineCFIInstruction::offset(*DwarfReg, Offset), Flag); +} + +[[nodiscard]] Expected<void> emitRestoreRegisterCFI(MachineIRBuilder &Builder, + const TargetDescription &Target, + PhysicalRegister Reg, MachineInstrFlag Flag) { + auto DwarfReg = dwarfRegister(Target, Reg); + if (!DwarfReg) + return std::unexpected(DwarfReg.error()); + return emitCFI(Builder, MachineCFIInstruction::restore(*DwarfReg), Flag); +} + +[[nodiscard]] Expected<void> emitDefCFI(MachineIRBuilder &Builder, const TargetDescription &Target, + PhysicalRegister Reg, std::int64_t Offset, + MachineInstrFlag Flag) { + auto DwarfReg = dwarfRegister(Target, Reg); + if (!DwarfReg) + return std::unexpected(DwarfReg.error()); + return emitCFI(Builder, MachineCFIInstruction::defCFA(*DwarfReg, Offset), Flag); +} + +[[nodiscard]] Expected<void> emitDefCFAOffset(MachineIRBuilder &Builder, std::int64_t Offset, + MachineInstrFlag Flag) { + return emitCFI(Builder, MachineCFIInstruction::defCFAOffset(Offset), Flag); +} + +[[nodiscard]] Expected<void> emitConstant(MachineIRBuilder &Builder, PhysicalRegister Destination, + PhysicalRegister Zero, std::int64_t Value, + std::optional<MachineInstrFlag> Flag) { + if (Value > MaxSigned12 && Value <= std::numeric_limits<std::int32_t>::max()) { + const std::int64_t Upper = (Value + 0x800) >> 12; + const std::int64_t Lower = Value - (Upper << 12); + auto High = Builder.buildInstr(instructionOpcode(InstructionOpcode::LUI)); + if (!High) + return std::unexpected(High.error()); + if (auto Result = High->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + if (auto Result = High->addImmediate(Upper); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*High, Flag); !Result) + return std::unexpected(Result.error()); + if (auto Result = High->build(); !Result) + return std::unexpected(Result.error()); + if (Lower == 0) + return {}; + auto Low = Builder.buildInstr(instructionOpcode(InstructionOpcode::ADDI)); + if (!Low) + return std::unexpected(Low.error()); + if (auto Result = Low->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + RegisterOperandFlags Killed; + Killed.set(RegisterOperandFlag::Kill); + if (auto Result = Low->addUse(Register::fromPhysical(Destination), Killed); !Result) + return std::unexpected(Result.error()); + if (auto Result = Low->addImmediate(Lower); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Low, Flag); !Result) + return std::unexpected(Result.error()); + auto Built = Low->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; + } + + std::uint64_t Remaining = static_cast<std::uint64_t>(Value); + std::vector<std::int64_t> Digits; + while (!fitsSigned12(std::bit_cast<std::int64_t>(Remaining))) { + std::int64_t Digit = static_cast<std::int64_t>(Remaining & 0xFFFU); + if (Digit >= 0x800) + Digit -= 0x1000; + Digits.push_back(Digit); + const std::uint64_t Adjusted = Remaining - static_cast<std::uint64_t>(Digit); + Remaining = Adjusted >> 12U; + if ((Adjusted & (std::uint64_t{1} << 63U)) != 0) + Remaining |= ~std::uint64_t{0} << 52U; + } + + auto Base = Builder.buildInstr(instructionOpcode(InstructionOpcode::ADDI)); + if (!Base) + return std::unexpected(Base.error()); + if (auto Result = Base->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Base->addUse(Register::fromPhysical(Zero)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Base->addImmediate(std::bit_cast<std::int64_t>(Remaining)); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Base, Flag); !Result) + return std::unexpected(Result.error()); + if (auto Result = Base->build(); !Result) + return std::unexpected(Result.error()); + + for (std::size_t Reverse = Digits.size(); Reverse != 0; --Reverse) { + auto Shift = Builder.buildInstr(instructionOpcode(InstructionOpcode::SLLI)); + if (!Shift) + return std::unexpected(Shift.error()); + if (auto Result = Shift->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + RegisterOperandFlags Killed; + Killed.set(RegisterOperandFlag::Kill); + if (auto Result = Shift->addUse(Register::fromPhysical(Destination), Killed); !Result) + return std::unexpected(Result.error()); + if (auto Result = Shift->addImmediate(12); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Shift, Flag); !Result) + return std::unexpected(Result.error()); + if (auto Result = Shift->build(); !Result) + return std::unexpected(Result.error()); + + auto Add = Builder.buildInstr(instructionOpcode(InstructionOpcode::ADDI)); + if (!Add) + return std::unexpected(Add.error()); + if (auto Result = Add->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Add->addUse(Register::fromPhysical(Destination), Killed); !Result) + return std::unexpected(Result.error()); + if (auto Result = Add->addImmediate(Digits[Reverse - 1U]); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Add, Flag); !Result) + return std::unexpected(Result.error()); + if (auto Result = Add->build(); !Result) + return std::unexpected(Result.error()); + } + return {}; +} + +[[nodiscard]] Expected<void> emitRegisterAdd(MachineIRBuilder &Builder, + PhysicalRegister Destination, PhysicalRegister Left, + PhysicalRegister Right, + std::optional<MachineInstrFlag> Flag, + bool KillRight = false) { + auto Add = Builder.buildInstr(instructionOpcode(InstructionOpcode::ADD)); + if (!Add) + return std::unexpected(Add.error()); + if (auto Result = Add->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Add->addUse(Register::fromPhysical(Left)); !Result) + return std::unexpected(Result.error()); + RegisterOperandFlags RightFlags; + if (KillRight) + RightFlags.set(RegisterOperandFlag::Kill); + if (auto Result = Add->addUse(Register::fromPhysical(Right), RightFlags); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Add, Flag); !Result) + return std::unexpected(Result.error()); + auto Built = Add->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] Expected<void> emitRegisterSub(MachineIRBuilder &Builder, + PhysicalRegister Destination, PhysicalRegister Left, + PhysicalRegister Right, + std::optional<MachineInstrFlag> Flag, + bool KillRight = false) { + auto Sub = Builder.buildInstr(instructionOpcode(InstructionOpcode::SUB)); + if (!Sub) + return std::unexpected(Sub.error()); + if (auto Result = Sub->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Sub->addUse(Register::fromPhysical(Left)); !Result) + return std::unexpected(Result.error()); + RegisterOperandFlags RightFlags; + if (KillRight) + RightFlags.set(RegisterOperandFlag::Kill); + if (auto Result = Sub->addUse(Register::fromPhysical(Right), RightFlags); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Sub, Flag); !Result) + return std::unexpected(Result.error()); + auto Built = Sub->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] Expected<void> emitShiftImmediate(MachineIRBuilder &Builder, Opcode OpcodeValue, + PhysicalRegister Destination, + PhysicalRegister Source, std::int64_t Amount, + std::optional<MachineInstrFlag> Flag) { + auto Shift = Builder.buildInstr(OpcodeValue); + if (!Shift) + return std::unexpected(Shift.error()); + if (auto Result = Shift->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Shift->addUse(Register::fromPhysical(Source)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Shift->addImmediate(Amount); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Shift, Flag); !Result) + return std::unexpected(Result.error()); + auto Built = Shift->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] Expected<void> emitScratchAccess(MachineIRBuilder &Builder, PhysicalRegister Scratch, + FrameIndexReference Reference, bool Restore) { + auto Memory = createResolvedStackMemory( + Builder.function(), Restore ? MemoryAccessFlag::Load : MemoryAccessFlag::Store, + Reference.Offset); + if (!Memory) + return std::unexpected(Memory.error()); + auto Pending = Builder.buildInstr( + instructionOpcode(Restore ? InstructionOpcode::LD : InstructionOpcode::SD)); + if (!Pending) + return std::unexpected(Pending.error()); + auto Value = Restore ? Pending->addDef(Register::fromPhysical(Scratch)) + : Pending->addUse(Register::fromPhysical(Scratch)); + if (!Value) + return std::unexpected(Value.error()); + if (auto Result = Pending->addUse(Register::fromPhysical(Reference.BaseRegister)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addImmediate(Reference.Offset); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addMemOperand(**Memory); !Result) + return std::unexpected(Result.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] Expected<void> emitAddImmediate(MachineIRBuilder &Builder, + PhysicalRegister Destination, PhysicalRegister Source, + std::int64_t Amount, + std::optional<MachineInstrFlag> Flag, + bool KillSource = false) { + auto Add = Builder.buildInstr(instructionOpcode(InstructionOpcode::ADDI)); + if (!Add) + return std::unexpected(Add.error()); + if (auto Result = Add->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + RegisterOperandFlags SourceFlags; + if (KillSource) + SourceFlags.set(RegisterOperandFlag::Kill); + if (auto Result = Add->addUse(Register::fromPhysical(Source), SourceFlags); !Result) + return std::unexpected(Result.error()); + if (auto Result = Add->addImmediate(Amount); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Add, Flag); !Result) + return std::unexpected(Result.error()); + auto Built = Add->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] constexpr bool +canEmitAddImmediateAdjustment(std::int64_t Amount, std::uint64_t RequiredAlignment) noexcept { + if (fitsSigned12(Amount)) + return true; + if (RequiredAlignment == 0 || RequiredAlignment >= 2048U) + return false; + const std::int64_t MaximumPositiveStep = 2048 - static_cast<std::int64_t>(RequiredAlignment); + return Amount > -4096 && Amount <= 2 * MaximumPositiveStep; +} + +[[nodiscard]] Expected<void> +emitAddImmediateAdjustment(MachineIRBuilder &Builder, PhysicalRegister Destination, + PhysicalRegister Source, std::int64_t Amount, + std::uint64_t RequiredAlignment, std::optional<MachineInstrFlag> Flag) { + if (fitsSigned12(Amount)) + return emitAddImmediate(Builder, Destination, Source, Amount, Flag); + if (!canEmitAddImmediateAdjustment(Amount, RequiredAlignment)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V stack adjustment cannot be encoded with ADDI instructions"); + } + const std::int64_t FirstAdjustment = + Amount < 0 ? -2048 : 2048 - static_cast<std::int64_t>(RequiredAlignment); + auto First = emitAddImmediate(Builder, Destination, Source, FirstAdjustment, Flag); + if (!First) + return std::unexpected(First.error()); + return emitAddImmediate(Builder, Destination, Destination, Amount - FirstAdjustment, Flag, true); +} + +[[nodiscard]] Expected<void> emitAndImmediate(MachineIRBuilder &Builder, + PhysicalRegister Destination, PhysicalRegister Source, + std::int64_t Amount, + std::optional<MachineInstrFlag> Flag) { + auto And = Builder.buildInstr(instructionOpcode(InstructionOpcode::ANDI)); + if (!And) + return std::unexpected(And.error()); + if (auto Result = And->addDef(Register::fromPhysical(Destination)); !Result) + return std::unexpected(Result.error()); + if (auto Result = And->addUse(Register::fromPhysical(Source)); !Result) + return std::unexpected(Result.error()); + if (auto Result = And->addImmediate(Amount); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*And, Flag); !Result) + return std::unexpected(Result.error()); + auto Built = And->build(); + if (!Built) + return std::unexpected(Built.error()); + return {}; +} + +[[nodiscard]] std::array<PhysicalRegisterID, 15> scratchOrder() noexcept { + return { + PhysicalRegisterID::X10, PhysicalRegisterID::X11, PhysicalRegisterID::X12, + PhysicalRegisterID::X13, PhysicalRegisterID::X14, PhysicalRegisterID::X15, + PhysicalRegisterID::X16, PhysicalRegisterID::X17, PhysicalRegisterID::X5, + PhysicalRegisterID::X6, PhysicalRegisterID::X7, PhysicalRegisterID::X28, + PhysicalRegisterID::X29, PhysicalRegisterID::X30, PhysicalRegisterID::X31, + }; +} + +[[nodiscard]] Expected<std::vector<PhysicalRegister>> scratchRegisters() { + std::vector<PhysicalRegister> Result; + for (const PhysicalRegisterID ID : scratchOrder()) { + auto Reg = physicalRegister(ID); + if (!Reg) + return std::unexpected(Reg.error()); + Result.push_back(*Reg); + } + return Result; +} + +[[nodiscard]] const StackObject *emergencyObject(const MachineFunction &Function) noexcept { + for (const StackObject &Object : Function.frameInfo().objects()) { + if (!Object.isTombstone() && !Object.description()->IsDead && + Object.description()->Kind == StackObjectKind::EmergencySpill) + return &Object; + } + return nullptr; +} + +[[nodiscard]] Expected<FrameIndex> createEmergencyObject(MachineFunction &Function) { + auto Align = Alignment::create(8); + if (!Align) + return std::unexpected(Align.error()); + StackObjectDesc Description; + Description.Kind = StackObjectKind::EmergencySpill; + Description.Size = 8; + Description.Align = *Align; + return Function.frameInfo().createObject(std::move(Description)); +} + +[[nodiscard]] Expected<std::uint64_t> layoutFrame(MachineFunction &Function, bool ReserveCallFrame, + std::uint64_t ReservedFrameSize, + bool EmitDebugOutput = false) { + MachineFrameInfo &Frame = Function.frameInfo(); + const std::uint64_t CallFrame = ReserveCallFrame ? Frame.maxCallFrameSize().value_or(0) : 0; + std::uint64_t Cursor = ReservedFrameSize; + + std::vector<const StackObject *> Specials; + std::vector<const StackObject *> Locals; + std::vector<const StackObject *> Emergencies; + std::uint64_t MaximumAlignment = requiredStackAlignment(Function); + for (const StackObject &Object : Frame.objects()) { + if (Object.isTombstone() || Object.description()->IsDead) + continue; + const StackObjectDesc &Description = *Object.description(); + if (Description.Stack != StackID::defaultStack()) { + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC does not support non-default stack IDs"); + } + MaximumAlignment = std::max(MaximumAlignment, Description.Align.bytes()); + if (Object.isVariableSized()) + continue; + if (Description.LocalOffset && isOutgoingCallObject(Description)) { + if (*Description.LocalOffset < 0 || + static_cast<std::uint64_t>(*Description.LocalOffset) % Description.Align.bytes() != 0) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V outgoing stack object has an invalid offset"); + } + const std::uint64_t Offset = static_cast<std::uint64_t>(*Description.LocalOffset); + if (Description.Size.value_or(0) > std::numeric_limits<std::uint64_t>::max() - Offset) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V outgoing stack object range overflows"); + } + const std::uint64_t End = Offset + Description.Size.value_or(0); + if (!Frame.maxCallFrameSize() || End > *Frame.maxCallFrameSize()) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V outgoing stack object exceeds the call frame"); + } + continue; + } + if (Object.isFixed()) + continue; + if (Description.Kind == StackObjectKind::CalleeSaved) { + Specials.push_back(&Object); + } else if (Description.Kind == StackObjectKind::EmergencySpill) { + Emergencies.push_back(&Object); + } else { + Locals.push_back(&Object); + } + } + const auto Order = [](const StackObject *Left, const StackObject *Right) { + const StackObjectDesc &L = *Left->description(); + const StackObjectDesc &R = *Right->description(); + if (L.Align.bytes() != R.Align.bytes()) + return L.Align.bytes() > R.Align.bytes(); + if (L.Size != R.Size) + return L.Size.value_or(0) > R.Size.value_or(0); + return Left->index().value() < Right->index().value(); + }; + std::ranges::sort(Locals, Order); + std::ranges::sort(Specials, Order); + std::ranges::sort(Emergencies, Order); + + const auto Place = [&](std::span<const StackObject *const> Objects) -> Expected<void> { + for (const StackObject *Object : Objects) { + const StackObjectDesc &Description = *Object->description(); + if (Description.Size.value_or(0) > std::numeric_limits<std::uint64_t>::max() - Cursor) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, "RISC-V frame size overflows uint64"); + } + Cursor += Description.Size.value_or(0); + auto Aligned = alignTo(Cursor, Description.Align.bytes()); + if (!Aligned) + return std::unexpected(Aligned.error()); + Cursor = *Aligned; + if (Cursor > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V frame offset exceeds signed 64-bit range"); + } + auto Set = Frame.setObjectOffset(Object->index(), -static_cast<std::int64_t>(Cursor)); + if (!Set) + return std::unexpected(Set.error()); + } + return {}; + }; + + auto PlacedSpecials = Place(Specials); + if (!PlacedSpecials) + return std::unexpected(PlacedSpecials.error()); + const bool PlaceEmergenciesBeforeLocals = + requiresFramePointer(Function) && !requiresStackRealignment(Function); + if (PlaceEmergenciesBeforeLocals) { + auto PlacedEmergencies = Place(Emergencies); + if (!PlacedEmergencies) + return std::unexpected(PlacedEmergencies.error()); + } + auto PlacedLocals = Place(Locals); + if (!PlacedLocals) + return std::unexpected(PlacedLocals.error()); + if (!PlaceEmergenciesBeforeLocals) { + auto PlacedEmergencies = Place(Emergencies); + if (!PlacedEmergencies) + return std::unexpected(PlacedEmergencies.error()); + } + + for (const StackObject &Object : Frame.objects()) { + if (Object.isTombstone() || Object.description()->IsDead || Object.isVariableSized()) + continue; + const StackObjectDesc &Description = *Object.description(); + if (Description.LocalOffset && isOutgoingCallObject(Description)) { + auto Set = Frame.setObjectOffset(Object.index(), *Description.LocalOffset); + if (!Set) + return std::unexpected(Set.error()); + } + } + if (CallFrame > std::numeric_limits<std::uint64_t>::max() - Cursor) + return makeUnexpected(ErrorCode::FrameLayoutFailed, "RISC-V call frame size overflows uint64"); + Cursor += CallFrame; + const std::uint64_t SizeBeforeFinalAlignment = Cursor; + auto FinalSize = alignTo(Cursor, MaximumAlignment); + if (!FinalSize) + return std::unexpected(FinalSize.error()); + if (*FinalSize != SizeBeforeFinalAlignment) { + const std::uint64_t Delta = *FinalSize - SizeBeforeFinalAlignment; + if (Delta > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V emergency spill adjustment exceeds int64"); + } + if (EmitDebugOutput && !Emergencies.empty()) + std::cerr << "Adjusting emergency spill slots!\n"; + for (const StackObject *Object : Emergencies) { + if (!Object->description()->Offset) + return makeUnexpected(ErrorCode::InternalError, + "RISC-V emergency spill slot has no preliminary offset"); + const std::int64_t PreliminaryOffset = *Object->description()->Offset; + auto Adjusted = checkedAdd(PreliminaryOffset, -static_cast<std::int64_t>(Delta)); + if (!Adjusted) + return std::unexpected(Adjusted.error()); + if (EmitDebugOutput) { + std::cerr << "Adjusting offset of emergency spill slot #" << Object->index().value() + << " from " << PreliminaryOffset << " to " << *Adjusted << '\n'; + } + auto Set = Frame.setObjectOffset(Object->index(), *Adjusted); + if (!Set) + return std::unexpected(Set.error()); + } + } + if (*FinalSize > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V frame size exceeds signed 64-bit range"); + } + return *FinalSize; +} + +[[nodiscard]] std::uint64_t firstSPAdjustAmount(const MachineFunction &Function, + const RISCVSubtargetFeatures &Features) noexcept { + if (saveRestoreFrameSize(Function.records().calleeSavedRegisters(), Features) != 0 || + zcmpPushPopInfo(Function, Function.records().calleeSavedRegisters(), Features)) + return 0; + const std::uint64_t StackSize = Function.frameInfo().state().StackSize; + if (StackSize <= static_cast<std::uint64_t>(MaxSigned12) || + Function.records().calleeSavedRegisters().empty()) { + return 0; + } + if (Features.has(RISCVExtension::C)) { + const auto CanCompress = [StackSize](std::uint64_t CompressLength) { + return StackSize <= 2047U + CompressLength || + (StackSize > 4096U - StackAlignmentBytes && StackSize <= 4094U + CompressLength) || + StackSize > 6144U - StackAlignmentBytes; + }; + constexpr std::uint64_t AddImmediate16SPCompressLength = 496; + if (CanCompress(AddImmediate16SPCompressLength)) + return AddImmediate16SPCompressLength; + constexpr std::uint64_t RV64StackCompressLength = 512; + if (CanCompress(RV64StackCompressLength)) + return RV64StackCompressLength; + } + return 2048U - StackAlignmentBytes; +} + +[[nodiscard]] Expected<std::int64_t> objectReferenceOffset(const MachineFunction &Function, + const MachineInstr &Instruction, + const StackObjectDesc &Object, + std::int64_t OperandOffset, + const RISCVSubtargetFeatures &Features) { + if (!Object.Offset) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V FrameIndex references an object without a final offset"); + const bool HasFP = requiresFramePointer(Function); + std::int64_t BaseOffset = *Object.Offset; + if (Object.LocalOffset && isOutgoingCallObject(Object)) + BaseOffset = *Object.LocalOffset; + else if (Object.Kind == StackObjectKind::CalleeSaved) { + const std::uint64_t FirstAdjustment = firstSPAdjustAmount(Function, Features); + const std::uint64_t Adjustment = + FirstAdjustment == 0 ? Function.frameInfo().state().StackSize : FirstAdjustment; + if (Adjustment > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V callee-save adjustment exceeds int64"); + } + auto Added = checkedAdd(BaseOffset, static_cast<std::int64_t>(Adjustment)); + if (!Added) + return std::unexpected(Added.error()); + BaseOffset = *Added; + } else if (Object.Kind == StackObjectKind::FixedABI || + Object.Kind == StackObjectKind::FixedSpillSlot) { + if (!HasFP) { + if (Function.frameInfo().state().StackSize > + static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V stack size exceeds signed 64-bit range"); + } + auto Added = + checkedAdd(BaseOffset, static_cast<std::int64_t>(Function.frameInfo().state().StackSize)); + if (!Added) + return std::unexpected(Added.error()); + BaseOffset = *Added; + } + } else if (!HasFP || requiresBasePointer(Function) || requiresStackRealignment(Function) || + Instruction.flags().has(MachineInstrFlag::FrameSetup)) { + if (Function.frameInfo().state().StackSize > + static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V stack size exceeds signed 64-bit range"); + } + auto Added = + checkedAdd(BaseOffset, static_cast<std::int64_t>(Function.frameInfo().state().StackSize)); + if (!Added) + return std::unexpected(Added.error()); + BaseOffset = *Added; + } + return checkedAdd(BaseOffset, OperandOffset); +} + +[[nodiscard]] Expected<FrameIndexReference> emergencyFrameReference(const MachineFunction &Function, + const StackObjectDesc &Object) { + if (!Object.Offset) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V emergency spill object has no final offset"); + } + PhysicalRegisterID BaseID = PhysicalRegisterID::X2; + std::int64_t Offset = *Object.Offset; + if (requiresBasePointer(Function)) { + BaseID = PhysicalRegisterID::X9; + } + if (Function.frameInfo().state().StackSize > + static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V stack size exceeds signed 64-bit range"); + } + auto Adjusted = + checkedAdd(Offset, static_cast<std::int64_t>(Function.frameInfo().state().StackSize)); + if (!Adjusted) + return std::unexpected(Adjusted.error()); + Offset = *Adjusted; + auto Base = physicalRegister(BaseID); + if (!Base) + return std::unexpected(Base.error()); + return FrameIndexReference{*Base, Offset}; +} + +[[nodiscard]] Expected<MachineInstr *> emitCalleeSave(MachineIRBuilder &Builder, + PhysicalRegister Reg, FrameIndex Index, + bool Restore, bool KillValue = false) { + const bool IsFPR = Reg.number() >= static_cast<std::uint32_t>(PhysicalRegisterID::F0_D); + const InstructionOpcode OpcodeValue = + Restore ? (IsFPR ? InstructionOpcode::FLD : InstructionOpcode::LD) + : (IsFPR ? InstructionOpcode::FSD : InstructionOpcode::SD); + auto Memory = createStackMemory(Builder.function(), Index, + Restore ? MemoryAccessFlag::Load : MemoryAccessFlag::Store); + if (!Memory) + return std::unexpected(Memory.error()); + auto Pending = Builder.buildInstr(instructionOpcode(OpcodeValue)); + if (!Pending) + return std::unexpected(Pending.error()); + const Register Value = Register::fromPhysical(Reg); + RegisterOperandFlags ValueFlags; + if (!Restore && KillValue) + ValueFlags.set(RegisterOperandFlag::Kill); + auto RegisterResult = Restore ? Pending->addDef(Value) : Pending->addUse(Value, ValueFlags); + if (!RegisterResult) + return std::unexpected(RegisterResult.error()); + if (auto Result = Pending->addFrameIndex(Index); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addImmediate(0); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addMemOperand(**Memory); !Result) + return std::unexpected(Result.error()); + if (auto Result = setFrameFlag(*Pending, Restore ? MachineInstrFlag::FrameDestroy + : MachineInstrFlag::FrameSetup); + !Result) { + return std::unexpected(Result.error()); + } + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + return *Built; +} + +[[nodiscard]] std::optional<FrameIndex> calleeSaveSlot(const MachineFunction &Function, + PhysicalRegister Reg) noexcept { + for (const StackObject &Object : Function.frameInfo().objects()) { + if (Object.isTombstone() || Object.description()->IsDead) + continue; + if (Object.description()->CalleeSavedRegister == Reg) + return Object.index(); + } + return std::nullopt; +} + +[[nodiscard]] bool isABIStackSaveRegister(PhysicalRegister Reg, RISCVABI ABI) { + return std::ranges::any_of(saveOrder(ABI), [Reg](PhysicalRegisterID ID) { + auto Candidate = physicalRegister(ID); + return Candidate && *Candidate == Reg; + }); +} + +[[nodiscard]] Expected<void> +validateCalleeSaveObjects(const MachineFunction &Function, + std::span<const PhysicalRegister> SavedRegisters, RISCVABI ABI, + const RISCVSubtargetFeatures &Features, + const std::optional<ZcmpPushPopInfo> &ZcmpInfo = std::nullopt) { + std::unordered_set<PhysicalRegister> Slots; + for (const StackObject &Object : Function.frameInfo().objects()) { + if (Object.isTombstone() || Object.description()->IsDead || + !Object.description()->CalleeSavedRegister) { + continue; + } + const StackObjectDesc &Description = *Object.description(); + const PhysicalRegister Reg = *Description.CalleeSavedRegister; + if (!isABIStackSaveRegister(Reg, ABI)) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V callee-save object names a caller-saved register"); + } + const auto SaveRestoreIndex = saveRestoreRegisterIndex(Reg); + std::optional<std::int64_t> ExpectedOffset; + if (ZcmpInfo && SaveRestoreIndex && *SaveRestoreIndex < ZcmpInfo->RegisterCount) { + ExpectedOffset = -static_cast<std::int64_t>(ZcmpInfo->RegisterCount - *SaveRestoreIndex) * 8; + } + const bool ManagedByZcmp = ExpectedOffset.has_value(); + const bool ManagedByLibCall = !ZcmpInfo && Features.has(RISCVSubtargetOption::SaveRestore) && + SaveRestoreIndex.has_value(); + const StackObjectKind ExpectedKind = ManagedByZcmp || ManagedByLibCall + ? StackObjectKind::FixedSpillSlot + : StackObjectKind::CalleeSaved; + if (ManagedByLibCall) { + ExpectedOffset = -static_cast<std::int64_t>(*SaveRestoreIndex + 1U) * 8; + } + if (Description.Kind != ExpectedKind || Description.Size != std::optional<std::uint64_t>{8} || + Description.Align.bytes() < 8 || Description.Stack != StackID::defaultStack() || + ((ManagedByZcmp || ManagedByLibCall) && Description.Offset != ExpectedOffset)) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V callee-save object has an invalid kind, size, or alignment"); + } + if (!Slots.insert(Reg).second) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V callee-save register has duplicate stack objects"); + } + if (std::ranges::find(SavedRegisters, Reg) == SavedRegisters.end()) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V callee-save object was omitted from save analysis"); + } + } + return {}; +} + +[[nodiscard]] bool calleeSaveIsRestored(const MachineFunction &Function, + PhysicalRegister Reg) noexcept { + const auto Slot = calleeSaveSlot(Function, Reg); + const StackObject *Object = Slot ? Function.frameInfo().object(*Slot) : nullptr; + return Object != nullptr && Object->description()->CalleeSavedRestored; +} + +struct CalleeSaveOperation final { + MachineInstr *Instruction = nullptr; + PhysicalRegister Reg; +}; + +[[nodiscard]] std::vector<CalleeSaveOperation> +calleeSaveOperations(MachineBasicBlock &Block, const MachineFunction &Function, bool Restore) { + std::vector<CalleeSaveOperation> Result; + for (MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (!Instruction->flags().has(Restore ? MachineInstrFlag::FrameDestroy + : MachineInstrFlag::FrameSetup)) { + continue; + } + for (const MachineOperand &Operand : Instruction->operands()) { + const auto *Frame = Operand.getIf<FrameIndexOperand>(); + if (Frame == nullptr) + continue; + const StackObject *Object = Function.frameInfo().object(Frame->Index); + if (Object != nullptr && Object->description()->CalleeSavedRegister) { + Result.push_back( + CalleeSaveOperation{Instruction, *Object->description()->CalleeSavedRegister}); + break; + } + } + } + return Result; +} + +[[nodiscard]] MachineBasicBlock *blockWithID(MachineFunction &Function, + MachineBlockStableID ID) noexcept { + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) { + if (Block->stableID() == ID) + return Block; + } + return nullptr; +} + +[[nodiscard]] MachineBasicBlock *frameSaveBlock(MachineFunction &Function) noexcept { + const auto &SavePoints = Function.frameInfo().state().SavePoints; + return SavePoints.empty() ? Function.firstBlock() + : blockWithID(Function, SavePoints.front().Block); +} + +[[nodiscard]] bool isFrameRestoreBlock(const MachineFunction &Function, + const MachineBasicBlock &Block) noexcept { + const MachineFrameState &State = Function.frameInfo().state(); + if (State.RestorePoints.empty()) + return State.SavePoints.empty(); + return std::ranges::any_of(State.RestorePoints, + [&Block](const MachineFrameSaveRestorePoint &Point) { + return Point.Block == Block.stableID(); + }); +} + +class RISCVFrameLowering final : public TargetFrameLowering { +public: + explicit RISCVFrameLowering(const RISCVSubtarget &Subtarget) noexcept : Subtarget(Subtarget) {} + + [[nodiscard]] PhysicalRegister + initialCFARegister(const MachineFunction &, + const TargetDescription &Target) const noexcept override { + return Target.stackPointerRegisterToSaveRestore(); + } + + [[nodiscard]] bool + isCalleeSavedRegister(PhysicalRegister Reg, const MachineFunction &, + const TargetDescription &Target) const noexcept override { + return std::ranges::any_of(saveOrder(Subtarget.targetABI()), [&](PhysicalRegisterID ID) { + const auto Candidate = physicalRegister(ID); + return Candidate && Target.registers().regsOverlap(Reg, *Candidate); + }); + } + + [[nodiscard]] Expected<void> prepareFrame(MachineFunction &Function, + const TargetDescription &Target, + FrameLoweringOptions Options) const override { + MachineFrameInfo &Frame = Function.frameInfo(); + if (Frame.isFrozen()) + return makeUnexpected(ErrorCode::InvalidArgument, "RISC-V frame layout is already frozen"); + + MachineFrameState State = Frame.state(); + const std::vector<MachineFrameSaveRestorePoint> RequestedSavePoints = State.SavePoints; + const std::vector<MachineFrameSaveRestorePoint> RequestedRestorePoints = State.RestorePoints; + const bool HasRequestedPlacement = + !RequestedSavePoints.empty() || !RequestedRestorePoints.empty(); + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = + Target.instructionDescriptor(Instruction->opcode()); + if (Descriptor != nullptr && Descriptor->Flags.has(InstructionFlag::Call)) + State.HasCalls = true; + } + } + auto UpdatedState = Frame.setState(State); + if (!UpdatedState) + return std::unexpected(UpdatedState.error()); + + auto CallFrame = computeMaxCallFrameSize(Function); + if (!CallFrame) + return std::unexpected(CallFrame.error()); + if (requiresFramePointer(Function)) { + auto FP = physicalRegister(PhysicalRegisterID::X8); + if (!FP) + return std::unexpected(FP.error()); + if (!Function.registerInfo().isReserved(*FP)) { + auto Reserved = Function.registerInfo().reservePhysicalRegister(*FP); + if (!Reserved) + return std::unexpected(Reserved.error()); + } + } + if (requiresBasePointer(Function)) { + auto BP = physicalRegister(PhysicalRegisterID::X9); + if (!BP) + return std::unexpected(BP.error()); + if (!Function.registerInfo().isReserved(*BP)) { + auto Reserved = Function.registerInfo().reservePhysicalRegister(*BP); + if (!Reserved) + return std::unexpected(Reserved.error()); + } + } + auto FrameRegisters = validateFrameRegisterUses(Function, Target); + if (!FrameRegisters) + return std::unexpected(FrameRegisters.error()); + + auto SavedRegisters = modifiedCalleeSavedRegisters(Function, Target, Subtarget.targetABI()); + if (!SavedRegisters) + return std::unexpected(SavedRegisters.error()); + auto CompletedZcmpList = + completeZcmpRegisterList(Function, Subtarget.features(), *SavedRegisters); + if (!CompletedZcmpList) + return std::unexpected(CompletedZcmpList.error()); + const auto ZcmpInfo = zcmpPushPopInfo(Function, *SavedRegisters, Subtarget.features()); + auto Records = Function.records().setCalleeSavedRegisters(*SavedRegisters); + if (!Records) + return std::unexpected(Records.error()); + auto ExistingSlots = validateCalleeSaveObjects(Function, *SavedRegisters, Subtarget.targetABI(), + Subtarget.features(), ZcmpInfo); + if (!ExistingSlots) + return std::unexpected(ExistingSlots.error()); + auto Align8 = Alignment::create(8); + auto Align16 = Alignment::create(16); + if (!Align8 || !Align16) + return std::unexpected(!Align8 ? Align8.error() : Align16.error()); + for (const PhysicalRegister Reg : *SavedRegisters) { + if (calleeSaveSlot(Function, Reg)) + continue; + StackObjectDesc Description; + const auto SaveRestoreIndex = saveRestoreRegisterIndex(Reg); + std::optional<std::int64_t> ZcmpOffset; + if (ZcmpInfo && SaveRestoreIndex && *SaveRestoreIndex < ZcmpInfo->RegisterCount) { + ZcmpOffset = -static_cast<std::int64_t>(ZcmpInfo->RegisterCount - *SaveRestoreIndex) * 8; + } + const bool ManagedByZcmp = ZcmpOffset.has_value(); + const bool ManagedByLibCall = !ZcmpInfo && + Subtarget.features().has(RISCVSubtargetOption::SaveRestore) && + SaveRestoreIndex; + Description.Kind = ManagedByZcmp || ManagedByLibCall ? StackObjectKind::FixedSpillSlot + : StackObjectKind::CalleeSaved; + Description.Size = 8; + Description.Align = + ManagedByLibCall && (*SaveRestoreIndex + 1U) % 2U == 0 ? *Align16 : *Align8; + if (ManagedByZcmp) { + Description.Offset = ZcmpOffset; + } else if (ManagedByLibCall) { + Description.Offset = -static_cast<std::int64_t>(*SaveRestoreIndex + 1U) * 8; + } + Description.CalleeSavedRegister = Reg; + auto Created = Frame.createObject(std::move(Description)); + if (!Created) + return std::unexpected(Created.error()); + } + const std::uint64_t ReservedFrameSize = + ZcmpInfo ? ZcmpInfo->FrameSize + : saveRestoreFrameSize(*SavedRegisters, Subtarget.features()); + if (ReservedFrameSize != 0) { + const bool HasCoveringObject = + std::ranges::any_of(Frame.objects(), [&](const StackObject &O) { + const StackObjectDesc *Description = O.description(); + return Description != nullptr && !Description->IsDead && + Description->Kind == StackObjectKind::FixedSpillSlot && + !Description->CalleeSavedRegister && + Description->Size == std::optional<std::uint64_t>{ReservedFrameSize} && + Description->Offset == + std::optional<std::int64_t>{-static_cast<std::int64_t>(ReservedFrameSize)}; + }); + if (!HasCoveringObject) { + StackObjectDesc Description; + Description.Kind = StackObjectKind::FixedSpillSlot; + Description.Size = ReservedFrameSize; + Description.Align = *Align16; + Description.Offset = -static_cast<std::int64_t>(ReservedFrameSize); + auto Created = Frame.createObject(std::move(Description)); + if (!Created) + return std::unexpected(Created.error()); + } + } + auto CompleteSlots = validateCalleeSaveObjects(Function, *SavedRegisters, Subtarget.targetABI(), + Subtarget.features(), ZcmpInfo); + if (!CompleteSlots) + return std::unexpected(CompleteSlots.error()); + + const bool ReserveCallFrame = hasReservedCallFrame(Function); + auto StackSize = + layoutFrame(Function, ReserveCallFrame, ReservedFrameSize, Options.EmitDebugOutput); + if (!StackSize) + return std::unexpected(StackSize.error()); + + State = Frame.state(); + State.StackSize = *StackSize; + auto RequiredAlignment = Alignment::create(requiredStackAlignment(Function)); + if (!RequiredAlignment) + return std::unexpected(RequiredAlignment.error()); + State.MaxAlignment = *RequiredAlignment; + auto StateForReferences = Frame.setState(State); + if (!StateForReferences) + return std::unexpected(StateForReferences.error()); + + bool NeedsEmergency = *StackSize > static_cast<std::uint64_t>(MaxSigned11); + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + for (std::size_t Index = 0; Index < Instruction->operandCount(); ++Index) { + const auto *FrameOperand = Instruction->operandAt(Index)->getIf<FrameIndexOperand>(); + if (FrameOperand == nullptr) + continue; + std::int64_t Extra = FrameOperand->Offset; + if (Index + 1U >= Instruction->operandCount()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V frame operand lacks an adjacent immediate"); + } + auto Immediate = immediateValue(*Instruction->operandAt(Index + 1U)); + if (!Immediate) + return std::unexpected(Immediate.error()); + auto Added = checkedAdd(Extra, *Immediate); + if (!Added) + return std::unexpected(Added.error()); + Extra = *Added; + const StackObject *Object = Frame.object(FrameOperand->Index); + if (Object == nullptr || Object->description()->IsDead) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V frame instruction references an invalid object"); + auto Offset = objectReferenceOffset(Function, *Instruction, *Object->description(), Extra, + Subtarget.features()); + if (!Offset) + return std::unexpected(Offset.error()); + NeedsEmergency |= !fitsSigned12(*Offset); + } + } + } + if (NeedsEmergency && emergencyObject(Function) == nullptr) { + auto Emergency = createEmergencyObject(Function); + if (!Emergency) + return std::unexpected(Emergency.error()); + StackSize = + layoutFrame(Function, ReserveCallFrame, ReservedFrameSize, Options.EmitDebugOutput); + if (!StackSize) + return std::unexpected(StackSize.error()); + } + + State = Frame.state(); + State.StackSize = *StackSize; + if (*StackSize < ReservedFrameSize) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V managed callee-save frame exceeds the complete frame"); + } + State.AdjustsStack = State.AdjustsStack || *StackSize != 0; + State.IsCalleeSavedInfoValid = true; + State.SavePoints.clear(); + State.RestorePoints.clear(); + std::vector<PhysicalRegister> RestoredRegisters; + std::ranges::copy_if( + *SavedRegisters, std::back_inserter(RestoredRegisters), + [&Function](PhysicalRegister Reg) { return calleeSaveIsRestored(Function, Reg); }); + if (HasRequestedPlacement) { + if (RequestedSavePoints.size() != 1 || RequestedRestorePoints.empty()) { + return makeUnexpected( + ErrorCode::VerificationError, + "RISC-V shrink-wrapped frame requires one save point and at least one restore point"); + } + State.SavePoints.push_back({RequestedSavePoints.front().Block, *SavedRegisters}); + for (const MachineFrameSaveRestorePoint &Point : RequestedRestorePoints) + State.RestorePoints.push_back({Point.Block, RestoredRegisters}); + } else { + if (!SavedRegisters->empty() && Function.firstBlock() != nullptr) { + State.SavePoints.push_back({Function.firstBlock()->stableID(), *SavedRegisters}); + } + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + bool HasEpilogue = false; + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + HasEpilogue |= isEpilogueTerminator(*Instruction, Target); + } + if (HasEpilogue && !RestoredRegisters.empty()) { + State.RestorePoints.push_back({Block->stableID(), RestoredRegisters}); + } + } + } + auto SetState = Frame.setState(std::move(State)); + if (!SetState) + return std::unexpected(SetState.error()); + return {}; + } + + [[nodiscard]] Expected<void> + insertCalleeSavedSpillsAndRestores(MachineFunction &Function, + const TargetDescription &Target) const override { + if (Function.frameInfo().isFrozen()) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V callee saves must be inserted before frame freeze"); + } + const std::span<const PhysicalRegister> Saved = Function.records().calleeSavedRegisters(); + if (Saved.empty()) + return {}; + MachineBasicBlock *SaveBlock = frameSaveBlock(Function); + if (SaveBlock == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V callee-save frame has no valid save block"); + } + auto FP = physicalRegister(PhysicalRegisterID::X8); + auto BP = physicalRegister(PhysicalRegisterID::X9); + if (!FP || !BP) + return makeUnexpected(ErrorCode::InternalError, "RISC-V frame registers are unavailable"); + const bool Realign = requiresStackRealignment(Function); + + std::unordered_set<PhysicalRegister> KilledSaves; + if (Function.properties().has(MachineFunctionProperty::TracksLiveness)) { + for (const PhysicalRegister Reg : Saved) { + const bool IsLiveOnEntry = + std::ranges::any_of(SaveBlock->liveIns(), [&](const MachineBasicBlockLiveIn &LiveIn) { + return Target.registers().regsOverlap(Reg, LiveIn.PhysReg); + }); + if (!IsLiveOnEntry) + KilledSaves.insert(Reg); + } + } + + if (Function.properties().has(MachineFunctionProperty::TracksLiveness)) { + for (const PhysicalRegister Reg : Saved) { + if (Function.registerInfo().isReserved(Reg)) + continue; + auto LiveIn = SaveBlock->addLiveIn(Reg); + if (!LiveIn) + return std::unexpected(LiveIn.error()); + } + } + + MachineIRBuilder SaveBuilder(Function, Target); + auto SavePoint = SaveBlock->firstInstruction() != nullptr + ? SaveBuilder.setInsertPointBefore(*SaveBlock->firstInstruction()) + : SaveBuilder.setInsertPoint(*SaveBlock); + if (!SavePoint) + return std::unexpected(SavePoint.error()); + const auto ZcmpInfo = zcmpPushPopInfo(Function, Saved, Subtarget.features()); + const auto LibCallID = + ZcmpInfo ? std::nullopt : saveRestoreLibCallID(Saved, Subtarget.features()); + if (ZcmpInfo) { + auto Pending = SaveBuilder.buildInstr(instructionOpcode(InstructionOpcode::CM_PUSH)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Result = Pending->addImmediate(ZcmpInfo->RegisterListEncoding); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addImmediate(0); !Result) + return std::unexpected(Result.error()); + for (std::size_t Index = 0; Index < ZcmpInfo->RegisterCount; ++Index) { + auto Reg = physicalRegister(SaveRestoreOrder[Index]); + if (!Reg) + return std::unexpected(Reg.error()); + if (auto Result = Pending->addImplicitUse(Register::fromPhysical(*Reg)); !Result) + return std::unexpected(Result.error()); + } + if (auto Result = setFrameFlag(*Pending, MachineInstrFlag::FrameSetup); !Result) + return std::unexpected(Result.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + } else if (LibCallID) { + auto Link = physicalRegister(PhysicalRegisterID::X5); + auto SP = physicalRegister(PhysicalRegisterID::X2); + if (!Link || !SP) + return std::unexpected(!Link ? Link.error() : SP.error()); + auto Pending = SaveBuilder.buildInstr(instructionOpcode(InstructionOpcode::PseudoCALLReg)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Result = Pending->addDef(Register::fromPhysical(*Link)); !Result) + return std::unexpected(Result.error()); + if (auto Result = + Pending->addExternalSymbol("__riscv_save_" + std::to_string(*LibCallID), 0, + static_cast<std::uint32_t>(CallTargetFlag::DirectCall)); + !Result) { + return std::unexpected(Result.error()); + } + if (auto Result = Pending->addImplicitUse(Register::fromPhysical(*SP)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addImplicitDef(Register::fromPhysical(*SP)); !Result) + return std::unexpected(Result.error()); + for (const PhysicalRegister Reg : Saved) { + if (auto Result = Pending->addImplicitUse(Register::fromPhysical(Reg)); !Result) + return std::unexpected(Result.error()); + } + if (auto Result = setFrameFlag(*Pending, MachineInstrFlag::FrameSetup); !Result) + return std::unexpected(Result.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + } + for (const PhysicalRegister Reg : Saved) { + if (ZcmpInfo && zcmpManagesRegister(*ZcmpInfo, Reg)) + continue; + if (LibCallID && saveRestoreRegisterIndex(Reg)) + continue; + const auto Slot = calleeSaveSlot(Function, Reg); + if (!Slot) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V callee-save register has no stack slot"); + } + auto Stored = emitCalleeSave(SaveBuilder, Reg, *Slot, false, KilledSaves.contains(Reg)); + if (!Stored) + return std::unexpected(Stored.error()); + } + + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + if (!isFrameRestoreBlock(Function, *Block)) + continue; + std::vector<MachineInstr *> Terminators; + for (MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (isEpilogueTerminator(*Instruction, Target)) + Terminators.push_back(Instruction); + } + for (MachineInstr *Terminator : Terminators) { + MachineIRBuilder RestoreBuilder(Function, Target); + auto Point = RestoreBuilder.setInsertPointBefore(*Terminator); + if (!Point) + return std::unexpected(Point.error()); + const auto EmitRestore = [&](PhysicalRegister Reg) -> Expected<void> { + if (!calleeSaveIsRestored(Function, Reg)) + return {}; + if (ZcmpInfo && zcmpManagesRegister(*ZcmpInfo, Reg)) + return {}; + if (LibCallID && saveRestoreRegisterIndex(Reg)) + return {}; + const auto Slot = calleeSaveSlot(Function, Reg); + if (!Slot) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V callee-save register has no restore slot"); + } + auto Loaded = emitCalleeSave(RestoreBuilder, Reg, *Slot, true); + if (!Loaded) + return std::unexpected(Loaded.error()); + return {}; + }; + for (const PhysicalRegister Reg : Saved) { + if ((requiresFramePointer(Function) && Reg == *FP) || (Realign && Reg == *BP)) + continue; + auto Loaded = EmitRestore(Reg); + if (!Loaded) + return std::unexpected(Loaded.error()); + } + if (requiresFramePointer(Function)) { + auto Loaded = EmitRestore(*FP); + if (!Loaded) + return std::unexpected(Loaded.error()); + } + if (Realign) { + auto Loaded = EmitRestore(*BP); + if (!Loaded) + return std::unexpected(Loaded.error()); + } + if (ZcmpInfo) { + auto Pending = RestoreBuilder.buildInstr(instructionOpcode(InstructionOpcode::CM_POP)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Result = Pending->addImmediate(ZcmpInfo->RegisterListEncoding); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addImmediate(0); !Result) + return std::unexpected(Result.error()); + for (std::size_t Index = 0; Index < ZcmpInfo->RegisterCount; ++Index) { + auto Reg = physicalRegister(SaveRestoreOrder[Index]); + if (!Reg) + return std::unexpected(Reg.error()); + if (auto Result = Pending->addImplicitDef(Register::fromPhysical(*Reg)); !Result) + return std::unexpected(Result.error()); + } + if (auto Result = setFrameFlag(*Pending, MachineInstrFlag::FrameDestroy); !Result) + return std::unexpected(Result.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + } + } + } + return {}; + } + + [[nodiscard]] Expected<void> finalizeFrame(MachineFunction &Function, + const TargetDescription &) const override { + MachineFrameInfo &Frame = Function.frameInfo(); + if (Frame.isFrozen()) + return makeUnexpected(ErrorCode::InternalError, + "RISC-V frame was frozen before final layout"); + std::vector<std::optional<std::int64_t>> PreliminaryOffsets; + PreliminaryOffsets.reserve(Frame.objects().size()); + for (const StackObject &Object : Frame.objects()) { + PreliminaryOffsets.push_back(Object.isTombstone() ? std::nullopt + : Object.description()->Offset); + } + const std::uint64_t PreliminarySize = Frame.state().StackSize; + const auto ZcmpInfo = + zcmpPushPopInfo(Function, Function.records().calleeSavedRegisters(), Subtarget.features()); + const std::uint64_t ReservedFrameSize = + ZcmpInfo + ? ZcmpInfo->FrameSize + : saveRestoreFrameSize(Function.records().calleeSavedRegisters(), Subtarget.features()); + auto StackSize = layoutFrame(Function, hasReservedCallFrame(Function), ReservedFrameSize); + if (!StackSize) + return std::unexpected(StackSize.error()); + if (*StackSize != PreliminarySize || Frame.objects().size() != PreliminaryOffsets.size()) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V frame resources changed after preliminary layout"); + } + for (const StackObject &Object : Frame.objects()) { + if (!Object.isTombstone() && + Object.description()->Offset != PreliminaryOffsets[Object.index().value()]) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V frame offsets changed after callee-save insertion"); + } + } + auto StackAlignment = Alignment::create(requiredStackAlignment(Function)); + if (!StackAlignment) + return std::unexpected(StackAlignment.error()); + return Frame.freeze(*StackSize, *StackAlignment); + } + + [[nodiscard]] bool isCallFramePseudo(const MachineInstr &Instruction) const noexcept override { + return Instruction.opcode() == instructionOpcode(InstructionOpcode::ADJCALLSTACKDOWN) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::ADJCALLSTACKUP); + } + + [[nodiscard]] Expected<void> + eliminateCallFramePseudo(MachineInstr &Instruction, MachineFunction &Function, + const TargetDescription &Target, + RegisterScavenger &Scavenger) const override { + MachineBasicBlock *Block = Instruction.parent(); + if (Block == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "RISC-V call-frame pseudo is detached"); + if (!hasReservedCallFrame(Function)) { + auto Amount = callFrameAmount(Instruction); + if (!Amount) + return std::unexpected(Amount.error()); + std::int64_t Adjustment = *Amount; + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::ADJCALLSTACKDOWN)) + Adjustment = -Adjustment; + auto Adjusted = + emitStackAdjustment(Instruction, Function, Target, Scavenger, Adjustment, std::nullopt); + if (!Adjusted) + return std::unexpected(Adjusted.error()); + } + return Instruction.eraseFromParent(); + } + + [[nodiscard]] Expected<void> emitPrologue(MachineFunction &Function, + const TargetDescription &Target, + RegisterScavenger &Scavenger) const override { + MachineBasicBlock *SaveBlock = frameSaveBlock(Function); + if (SaveBlock == nullptr) + return Function.frameInfo().state().StackSize == 0 + ? Expected<void>{} + : makeUnexpected(ErrorCode::VerificationError, + "RISC-V nonempty frame has no save block"); + const std::vector<CalleeSaveOperation> Saves = + calleeSaveOperations(*SaveBlock, Function, false); + const std::span<const PhysicalRegister> SavedRegisters = + Function.records().calleeSavedRegisters(); + const auto ZcmpInfo = zcmpPushPopInfo(Function, SavedRegisters, Subtarget.features()); + const auto LibCallID = + ZcmpInfo ? std::nullopt : saveRestoreLibCallID(SavedRegisters, Subtarget.features()); + const std::size_t ManualSaveCount = + static_cast<std::size_t>(std::ranges::count_if(SavedRegisters, [&](PhysicalRegister Reg) { + return (!ZcmpInfo || !zcmpManagesRegister(*ZcmpInfo, Reg)) && + (!LibCallID || !saveRestoreRegisterIndex(Reg)); + })); + if (Saves.size() != ManualSaveCount) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V prologue has an incomplete callee-save sequence"); + } + MachineInstr *Push = nullptr; + MachineInstr *SpillLibCall = nullptr; + if (ZcmpInfo) { + for (MachineInstr *Instruction = SaveBlock->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (Instruction->opcode() == instructionOpcode(InstructionOpcode::CM_PUSH) && + Instruction->flags().has(MachineInstrFlag::FrameSetup)) { + if (Push != nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V prologue has duplicate Zcmp pushes"); + } + Push = Instruction; + } + } + if (Push == nullptr) { + return makeUnexpected(ErrorCode::InternalError, "RISC-V prologue is missing its Zcmp push"); + } + } else if (LibCallID) { + for (MachineInstr *Instruction = SaveBlock->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (Instruction->opcode() == instructionOpcode(InstructionOpcode::PseudoCALLReg) && + Instruction->flags().has(MachineInstrFlag::FrameSetup)) { + if (SpillLibCall != nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V prologue has duplicate save libcalls"); + } + SpillLibCall = Instruction; + } + } + if (SpillLibCall == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V prologue is missing its save libcall"); + } + } + MachineInstr *Anchor = SaveBlock->firstInstruction(); + MachineIRBuilder Builder(Function, Target); + auto Point = Push != nullptr ? Builder.setInsertPointAfter(*Push) + : (SpillLibCall != nullptr + ? Builder.setInsertPointAfter(*SpillLibCall) + : (Anchor != nullptr ? Builder.setInsertPointBefore(*Anchor) + : Builder.setInsertPoint(*SaveBlock))); + if (!Point) + return std::unexpected(Point.error()); + + const std::uint64_t StackSize = Function.frameInfo().state().StackSize; + const std::uint64_t StackAlignment = requiredStackAlignment(Function); + const bool Realign = requiresStackRealignment(Function); + auto SP = physicalRegister(PhysicalRegisterID::X2); + auto FP = physicalRegister(PhysicalRegisterID::X8); + auto BP = physicalRegister(PhysicalRegisterID::X9); + auto Zero = physicalRegister(PhysicalRegisterID::X0); + auto DefaultTemporary = physicalRegister(PhysicalRegisterID::X6); + if (!SP || !FP || !BP || !Zero || !DefaultTemporary) { + return makeUnexpected(ErrorCode::InternalError, "RISC-V frame registers are unavailable"); + } + PhysicalRegister Temporary = *DefaultTemporary; + std::optional<ScavengedRegister> Scratch; + const std::uint64_t BaseReservedFrameSize = + ZcmpInfo ? ZcmpInfo->FrameSize : saveRestoreFrameSize(SavedRegisters, Subtarget.features()); + if (StackSize < BaseReservedFrameSize) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V managed callee-save frame exceeds the complete frame"); + } + std::uint64_t ZcmpExtraAdjustment = 0; + if (Push != nullptr) { + ZcmpExtraAdjustment = + std::min((StackSize - BaseReservedFrameSize) & ~std::uint64_t{15}, std::uint64_t{48}); + MachineOperand *Adjustment = Push->operandAt(1); + if (Adjustment == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V Zcmp push lacks its stack adjustment"); + } + auto SetAdjustment = Adjustment->setImmediate(static_cast<std::int64_t>(ZcmpExtraAdjustment)); + if (!SetAdjustment) + return std::unexpected(SetAdjustment.error()); + } + const std::uint64_t ReservedFrameSize = BaseReservedFrameSize + ZcmpExtraAdjustment; + const std::uint64_t ManagedStackSize = StackSize - ReservedFrameSize; + const std::uint64_t FirstAdjustment = firstSPAdjustAmount(Function, Subtarget.features()); + const std::uint64_t InitialAllocation = + FirstAdjustment == 0 ? ManagedStackSize : FirstAdjustment; + const std::uint64_t SecondAdjustment = ManagedStackSize - InitialAllocation; + const std::uint64_t FramePointerAdjustment = ReservedFrameSize + InitialAllocation; + const std::int64_t InitialAmount = -static_cast<std::int64_t>(InitialAllocation); + const std::int64_t SecondAmount = -static_cast<std::int64_t>(SecondAdjustment); + const std::int64_t AlignmentMask = std::bit_cast<std::int64_t>(~(StackAlignment - 1U)); + const bool NeedsTemporary = + !canEmitAddImmediateAdjustment(InitialAmount, StackAlignmentBytes) || + !canEmitAddImmediateAdjustment(SecondAmount, StackAlignmentBytes) || + (!Realign && requiresFramePointer(Function) && + !canEmitAddImmediateAdjustment(static_cast<std::int64_t>(FramePointerAdjustment), + StackAlignmentBytes)) || + (Realign && !fitsSigned12(AlignmentMask)); + if (NeedsTemporary) { + auto Candidates = scratchRegisters(); + if (!Candidates) + return std::unexpected(Candidates.error()); + if (Anchor != nullptr) { + auto Scavenged = Scavenger.scavenge(*Anchor, *Candidates); + if (!Scavenged) + return std::unexpected(Scavenged.error()); + Temporary = Scavenged->Reg; + Scratch = *Scavenged; + } else { + Temporary = Candidates->front(); + } + } + + if (ZcmpInfo) { + auto CFA = emitDefCFAOffset(Builder, static_cast<std::int64_t>(ReservedFrameSize), + MachineInstrFlag::FrameSetup); + if (!CFA) + return std::unexpected(CFA.error()); + for (const PhysicalRegister Reg : SavedRegisters) { + if (!zcmpManagesRegister(*ZcmpInfo, Reg)) + continue; + const auto Slot = calleeSaveSlot(Function, Reg); + const StackObject *Object = Slot ? Function.frameInfo().object(*Slot) : nullptr; + if (Object == nullptr || !Object->description()->Offset) { + return makeUnexpected(ErrorCode::InternalError, "RISC-V Zcmp stack slot is incomplete"); + } + auto Saved = emitSavedRegisterCFI(Builder, Target, Reg, *Object->description()->Offset, + MachineInstrFlag::FrameSetup); + if (!Saved) + return std::unexpected(Saved.error()); + } + } else if (BaseReservedFrameSize != 0) { + auto CFA = emitDefCFAOffset(Builder, static_cast<std::int64_t>(BaseReservedFrameSize), + MachineInstrFlag::FrameSetup); + if (!CFA) + return std::unexpected(CFA.error()); + for (const PhysicalRegister Reg : SavedRegisters) { + if (!saveRestoreRegisterIndex(Reg)) + continue; + const auto Slot = calleeSaveSlot(Function, Reg); + const StackObject *Object = Slot ? Function.frameInfo().object(*Slot) : nullptr; + if (Object == nullptr || !Object->description()->Offset) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V save libcall stack slot is incomplete"); + } + auto Saved = emitSavedRegisterCFI(Builder, Target, Reg, *Object->description()->Offset, + MachineInstrFlag::FrameSetup); + if (!Saved) + return std::unexpected(Saved.error()); + } + } + + const std::uint64_t AllocationSize = InitialAllocation; + if (AllocationSize > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, "RISC-V stack adjustment exceeds int64"); + } + if (AllocationSize != 0) { + if (canEmitAddImmediateAdjustment(InitialAmount, StackAlignmentBytes)) { + auto Adjust = emitAddImmediateAdjustment(Builder, *SP, *SP, InitialAmount, + StackAlignmentBytes, MachineInstrFlag::FrameSetup); + if (!Adjust) + return std::unexpected(Adjust.error()); + } else { + if (Scratch && Scratch->RequiresSpill) { + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "RISC-V initial stack adjustment has no dead scratch register"); + } + auto Constant = + emitConstant(Builder, Temporary, *Zero, static_cast<std::int64_t>(AllocationSize), + MachineInstrFlag::FrameSetup); + if (!Constant) + return std::unexpected(Constant.error()); + auto Adjust = + emitRegisterSub(Builder, *SP, *SP, Temporary, MachineInstrFlag::FrameSetup, true); + if (!Adjust) + return std::unexpected(Adjust.error()); + } + if (!Realign) { + auto CFA = + emitDefCFAOffset(Builder, static_cast<std::int64_t>(ReservedFrameSize + AllocationSize), + MachineInstrFlag::FrameSetup); + if (!CFA) + return std::unexpected(CFA.error()); + } + } + if (Realign) { + if (Saves.empty() && SpillLibCall == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V realigned frame has no callee-save sequence"); + } + MachineIRBuilder AfterManualSaves(Function, Target); + MachineIRBuilder *AfterSaves = &Builder; + if (!Saves.empty()) { + auto PointAfterSaves = AfterManualSaves.setInsertPointAfter(*Saves.back().Instruction); + if (!PointAfterSaves) + return std::unexpected(PointAfterSaves.error()); + AfterSaves = &AfterManualSaves; + } + auto Set = + emitAddImmediate(*AfterSaves, *FP, *SP, static_cast<std::int64_t>(FramePointerAdjustment), + MachineInstrFlag::FrameSetup); + if (!Set) + return std::unexpected(Set.error()); + auto CFA = emitDefCFI(*AfterSaves, Target, *FP, 0, MachineInstrFlag::FrameSetup); + if (!CFA) + return std::unexpected(CFA.error()); + for (const CalleeSaveOperation &Save : Saves) { + const auto Slot = calleeSaveSlot(Function, Save.Reg); + const StackObject *Object = Slot ? Function.frameInfo().object(*Slot) : nullptr; + if (Object == nullptr || !Object->description()->Offset) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V realigned CFI slot is incomplete"); + } + auto CFI = + emitSavedRegisterCFI(*AfterSaves, Target, Save.Reg, *Object->description()->Offset, + MachineInstrFlag::FrameSetup); + if (!CFI) + return std::unexpected(CFI.error()); + } + + std::optional<FrameIndexReference> Emergency; + if (Scratch && Scratch->RequiresSpill) { + const StackObject *Object = emergencyObject(Function); + if (Object == nullptr) { + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "RISC-V prologue requires an emergency spill slot"); + } + auto Reference = emergencyFrameReference(Function, *Object->description()); + if (!Reference || !fitsSigned12(Reference->Offset)) { + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "RISC-V prologue emergency spill slot is unavailable"); + } + Emergency = *Reference; + auto Saved = emitScratchAccess(*AfterSaves, Temporary, *Emergency, false); + if (!Saved) + return std::unexpected(Saved.error()); + } + + if (SecondAdjustment != 0) { + if (SecondAdjustment > + static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V second stack adjustment exceeds int64"); + } + if (canEmitAddImmediateAdjustment(SecondAmount, StackAlignmentBytes)) { + auto Adjust = + emitAddImmediateAdjustment(*AfterSaves, *SP, *SP, SecondAmount, StackAlignmentBytes, + MachineInstrFlag::FrameSetup); + if (!Adjust) + return std::unexpected(Adjust.error()); + } else { + auto Constant = emitConstant(*AfterSaves, Temporary, *Zero, + static_cast<std::int64_t>(SecondAdjustment), + MachineInstrFlag::FrameSetup); + if (!Constant) + return std::unexpected(Constant.error()); + auto Adjust = + emitRegisterSub(*AfterSaves, *SP, *SP, Temporary, MachineInstrFlag::FrameSetup, true); + if (!Adjust) + return std::unexpected(Adjust.error()); + } + } + + if (fitsSigned12(AlignmentMask)) { + auto Aligned = + emitAndImmediate(*AfterSaves, *SP, *SP, AlignmentMask, MachineInstrFlag::FrameSetup); + if (!Aligned) + return std::unexpected(Aligned.error()); + } else { + const std::int64_t ShiftAmount = + static_cast<std::int64_t>(std::countr_zero(StackAlignment)); + auto Shifted = + emitShiftImmediate(*AfterSaves, instructionOpcode(InstructionOpcode::SRLI), Temporary, + *SP, ShiftAmount, MachineInstrFlag::FrameSetup); + if (!Shifted) + return std::unexpected(Shifted.error()); + Shifted = emitShiftImmediate(*AfterSaves, instructionOpcode(InstructionOpcode::SLLI), *SP, + Temporary, ShiftAmount, MachineInstrFlag::FrameSetup); + if (!Shifted) + return std::unexpected(Shifted.error()); + } + if (requiresBasePointer(Function)) { + auto Base = emitAddImmediate(*AfterSaves, *BP, *SP, 0, MachineInstrFlag::FrameSetup); + if (!Base) + return std::unexpected(Base.error()); + } + if (Emergency) { + auto Restored = emitScratchAccess(*AfterSaves, Temporary, *Emergency, true); + if (!Restored) + return std::unexpected(Restored.error()); + } + return {}; + } + + if (!Saves.empty()) { + MachineIRBuilder AfterSaves(Function, Target); + auto PointAfterSaves = AfterSaves.setInsertPointAfter(*Saves.back().Instruction); + if (!PointAfterSaves) + return std::unexpected(PointAfterSaves.error()); + for (const CalleeSaveOperation &Save : Saves) { + const auto Slot = calleeSaveSlot(Function, Save.Reg); + const StackObject *Object = Slot ? Function.frameInfo().object(*Slot) : nullptr; + if (Object == nullptr || !Object->description()->Offset) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V callee-save CFI slot is incomplete"); + } + auto CFI = + emitSavedRegisterCFI(AfterSaves, Target, Save.Reg, *Object->description()->Offset, + MachineInstrFlag::FrameSetup); + if (!CFI) + return std::unexpected(CFI.error()); + } + if (requiresFramePointer(Function)) { + const std::int64_t FramePointerAmount = static_cast<std::int64_t>(FramePointerAdjustment); + Expected<void> Set = + canEmitAddImmediateAdjustment(FramePointerAmount, StackAlignmentBytes) + ? emitAddImmediateAdjustment(AfterSaves, *FP, *SP, FramePointerAmount, + StackAlignmentBytes, MachineInstrFlag::FrameSetup) + : emitConstant(AfterSaves, Temporary, *Zero, FramePointerAmount, + MachineInstrFlag::FrameSetup); + if (!Set) + return std::unexpected(Set.error()); + if (!canEmitAddImmediateAdjustment(FramePointerAmount, StackAlignmentBytes)) { + Set = + emitRegisterAdd(AfterSaves, *FP, *SP, Temporary, MachineInstrFlag::FrameSetup, true); + if (!Set) + return std::unexpected(Set.error()); + } + auto CFA = emitDefCFI(AfterSaves, Target, *FP, 0, MachineInstrFlag::FrameSetup); + if (!CFA) + return std::unexpected(CFA.error()); + } + if (SecondAdjustment != 0) { + if (canEmitAddImmediateAdjustment(SecondAmount, StackAlignmentBytes)) { + auto Adjust = + emitAddImmediateAdjustment(AfterSaves, *SP, *SP, SecondAmount, StackAlignmentBytes, + MachineInstrFlag::FrameSetup); + if (!Adjust) + return std::unexpected(Adjust.error()); + } else { + auto Constant = emitConstant(AfterSaves, Temporary, *Zero, + static_cast<std::int64_t>(SecondAdjustment), + MachineInstrFlag::FrameSetup); + if (!Constant) + return std::unexpected(Constant.error()); + auto Adjust = + emitRegisterSub(AfterSaves, *SP, *SP, Temporary, MachineInstrFlag::FrameSetup, true); + if (!Adjust) + return std::unexpected(Adjust.error()); + } + if (!requiresFramePointer(Function)) { + auto CFA = emitDefCFAOffset(AfterSaves, static_cast<std::int64_t>(StackSize), + MachineInstrFlag::FrameSetup); + if (!CFA) + return std::unexpected(CFA.error()); + } + } + } + if (Saves.empty() && requiresFramePointer(Function)) { + const std::int64_t FramePointerAmount = static_cast<std::int64_t>(FramePointerAdjustment); + Expected<void> Set = + canEmitAddImmediateAdjustment(FramePointerAmount, StackAlignmentBytes) + ? emitAddImmediateAdjustment(Builder, *FP, *SP, FramePointerAmount, + StackAlignmentBytes, MachineInstrFlag::FrameSetup) + : emitConstant(Builder, Temporary, *Zero, FramePointerAmount, + MachineInstrFlag::FrameSetup); + if (!Set) + return std::unexpected(Set.error()); + if (!canEmitAddImmediateAdjustment(FramePointerAmount, StackAlignmentBytes)) { + Set = emitRegisterAdd(Builder, *FP, *SP, Temporary, MachineInstrFlag::FrameSetup, true); + if (!Set) + return std::unexpected(Set.error()); + } + auto CFA = emitDefCFI(Builder, Target, *FP, 0, MachineInstrFlag::FrameSetup); + if (!CFA) + return std::unexpected(CFA.error()); + } + return {}; + } + + [[nodiscard]] bool isEpilogueTerminator(const MachineInstr &Instruction, + const TargetDescription &Target) const noexcept override { + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoTAIL)) + return true; + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction.opcode()); + return Descriptor != nullptr && Descriptor->Flags.has(InstructionFlag::Return); + } + + [[nodiscard]] Expected<void> emitEpilogue(MachineBasicBlock &Block, MachineInstr &Terminator, + MachineFunction &Function, + const TargetDescription &Target, + RegisterScavenger &Scavenger) const override { + if (!isFrameRestoreBlock(Function, Block)) + return {}; + auto FP = physicalRegister(PhysicalRegisterID::X8); + auto SP = physicalRegister(PhysicalRegisterID::X2); + auto BP = physicalRegister(PhysicalRegisterID::X9); + if (!FP || !SP || !BP) { + return makeUnexpected(ErrorCode::InternalError, "RISC-V frame registers are unavailable"); + } + const bool HasFP = requiresFramePointer(Function); + const bool Realign = requiresStackRealignment(Function); + const std::span<const PhysicalRegister> SavedRegisters = + Function.records().calleeSavedRegisters(); + const auto ZcmpInfo = zcmpPushPopInfo(Function, SavedRegisters, Subtarget.features()); + const auto LibCallID = + ZcmpInfo ? std::nullopt : saveRestoreLibCallID(SavedRegisters, Subtarget.features()); + MachineInstr *Pop = nullptr; + if (ZcmpInfo) { + for (MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (Instruction->opcode() == instructionOpcode(InstructionOpcode::CM_POP) && + Instruction->flags().has(MachineInstrFlag::FrameDestroy)) { + if (Pop != nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V epilogue has duplicate Zcmp pops"); + } + Pop = Instruction; + } + } + if (Pop == nullptr) { + return makeUnexpected(ErrorCode::InternalError, "RISC-V epilogue is missing its Zcmp pop"); + } + } + const std::uint64_t BaseReservedFrameSize = + ZcmpInfo ? ZcmpInfo->FrameSize : saveRestoreFrameSize(SavedRegisters, Subtarget.features()); + const std::uint64_t TotalStackSize = Function.frameInfo().state().StackSize; + if (TotalStackSize < BaseReservedFrameSize) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V managed callee-save frame exceeds the complete frame"); + } + std::uint64_t ZcmpExtraAdjustment = 0; + if (Pop != nullptr) { + ZcmpExtraAdjustment = std::min((TotalStackSize - BaseReservedFrameSize) & ~std::uint64_t{15}, + std::uint64_t{48}); + MachineOperand *Adjustment = Pop->operandAt(1); + if (Adjustment == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V Zcmp pop lacks its stack adjustment"); + } + auto SetAdjustment = Adjustment->setImmediate(static_cast<std::int64_t>(ZcmpExtraAdjustment)); + if (!SetAdjustment) + return std::unexpected(SetAdjustment.error()); + } + const std::uint64_t ReservedFrameSize = BaseReservedFrameSize + ZcmpExtraAdjustment; + const std::uint64_t StackSize = TotalStackSize - ReservedFrameSize; + + const std::vector<CalleeSaveOperation> BlockRestores = + calleeSaveOperations(Block, Function, true); + std::vector<CalleeSaveOperation> Restores; + for (MachineInstr *Cursor = Pop != nullptr ? Pop->previous() : Terminator.previous(); + Cursor != nullptr; Cursor = Cursor->previous()) { + const auto Position = + std::ranges::find(BlockRestores, Cursor, &CalleeSaveOperation::Instruction); + if (Position == BlockRestores.end()) + break; + Restores.push_back(*Position); + } + std::ranges::reverse(Restores); + const std::size_t ExpectedRestores = + static_cast<std::size_t>(std::ranges::count_if(SavedRegisters, [&](PhysicalRegister Reg) { + return calleeSaveIsRestored(Function, Reg) && + (!ZcmpInfo || !zcmpManagesRegister(*ZcmpInfo, Reg)) && + (!LibCallID || !saveRestoreRegisterIndex(Reg)); + })); + if (Restores.size() != ExpectedRestores) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V epilogue has an incomplete callee-restore sequence"); + } + + if (!Restores.empty()) { + MachineIRBuilder AfterRestores(Function, Target); + auto Point = AfterRestores.setInsertPointAfter(*Restores.back().Instruction); + if (!Point) + return std::unexpected(Point.error()); + for (const CalleeSaveOperation &Restore : Restores) { + auto CFI = emitRestoreRegisterCFI(AfterRestores, Target, Restore.Reg, + MachineInstrFlag::FrameDestroy); + if (!CFI) + return std::unexpected(CFI.error()); + } + } + + if (Realign) { + const std::uint64_t FirstAdjustment = firstSPAdjustAmount(Function, Subtarget.features()); + if (FirstAdjustment > static_cast<std::uint64_t>(MaxSigned12)) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V first epilogue adjustment exceeds signed 12 bits"); + } + MachineIRBuilder Teardown(Function, Target); + MachineInstr *FirstRestore = Restores.empty() ? &Terminator : Restores.front().Instruction; + auto Point = Teardown.setInsertPointBefore(*FirstRestore); + if (!Point) + return std::unexpected(Point.error()); + auto RestoreSP = + emitAddImmediate(Teardown, *SP, *FP, -static_cast<std::int64_t>(FirstAdjustment), + MachineInstrFlag::FrameDestroy); + if (!RestoreSP) + return std::unexpected(RestoreSP.error()); + auto CFA = emitDefCFI(Teardown, Target, *SP, 0, MachineInstrFlag::FrameDestroy); + if (!CFA) + return std::unexpected(CFA.error()); + if (FirstAdjustment != 0) { + MachineIRBuilder Deallocate(Function, Target); + auto BeforeTerminator = Deallocate.setInsertPointBefore(Terminator); + if (!BeforeTerminator) + return std::unexpected(BeforeTerminator.error()); + auto Adjust = + emitAddImmediate(Deallocate, *SP, *SP, static_cast<std::int64_t>(FirstAdjustment), + MachineInstrFlag::FrameDestroy); + if (!Adjust) + return std::unexpected(Adjust.error()); + } + } else if (HasFP || firstSPAdjustAmount(Function, Subtarget.features()) != 0) { + const std::uint64_t FirstAdjustment = firstSPAdjustAmount(Function, Subtarget.features()); + const std::uint64_t RestoreAreaSize = FirstAdjustment == 0 ? StackSize : FirstAdjustment; + const std::uint64_t SecondAdjustment = StackSize - RestoreAreaSize; + MachineInstr *FirstRestore = Restores.empty() ? &Terminator : Restores.front().Instruction; + if (SecondAdjustment != 0) { + if (SecondAdjustment > + static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V second epilogue adjustment exceeds int64"); + } + auto Adjusted = emitStackAdjustment(*FirstRestore, Function, Target, Scavenger, + static_cast<std::int64_t>(SecondAdjustment), + MachineInstrFlag::FrameDestroy); + if (!Adjusted) + return std::unexpected(Adjusted.error()); + } + MachineIRBuilder Teardown(Function, Target); + auto Point = Teardown.setInsertPointBefore(*FirstRestore); + if (!Point) + return std::unexpected(Point.error()); + if (SecondAdjustment == 0) { + if (!HasFP) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V split epilogue has no second adjustment"); + } + auto RestoreSP = emitAddImmediateAdjustment( + Teardown, *SP, *FP, -static_cast<std::int64_t>(RestoreAreaSize), StackAlignmentBytes, + MachineInstrFlag::FrameDestroy); + if (!RestoreSP) + return std::unexpected(RestoreSP.error()); + } + auto CFA = HasFP + ? emitDefCFI(Teardown, Target, *SP, static_cast<std::int64_t>(RestoreAreaSize), + MachineInstrFlag::FrameDestroy) + : emitDefCFAOffset(Teardown, static_cast<std::int64_t>(RestoreAreaSize), + MachineInstrFlag::FrameDestroy); + if (!CFA) + return std::unexpected(CFA.error()); + if (RestoreAreaSize != 0) { + MachineIRBuilder Deallocate(Function, Target); + auto BeforeTerminator = Deallocate.setInsertPointBefore(Terminator); + if (!BeforeTerminator) + return std::unexpected(BeforeTerminator.error()); + auto Adjust = + emitAddImmediate(Deallocate, *SP, *SP, static_cast<std::int64_t>(RestoreAreaSize), + MachineInstrFlag::FrameDestroy); + if (!Adjust) + return std::unexpected(Adjust.error()); + auto FinalCFA = emitDefCFAOffset(Deallocate, 0, MachineInstrFlag::FrameDestroy); + if (!FinalCFA) + return std::unexpected(FinalCFA.error()); + } + } else if (StackSize != 0) { + if (StackSize > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) + return makeUnexpected(ErrorCode::FrameLayoutFailed, + "RISC-V stack adjustment exceeds int64"); + MachineIRBuilder Teardown(Function, Target); + MachineInstr &DeallocationAnchor = Pop != nullptr ? *Pop : Terminator; + auto Point = Teardown.setInsertPointBefore(DeallocationAnchor); + if (!Point) + return std::unexpected(Point.error()); + const std::int64_t Amount = static_cast<std::int64_t>(StackSize); + if (canEmitAddImmediateAdjustment(Amount, StackAlignmentBytes)) { + auto Adjust = emitAddImmediateAdjustment(Teardown, *SP, *SP, Amount, StackAlignmentBytes, + MachineInstrFlag::FrameDestroy); + if (!Adjust) + return std::unexpected(Adjust.error()); + } else { + auto Candidates = scratchRegisters(); + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Candidates || !Zero) + return std::unexpected(!Candidates ? Candidates.error() : Zero.error()); + auto Scratch = Scavenger.scavenge(DeallocationAnchor, *Candidates); + if (!Scratch) + return std::unexpected(Scratch.error()); + if (Scratch->RequiresSpill) { + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "RISC-V epilogue has no dead scratch register"); + } + auto Constant = + emitConstant(Teardown, Scratch->Reg, *Zero, Amount, MachineInstrFlag::FrameDestroy); + if (!Constant) + return std::unexpected(Constant.error()); + auto Adjust = + emitRegisterAdd(Teardown, *SP, *SP, Scratch->Reg, MachineInstrFlag::FrameDestroy, true); + if (!Adjust) + return std::unexpected(Adjust.error()); + } + auto CFA = emitDefCFAOffset(Teardown, static_cast<std::int64_t>(ReservedFrameSize), + MachineInstrFlag::FrameDestroy); + if (!CFA) + return std::unexpected(CFA.error()); + } + if (LibCallID) { + MachineIRBuilder RestoreBuilder(Function, Target); + auto Point = RestoreBuilder.setInsertPointBefore(Terminator); + if (!Point) + return std::unexpected(Point.error()); + auto Pending = RestoreBuilder.buildInstr(instructionOpcode(InstructionOpcode::PseudoTAIL)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Result = + Pending->addExternalSymbol("__riscv_restore_" + std::to_string(*LibCallID), 0, + static_cast<std::uint32_t>(CallTargetFlag::DirectCall)); + !Result) { + return std::unexpected(Result.error()); + } + if (auto Result = Pending->addImplicitUse(Register::fromPhysical(*SP)); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addImplicitDef(Register::fromPhysical(*SP)); !Result) + return std::unexpected(Result.error()); + for (const PhysicalRegister Reg : SavedRegisters) { + if (!calleeSaveIsRestored(Function, Reg)) + continue; + if (auto Result = Pending->addImplicitDef(Register::fromPhysical(Reg)); !Result) + return std::unexpected(Result.error()); + } + for (const MachineOperand &Operand : Terminator.operands()) { + if (!Operand.isImplicit()) + continue; + if (auto Result = Pending->addImplicitOperand(Operand.cloneDetached()); !Result) + return std::unexpected(Result.error()); + } + if (auto Result = setFrameFlag(*Pending, MachineInstrFlag::FrameDestroy); !Result) + return std::unexpected(Result.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + auto Removed = Terminator.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + } + return {}; + } + + [[nodiscard]] Expected<FrameIndexReference> + getFrameIndexReference(const MachineFunction &Function, const MachineInstr &Instruction, + FrameIndex Index, std::int64_t OperandOffset) const override { + const StackObject *Object = Function.frameInfo().object(Index); + if (Object == nullptr || Object->description()->IsDead) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V FrameIndex references an invalid stack object"); + if (Object->isVariableSized()) + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V variable-sized object cannot be addressed by FrameIndex"); + auto Offset = objectReferenceOffset(Function, Instruction, *Object->description(), + OperandOffset, Subtarget.features()); + if (!Offset) + return std::unexpected(Offset.error()); + const StackObjectDesc &Description = *Object->description(); + PhysicalRegisterID BaseID = PhysicalRegisterID::X2; + if (Description.Kind == StackObjectKind::CalleeSaved) { + BaseID = PhysicalRegisterID::X2; + } else if (requiresStackRealignment(Function) && !Object->isFixed()) { + BaseID = requiresBasePointer(Function) ? PhysicalRegisterID::X9 : PhysicalRegisterID::X2; + } else if (!isOutgoingCallObject(Description) && + !Instruction.flags().has(MachineInstrFlag::FrameSetup)) { + const bool FixedObject = Description.Kind == StackObjectKind::FixedABI || + Description.Kind == StackObjectKind::FixedSpillSlot; + const bool HasFP = requiresFramePointer(Function); + const bool HasBP = requiresBasePointer(Function); + if (HasFP && (FixedObject || !HasBP)) { + BaseID = PhysicalRegisterID::X8; + } else if (HasBP) { + BaseID = PhysicalRegisterID::X9; + } + } + auto Base = physicalRegister(BaseID); + if (!Base) + return std::unexpected(Base.error()); + return FrameIndexReference{*Base, *Offset}; + } + + [[nodiscard]] Expected<void> eliminateFrameIndex(MachineInstr &Instruction, + std::size_t OperandIndex, + MachineFunction &Function, + const TargetDescription &Target, + RegisterScavenger &Scavenger) const override { + const auto *Frame = Instruction.operandAt(OperandIndex)->getIf<FrameIndexOperand>(); + if (Frame == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V frame elimination requires a FrameIndex operand"); + if (OperandIndex + 1U >= Instruction.operandCount()) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V frame operand lacks an adjacent immediate"); + auto Immediate = immediateValue(*Instruction.operandAt(OperandIndex + 1U)); + if (!Immediate) + return std::unexpected(Immediate.error()); + auto Extra = checkedAdd(Frame->Offset, *Immediate); + if (!Extra) + return std::unexpected(Extra.error()); + auto Reference = getFrameIndexReference(Function, Instruction, Frame->Index, *Extra); + if (!Reference) + return std::unexpected(Reference.error()); + if (fitsSigned12(Reference->Offset)) { + auto Base = replaceWithRegister(Instruction, OperandIndex, Reference->BaseRegister); + if (!Base) + return std::unexpected(Base.error()); + return replaceImmediate(Instruction, OperandIndex + 1U, Reference->Offset); + } + + auto Candidates = scratchRegisters(); + if (!Candidates) + return std::unexpected(Candidates.error()); + auto Scratch = Scavenger.scavenge(Instruction, *Candidates); + if (!Scratch) + return std::unexpected(Scratch.error()); + std::optional<FrameIndexReference> Emergency; + if (Scratch->RequiresSpill) { + const StackObject *Object = emergencyObject(Function); + if (Object == nullptr) + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "RISC-V scavenging requires an emergency spill slot"); + auto Ref = emergencyFrameReference(Function, *Object->description()); + if (!Ref) + return std::unexpected(Ref.error()); + if (!fitsSigned12(Ref->Offset)) { + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "RISC-V emergency spill slot is out of range"); + } + Emergency = *Ref; + auto Saved = + emitResolvedScratchAccess(Instruction, Function, Target, Scratch->Reg, *Emergency, false); + if (!Saved) + return std::unexpected(Saved.error()); + } + + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + std::int64_t InstructionOffset = 0; + std::int64_t AddressOffset = Reference->Offset; + if (Instruction.opcode() != instructionOpcode(InstructionOpcode::ADDI)) { + InstructionOffset = Reference->Offset & 0xFFF; + if (InstructionOffset >= 0x800) + InstructionOffset -= 0x1000; + AddressOffset -= InstructionOffset; + } + auto Constant = emitConstant(Builder, Scratch->Reg, *Zero, AddressOffset, std::nullopt); + if (!Constant) + return std::unexpected(Constant.error()); + auto Address = emitRegisterAdd(Builder, Scratch->Reg, Reference->BaseRegister, Scratch->Reg, + std::nullopt, true); + if (!Address) + return std::unexpected(Address.error()); + auto Base = replaceWithRegister(Instruction, OperandIndex, Scratch->Reg); + if (!Base) + return std::unexpected(Base.error()); + auto Offset = replaceImmediate(Instruction, OperandIndex + 1U, InstructionOffset); + if (!Offset) + return std::unexpected(Offset.error()); + if (Emergency) { + auto Restored = + emitResolvedScratchAccess(Instruction, Function, Target, Scratch->Reg, *Emergency, true); + if (!Restored) + return std::unexpected(Restored.error()); + } + return {}; + } + +private: + [[nodiscard]] Expected<void> emitStackAdjustment(MachineInstr &Anchor, MachineFunction &Function, + const TargetDescription &Target, + RegisterScavenger &Scavenger, + std::int64_t Amount, + std::optional<MachineInstrFlag> Flag) const { + if (Amount == 0) + return {}; + auto SP = physicalRegister(PhysicalRegisterID::X2); + if (!SP) + return std::unexpected(SP.error()); + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Anchor); + if (!Point) + return std::unexpected(Point.error()); + if (canEmitAddImmediateAdjustment(Amount, StackAlignmentBytes)) { + return emitAddImmediateAdjustment(Builder, *SP, *SP, Amount, StackAlignmentBytes, Flag); + } + + auto Candidates = scratchRegisters(); + if (!Candidates) + return std::unexpected(Candidates.error()); + auto Scratch = Scavenger.scavenge(Anchor, *Candidates); + if (!Scratch) + return std::unexpected(Scratch.error()); + std::optional<FrameIndexReference> Emergency; + if (Scratch->RequiresSpill) { + const StackObject *Object = emergencyObject(Function); + if (Object == nullptr) + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "RISC-V stack adjustment requires an emergency spill slot"); + auto Ref = emergencyFrameReference(Function, *Object->description()); + if (!Ref || !fitsSigned12(Ref->Offset)) + return makeUnexpected(ErrorCode::RegisterAllocationFailed, + "RISC-V emergency spill slot is unavailable"); + Emergency = *Ref; + auto Saved = + emitResolvedScratchAccess(Anchor, Function, Target, Scratch->Reg, *Emergency, false); + if (!Saved) + return std::unexpected(Saved.error()); + } + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + const bool Subtract = Amount < 0; + const std::int64_t Magnitude = Subtract ? -Amount : Amount; + auto Constant = emitConstant(Builder, Scratch->Reg, *Zero, Magnitude, Flag); + if (!Constant) + return std::unexpected(Constant.error()); + auto Adjust = Subtract ? emitRegisterSub(Builder, *SP, *SP, Scratch->Reg, Flag, true) + : emitRegisterAdd(Builder, *SP, *SP, Scratch->Reg, Flag, true); + if (!Adjust) + return std::unexpected(Adjust.error()); + if (Emergency) { + auto Restored = + emitResolvedScratchAccess(Anchor, Function, Target, Scratch->Reg, *Emergency, true); + if (!Restored) + return std::unexpected(Restored.error()); + } + return {}; + } + + [[nodiscard]] Expected<void> + emitResolvedScratchAccess(MachineInstr &Anchor, MachineFunction &Function, + const TargetDescription &Target, PhysicalRegister Scratch, + FrameIndexReference Reference, bool Restore) const { + MachineIRBuilder Builder(Function, Target); + auto Point = + Restore ? Builder.setInsertPointAfter(Anchor) : Builder.setInsertPointBefore(Anchor); + if (!Point) + return std::unexpected(Point.error()); + return emitScratchAccess(Builder, Scratch, Reference, Restore); + } + + const RISCVSubtarget &Subtarget; +}; + +} // namespace + +std::unique_ptr<TargetFrameLowering> createFrameLowering(const RISCVSubtarget &Subtarget) { + return std::make_unique<RISCVFrameLowering>(Subtarget); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVFunctionInfo.cpp b/backend/lib/Target/RISCV/RISCVFunctionInfo.cpp new file mode 100644 index 0000000..bd72103 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVFunctionInfo.cpp @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVFunctionInfo.h" + +#include "rcc/CodeGen/TargetMIRTextInfo.h" + +#include <bit> +#include <charconv> +#include <cstddef> +#include <cstdint> +#include <span> +#include <string> +#include <string_view> +#include <system_error> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] Expected<std::int32_t> parseSignedField(const MIRTargetNode &Node, + std::string_view Name) { + if (Node.Kind != MIRTargetNodeKind::Scalar) + return makeUnexpected(ErrorCode::ParseError, "RISC-V machineFunctionInfo field '" + + std::string(Name) + "' must be a scalar"); + std::int32_t Value = 0; + const auto Result = + std::from_chars(Node.Scalar.data(), Node.Scalar.data() + Node.Scalar.size(), Value, 10); + if (Result.ec != std::errc{} || Result.ptr != Node.Scalar.data() + Node.Scalar.size()) { + return makeUnexpected(ErrorCode::ParseError, "RISC-V machineFunctionInfo field '" + + std::string(Name) + + "' must be a 32-bit signed integer"); + } + return Value; +} + +[[nodiscard]] Expected<TargetFunctionState> parseFunctionInfo(const MIRTargetNode &Node) { + if (Node.Kind != MIRTargetNodeKind::Mapping) + return makeUnexpected(ErrorCode::ParseError, "RISC-V machineFunctionInfo must be a mapping"); + + std::int32_t VarArgsFrameIndex = 0; + std::int32_t VarArgsSaveSize = 0; + for (const MIRTargetMappingEntry &Entry : Node.Mapping) { + if (Entry.Key == "varArgsFrameIndex") { + auto Value = parseSignedField(Entry.Value, Entry.Key); + if (!Value) + return std::unexpected(Value.error()); + VarArgsFrameIndex = *Value; + continue; + } + if (Entry.Key == "varArgsSaveSize") { + auto Value = parseSignedField(Entry.Value, Entry.Key); + if (!Value) + return std::unexpected(Value.error()); + VarArgsSaveSize = *Value; + continue; + } + return makeUnexpected(ErrorCode::ParseError, + "unknown RISC-V machineFunctionInfo field '" + Entry.Key + "'"); + } + return TargetFunctionState::create(RISCVFunctionInfo(VarArgsFrameIndex, VarArgsSaveSize)); +} + +[[nodiscard]] Expected<MIRTargetNode> printFunctionInfo(const TargetFunctionState &State) { + const auto *Info = State.getIf<RISCVFunctionInfo>(); + if (Info == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, + "target function state is not RISC-V function information"); + std::vector<MIRTargetMappingEntry> Entries; + Entries.push_back( + {"varArgsFrameIndex", MIRTargetNode::scalar(std::to_string(Info->varArgsFrameIndex()))}); + Entries.push_back( + {"varArgsSaveSize", MIRTargetNode::scalar(std::to_string(Info->varArgsSaveSize()))}); + return MIRTargetNode::mapping(std::move(Entries)); +} + +void appendWord(std::vector<std::byte> &Payload, std::int32_t Value) { + const std::uint32_t Bits = std::bit_cast<std::uint32_t>(Value); + for (std::uint32_t Shift = 0; Shift != 32; Shift += 8) + Payload.push_back(static_cast<std::byte>((Bits >> Shift) & 0xFFU)); +} + +[[nodiscard]] std::int32_t readWord(std::span<const std::byte> Payload, + std::size_t Offset) noexcept { + std::uint32_t Bits = 0; + for (std::uint32_t Byte = 0; Byte != 4; ++Byte) { + Bits |= std::to_integer<std::uint32_t>(Payload[Offset + Byte]) << (Byte * 8U); + } + return std::bit_cast<std::int32_t>(Bits); +} + +} // namespace + +Expected<void> RISCVFunctionInfo::verify() const { + return {}; +} + +Expected<RISCVFunctionInfo> RISCVFunctionInfo::clone(const MachineFunctionCloneContext &) const { + return RISCVFunctionInfo(VarArgsFrameIndex, VarArgsSaveSize); +} + +std::string RISCVFunctionInfo::print() const { + return "riscv-varargs:" + std::to_string(VarArgsFrameIndex) + ":" + + std::to_string(VarArgsSaveSize); +} + +Expected<std::vector<std::byte>> RISCVFunctionInfo::encode() const { + std::vector<std::byte> Payload; + Payload.reserve(8); + appendWord(Payload, VarArgsFrameIndex); + appendWord(Payload, VarArgsSaveSize); + return Payload; +} + +Expected<RISCVFunctionInfo> RISCVFunctionInfo::decode(std::uint32_t Version, + std::span<const std::byte> Payload) { + if (Version != schemaVersion()) + return makeUnexpected(ErrorCode::ParseError, "unsupported RISC-V function-info schema version"); + if (Payload.size() != 8) + return makeUnexpected(ErrorCode::ParseError, + "RISC-V function-info payload must contain eight bytes"); + return RISCVFunctionInfo(readWord(Payload, 0), readWord(Payload, 4)); +} + +void configureFunctionInfoMIRSchema(TargetMIRTextInfoDesc &Description) { + Description.FunctionStateType = RISCVFunctionInfo::stableTypeID(); + Description.ParseFunctionState = parseFunctionInfo; + Description.PrintFunctionState = printFunctionInfo; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVGISelAddressing.rccdesc b/backend/lib/Target/RISCV/RISCVGISelAddressing.rccdesc new file mode 100644 index 0000000..1a1dd4d --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVGISelAddressing.rccdesc @@ -0,0 +1,282 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +selection_rules: + - + id: select_g_load_m8 + priority: 300 + match: + opcode: G_LOAD + memory: { index: 0, bytes: 1 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LBU + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_load_m16 + priority: 300 + match: + opcode: G_LOAD + memory: { index: 0, bytes: 2 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LH + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_load_m32 + priority: 300 + match: + opcode: G_LOAD + memory: { index: 0, bytes: 4 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LW + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_load_m64 + priority: 300 + match: + opcode: G_LOAD + memory: { index: 0, bytes: 8 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LD + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_sextload_m8 + priority: 300 + match: + opcode: G_SEXTLOAD + memory: { index: 0, bytes: 1 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LB + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_sextload_m16 + priority: 300 + match: + opcode: G_SEXTLOAD + memory: { index: 0, bytes: 2 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LH + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_sextload_m32 + priority: 300 + match: + opcode: G_SEXTLOAD + memory: { index: 0, bytes: 4 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LW + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_zextload_m8 + priority: 300 + match: + opcode: G_ZEXTLOAD + memory: { index: 0, bytes: 1 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LBU + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_zextload_m16 + priority: 300 + match: + opcode: G_ZEXTLOAD + memory: { index: 0, bytes: 2 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LHU + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_zextload_m32 + priority: 300 + match: + opcode: G_ZEXTLOAD + memory: { index: 0, bytes: 4 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: LWU + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_store_m8 + priority: 300 + match: + opcode: G_STORE + memory: { index: 0, bytes: 1 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: SB + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_store_m16 + priority: 300 + match: + opcode: G_STORE + memory: { index: 0, bytes: 2 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: SH + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_store_m32 + priority: 300 + match: + opcode: G_STORE + memory: { index: 0, bytes: 4 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: SW + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_store_m64 + priority: 300 + match: + opcode: G_STORE + memory: { index: 0, bytes: 8 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: SD + root: true + operands: + - { operand: 0 } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - + id: select_g_frame_index + priority: 100 + match: + opcode: G_FRAME_INDEX + result_type: { kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + apply: + instructions: + - opcode: ADDI + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 0 }] diff --git a/backend/lib/Target/RISCV/RISCVGISelAtomic.rccdesc b/backend/lib/Target/RISCV/RISCVGISelAtomic.rccdesc new file mode 100644 index 0000000..97e21e9 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVGISelAtomic.rccdesc @@ -0,0 +1,363 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +selection_rules: + - + id: select_g_atomicrmw_xchg_m32 + priority: 300 + match: + opcode: G_ATOMICRMW_XCHG + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOSWAP_W + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_xchg_m64 + priority: 300 + match: + opcode: G_ATOMICRMW_XCHG + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 8 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOSWAP_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_add_m32 + priority: 300 + match: + opcode: G_ATOMICRMW_ADD + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOADD_W + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_add_m64 + priority: 300 + match: + opcode: G_ATOMICRMW_ADD + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 8 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOADD_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_and_m32 + priority: 300 + match: + opcode: G_ATOMICRMW_AND + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOAND_W + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_and_m64 + priority: 300 + match: + opcode: G_ATOMICRMW_AND + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 8 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOAND_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_or_m32 + priority: 300 + match: + opcode: G_ATOMICRMW_OR + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOOR_W + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_or_m64 + priority: 300 + match: + opcode: G_ATOMICRMW_OR + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 8 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOOR_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_xor_m32 + priority: 300 + match: + opcode: G_ATOMICRMW_XOR + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOXOR_W + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_xor_m64 + priority: 300 + match: + opcode: G_ATOMICRMW_XOR + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 8 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOXOR_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_max_m32 + priority: 300 + match: + opcode: G_ATOMICRMW_MAX + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOMAX_W + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_max_m64 + priority: 300 + match: + opcode: G_ATOMICRMW_MAX + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 8 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOMAX_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_min_m32 + priority: 300 + match: + opcode: G_ATOMICRMW_MIN + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOMIN_W + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_min_m64 + priority: 300 + match: + opcode: G_ATOMICRMW_MIN + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 8 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOMIN_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_umax_m32 + priority: 300 + match: + opcode: G_ATOMICRMW_UMAX + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOMAXU_W + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_umax_m64 + priority: 300 + match: + opcode: G_ATOMICRMW_UMAX + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 8 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOMAXU_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_umin_m32 + priority: 300 + match: + opcode: G_ATOMICRMW_UMIN + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOMINU_W + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] + - + id: select_g_atomicrmw_umin_m64 + priority: 300 + match: + opcode: G_ATOMICRMW_UMIN + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 8 } + target_predicates: [a] + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AMOMINU_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 1 }] diff --git a/backend/lib/Target/RISCV/RISCVGISelCombiner.rccdesc b/backend/lib/Target/RISCV/RISCVGISelCombiner.rccdesc new file mode 100644 index 0000000..5f8585b --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVGISelCombiner.rccdesc @@ -0,0 +1,173 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +combiner_rules: + - + id: combine_nneg_zext + priority: 500 + match: + opcode: G_ZEXT + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + target_predicates: [sext_cheaper_than_zext] + flags: [nneg] + def_use: + - + operand: 1 + opcode: G_TRUNC + captures: + - { id: source, operand: 1 } + - { id: trunc_result, operand: 0 } + apply: + action: mutate_root + opcode: G_SEXT_INREG + operands: + - { operand: 0 } + - { captured_operand: source } + - { captured_type_bits: trunc_result } + clear_flags: [nneg] + erase_operand_definitions: [1] + - + id: combine_shl_zero + priority: 100 + match: + opcode: G_SHL + def_use: + - + operand: 1 + opcode: G_CONSTANT + captures: + - { id: zero, operand: 0 } + operand_def_constants: + - index: 1 + ranges: [{ min: 0, max: 0 }] + apply: + action: replace_root_with_capture + capture: zero + erase_operand_definitions: [1] + - + id: combine_lshr_zero + priority: 100 + match: + opcode: G_LSHR + def_use: + - + operand: 1 + opcode: G_CONSTANT + captures: + - { id: zero, operand: 0 } + operand_def_constants: + - index: 1 + ranges: [{ min: 0, max: 0 }] + apply: + action: replace_root_with_capture + capture: zero + erase_operand_definitions: [1] + - + id: combine_ashr_zero + priority: 100 + match: + opcode: G_ASHR + def_use: + - + operand: 1 + opcode: G_CONSTANT + captures: + - { id: zero, operand: 0 } + operand_def_constants: + - index: 1 + ranges: [{ min: 0, max: 0 }] + apply: + action: replace_root_with_capture + capture: zero + erase_operand_definitions: [1] + - + id: combine_sdiv_zero + priority: 100 + match: + opcode: G_SDIV + def_use: + - + operand: 1 + opcode: G_CONSTANT + captures: + - { id: zero, operand: 0 } + operand_def_constants: + - index: 1 + ranges: [{ min: 0, max: 0 }] + apply: + action: replace_root_with_capture + capture: zero + erase_operand_definitions: [1] + - + id: combine_udiv_zero + priority: 100 + match: + opcode: G_UDIV + def_use: + - + operand: 1 + opcode: G_CONSTANT + captures: + - { id: zero, operand: 0 } + operand_def_constants: + - index: 1 + ranges: [{ min: 0, max: 0 }] + apply: + action: replace_root_with_capture + capture: zero + erase_operand_definitions: [1] + - + id: combine_srem_zero + priority: 100 + match: + opcode: G_SREM + def_use: + - + operand: 1 + opcode: G_CONSTANT + captures: + - { id: zero, operand: 0 } + operand_def_constants: + - index: 1 + ranges: [{ min: 0, max: 0 }] + apply: + action: replace_root_with_capture + capture: zero + erase_operand_definitions: [1] + - + id: combine_urem_zero + priority: 100 + match: + opcode: G_UREM + def_use: + - + operand: 1 + opcode: G_CONSTANT + captures: + - { id: zero, operand: 0 } + operand_def_constants: + - index: 1 + ranges: [{ min: 0, max: 0 }] + apply: + action: replace_root_with_capture + capture: zero + erase_operand_definitions: [1] + - + id: combine_mul_zero + priority: 100 + match: + opcode: G_MUL + def_use: + - + operand: 1 + opcode: G_CONSTANT + captures: + - { id: zero, operand: 0 } + operand_def_constants: + - index: 1 + ranges: [{ min: 0, max: 0 }] + apply: + action: replace_root_with_capture + capture: zero + erase_operand_definitions: [1] diff --git a/backend/lib/Target/RISCV/RISCVGISelControlFlow.rccdesc b/backend/lib/Target/RISCV/RISCVGISelControlFlow.rccdesc new file mode 100644 index 0000000..7f8b67c --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVGISelControlFlow.rccdesc @@ -0,0 +1,280 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +selection_rules: + - + id: select_g_trap + priority: 100 + match: + opcode: G_TRAP + apply: + instructions: + - { opcode: UNIMP, root: true, operands: [] } + - + id: select_g_debugtrap + priority: 100 + match: + opcode: G_DEBUGTRAP + apply: + instructions: + - { opcode: EBREAK, root: true, operands: [] } + - + id: select_g_brindirect + priority: 100 + match: + opcode: G_BRINDIRECT + apply: + instructions: + - opcode: PseudoBRIND + root: true + operands: + - { operand: 0 } + - { immediate: 0 } + - + id: select_g_br + priority: 100 + match: + opcode: G_BR + apply: + instructions: + - opcode: PseudoBR + root: true + operands: [{ operand: 0 }] + - + id: select_g_brcond_sgt_minus_one + priority: 600 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: sgt } + captures: + - { id: lhs, operand: 2 } + operand_def_constants: + - index: 3 + ranges: [{ min: -1, max: -1 }] + apply: + instructions: + - opcode: BGE + root: true + operands: + - { captured_operand: lhs } + - { physical_register: X0, flags: [use] } + - { operand: 1 } + erase_operand_definitions: [0] + - + id: select_g_brcond_slt_one + priority: 600 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: slt } + captures: + - { id: lhs, operand: 2 } + operand_def_constants: + - index: 3 + ranges: [{ min: 1, max: 1 }] + apply: + instructions: + - opcode: BGE + root: true + operands: + - { physical_register: X0, flags: [use] } + - { captured_operand: lhs } + - { operand: 1 } + erase_operand_definitions: [0] + - + id: select_g_brcond_eq + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: eq } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BEQ + root: true + operands: [{ captured_operand: lhs }, { captured_operand: rhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond_ne + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: ne } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BNE + root: true + operands: [{ captured_operand: lhs }, { captured_operand: rhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond_slt + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: slt } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BLT + root: true + operands: [{ captured_operand: lhs }, { captured_operand: rhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond_sge + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: sge } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BGE + root: true + operands: [{ captured_operand: lhs }, { captured_operand: rhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond_ult + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: ult } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BLTU + root: true + operands: [{ captured_operand: lhs }, { captured_operand: rhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond_uge + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: uge } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BGEU + root: true + operands: [{ captured_operand: lhs }, { captured_operand: rhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond_sgt + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: sgt } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BLT + root: true + operands: [{ captured_operand: rhs }, { captured_operand: lhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond_sle + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: sle } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BGE + root: true + operands: [{ captured_operand: rhs }, { captured_operand: lhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond_ugt + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: ugt } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BLTU + root: true + operands: [{ captured_operand: rhs }, { captured_operand: lhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond_ule + priority: 400 + match: + opcode: G_BRCOND + def_use: + - operand: 0 + opcode: G_ICMP + integer_predicate: { index: 1, value: ule } + captures: + - { id: lhs, operand: 2 } + - { id: rhs, operand: 3 } + apply: + instructions: + - opcode: BGEU + root: true + operands: [{ captured_operand: rhs }, { captured_operand: lhs }, { operand: 1 }] + erase_operand_definitions: [0] + - + id: select_g_brcond + priority: 100 + match: + opcode: G_BRCOND + apply: + instructions: + - opcode: BNE + root: true + operands: + - { operand: 0 } + - { physical_register: X0, flags: [use] } + - { operand: 1 } diff --git a/backend/lib/Target/RISCV/RISCVGISelFloating.rccdesc b/backend/lib/Target/RISCV/RISCVGISelFloating.rccdesc new file mode 100644 index 0000000..940da64 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVGISelFloating.rccdesc @@ -0,0 +1,853 @@ +selection_rules: + - + id: select_g_fcmp_oeq_s32 + priority: 100 + match: + opcode: G_FCMP + result_type: { kind: scalar, bits: 64 } + floating_predicate: { index: 1, value: oeq } + operand_types: + - { index: 2, kind: scalar, bits: 32 } + - { index: 3, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: FEQ_S + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 3 }] + - + id: select_g_fcmp_olt_s32 + priority: 100 + match: + opcode: G_FCMP + result_type: { kind: scalar, bits: 64 } + floating_predicate: { index: 1, value: olt } + operand_types: + - { index: 2, kind: scalar, bits: 32 } + - { index: 3, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: FLT_S + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 3 }] + - + id: select_g_fcmp_ole_s32 + priority: 100 + match: + opcode: G_FCMP + result_type: { kind: scalar, bits: 64 } + floating_predicate: { index: 1, value: ole } + operand_types: + - { index: 2, kind: scalar, bits: 32 } + - { index: 3, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: FLE_S + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 3 }] + - + id: select_g_fcmp_oeq_s64 + priority: 100 + match: + opcode: G_FCMP + result_type: { kind: scalar, bits: 64 } + floating_predicate: { index: 1, value: oeq } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + - { index: 3, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: FEQ_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 3 }] + - + id: select_g_fcmp_olt_s64 + priority: 100 + match: + opcode: G_FCMP + result_type: { kind: scalar, bits: 64 } + floating_predicate: { index: 1, value: olt } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + - { index: 3, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: FLT_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 3 }] + - + id: select_g_fcmp_ole_s64 + priority: 100 + match: + opcode: G_FCMP + result_type: { kind: scalar, bits: 64 } + floating_predicate: { index: 1, value: ole } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + - { index: 3, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: FLE_D + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 3 }] + - + id: select_g_fadd_s32 + priority: 100 + match: + opcode: G_FADD + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FADD_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { immediate: 7 }] + - + id: select_g_fadd_s64 + priority: 100 + match: + opcode: G_FADD + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FADD_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { immediate: 7 }] + - + id: select_g_fsub_s32 + priority: 100 + match: + opcode: G_FSUB + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FSUB_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { immediate: 7 }] + - + id: select_g_fsub_s64 + priority: 100 + match: + opcode: G_FSUB + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FSUB_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { immediate: 7 }] + - + id: select_g_fmul_s32 + priority: 100 + match: + opcode: G_FMUL + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FMUL_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { immediate: 7 }] + - + id: select_g_fmul_s64 + priority: 100 + match: + opcode: G_FMUL + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FMUL_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { immediate: 7 }] + - + id: select_g_fdiv_s32 + priority: 100 + match: + opcode: G_FDIV + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FDIV_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { immediate: 7 }] + - + id: select_g_fdiv_s64 + priority: 100 + match: + opcode: G_FDIV + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FDIV_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { immediate: 7 }] + - + id: select_g_fma_s32 + priority: 100 + match: + opcode: G_FMA + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 32 } + - { index: 3, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: FMADD_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { operand: 3 }, { immediate: 7 }] + - + id: select_g_fma_s64 + priority: 100 + match: + opcode: G_FMA + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + - { index: 3, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: FMADD_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }, { operand: 3 }, { immediate: 7 }] + - + id: select_g_fsqrt_s32 + priority: 100 + match: + opcode: G_FSQRT + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FSQRT_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 7 }] + - + id: select_g_fsqrt_s64 + priority: 100 + match: + opcode: G_FSQRT + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FSQRT_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 7 }] + - + id: select_g_fneg_s32 + priority: 100 + match: + opcode: G_FNEG + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FSGNJN_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 1 }] + - + id: select_g_fneg_s64 + priority: 100 + match: + opcode: G_FNEG + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FSGNJN_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 1 }] + - + id: select_g_fabs_s32 + priority: 100 + match: + opcode: G_FABS + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FSGNJX_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 1 }] + - + id: select_g_fabs_s64 + priority: 100 + match: + opcode: G_FABS + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FSGNJX_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 1 }] + - + id: select_g_fcopysign_s32_fneg + priority: 200 + match: + opcode: G_FCOPYSIGN + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + def_use: + - operand: 2 + opcode: G_FNEG + captures: + - { id: sign, operand: 1 } + apply: + instructions: + - opcode: FSGNJN_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { captured_operand: sign }] + erase_operand_definitions: [2] + - + id: select_g_fcopysign_s64_fneg + priority: 200 + match: + opcode: G_FCOPYSIGN + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + def_use: + - operand: 2 + opcode: G_FNEG + captures: + - { id: sign, operand: 1 } + apply: + instructions: + - opcode: FSGNJN_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { captured_operand: sign }] + erase_operand_definitions: [2] + - + id: select_g_fcopysign_s32_s32 + priority: 100 + match: + opcode: G_FCOPYSIGN + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FSGNJ_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fcopysign_s64_s64 + priority: 100 + match: + opcode: G_FCOPYSIGN + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FSGNJ_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fcopysign_s32_s64 + priority: 100 + match: + opcode: G_FCOPYSIGN + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FSGNJ_S + root: true + operands: + - { operand: 0 } + - { operand: 1 } + - { complex: fcopysign_to_s32, operand: 2 } + - + id: select_g_fcopysign_s64_s32 + priority: 100 + match: + opcode: G_FCOPYSIGN + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FSGNJ_D + root: true + operands: + - { operand: 0 } + - { operand: 1 } + - { complex: fcopysign_to_s64, operand: 2 } + - + id: select_g_fminnum_s32 + priority: 100 + match: + opcode: G_FMINNUM + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FMIN_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fminnum_s64 + priority: 100 + match: + opcode: G_FMINNUM + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FMIN_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fmaxnum_s32 + priority: 100 + match: + opcode: G_FMAXNUM + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + - { index: 2, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FMAX_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fmaxnum_s64 + priority: 100 + match: + opcode: G_FMAXNUM + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + - { index: 2, bank: FPR } + apply: + instructions: + - opcode: FMAX_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fpext_s32_to_s64 + priority: 100 + match: + opcode: G_FPEXT + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_D_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 0 }] + - + id: select_g_fptrunc_s64_to_s32 + priority: 100 + match: + opcode: G_FPTRUNC + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_S_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 7 }] + - + id: select_g_fptosi_s32 + priority: 100 + match: + opcode: G_FPTOSI + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_L_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 1 }] + - + id: select_g_fptosi_s64 + priority: 100 + match: + opcode: G_FPTOSI + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_L_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 1 }] + - + id: select_g_fptoui_s32 + priority: 100 + match: + opcode: G_FPTOUI + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_LU_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 1 }] + - + id: select_g_fptoui_s64 + priority: 100 + match: + opcode: G_FPTOUI + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_LU_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 1 }] + - + id: select_g_sitofp_s32 + priority: 100 + match: + opcode: G_SITOFP + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: FCVT_S_L + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 7 }] + - + id: select_g_sitofp_s64 + priority: 100 + match: + opcode: G_SITOFP + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: FCVT_D_L + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 7 }] + - + id: select_g_uitofp_s32 + priority: 100 + match: + opcode: G_UITOFP + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: FCVT_S_LU + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 7 }] + - + id: select_g_uitofp_s64 + priority: 100 + match: + opcode: G_UITOFP + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: FCVT_D_LU + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 7 }] + - + id: select_g_select_fpr32 + priority: 100 + match: + opcode: G_SELECT + result_type: { kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: GPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: Select_FPR32_Using_CC_GPR + root: true + operands: + - { operand: 0 } + - { complex: select_condition_lhs, operand: 1 } + - { complex: select_condition_rhs, operand: 1 } + - { complex: select_condition_code, operand: 1 } + - { operand: 2 } + - { operand: 3 } + - + id: select_g_select_fpr64 + priority: 100 + match: + opcode: G_SELECT + result_type: { kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: GPR } + - { index: 2, bank: FPR } + - { index: 3, bank: FPR } + apply: + instructions: + - opcode: Select_FPR64_Using_CC_GPR + root: true + operands: + - { operand: 0 } + - { complex: select_condition_lhs, operand: 1 } + - { complex: select_condition_rhs, operand: 1 } + - { complex: select_condition_code, operand: 1 } + - { operand: 2 } + - { operand: 3 } + - + id: select_g_fpextload_s32_to_s64 + priority: 300 + match: + opcode: G_FPEXTLOAD + result_type: { kind: scalar, bits: 64 } + memory: { index: 0, bytes: 4 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: GPR } + apply: + temporaries: + - { id: loaded, type: { kind: scalar, bits: 32 } } + instructions: + - opcode: FLW + root: true + operands: + - { temporary: loaded, flags: [define] } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } + - opcode: FCVT_D_S + root: false + operands: + - { operand: 0 } + - { temporary: loaded, flags: [use] } + - { immediate: 0 } + - + id: select_g_fptruncstore_s64_to_s32 + priority: 300 + match: + opcode: G_FPTRUNCSTORE + memory: { index: 0, bytes: 4 } + operand_types: + - { index: 0, kind: scalar, bits: 64 } + - { index: 1, kind: pointer, bits: 64 } + operand_banks: + - { index: 0, bank: FPR } + - { index: 1, bank: GPR } + apply: + temporaries: + - { id: rounded, type: { kind: scalar, bits: 32 } } + instructions: + - opcode: FCVT_S_D + root: false + operands: + - { temporary: rounded, flags: [define] } + - { operand: 0 } + - { immediate: 7 } + - opcode: FSW + root: true + operands: + - { temporary: rounded, flags: [use] } + - { complex: addr_reg_imm_base, operand: 1 } + - { complex: addr_reg_imm_offset, operand: 1 } diff --git a/backend/lib/Target/RISCV/RISCVGISelInteger.rccdesc b/backend/lib/Target/RISCV/RISCVGISelInteger.rccdesc new file mode 100644 index 0000000..d217460 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVGISelInteger.rccdesc @@ -0,0 +1,1396 @@ +selection_rules: + - + id: select_g_implicit_def + priority: 100 + match: + opcode: G_IMPLICIT_DEF + apply: + instructions: + - opcode: IMPLICIT_DEF + root: true + operands: [{ operand: 0 }] + - + id: select_g_sext_s64_s32 + priority: 500 + match: + opcode: G_SEXT + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: ADDIW + root: true + operands: [{ operand: 0 }, { operand: 1 }, { immediate: 0 }] + - + id: select_g_sext_s64_s16 + priority: 400 + match: + opcode: G_SEXT + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 16 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + temporaries: + - { id: extended, type_from_operand: 0 } + instructions: + - opcode: SLLI + root: true + operands: + - { temporary: extended, flags: [define] } + - { operand: 1 } + - { immediate: 48 } + - opcode: SRAI + root: false + operands: + - { operand: 0 } + - { temporary: extended, flags: [use] } + - { immediate: 48 } + - + id: select_g_sext_s32_s16 + priority: 400 + match: + opcode: G_SEXT + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 16 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + temporaries: + - { id: extended, type_from_operand: 0 } + instructions: + - opcode: SLLIW + root: true + operands: + - { temporary: extended, flags: [define] } + - { operand: 1 } + - { immediate: 16 } + - opcode: SRAIW + root: false + operands: + - { operand: 0 } + - { temporary: extended, flags: [use] } + - { immediate: 16 } + - + id: select_g_zext_s64_s32_zba + priority: 500 + match: + opcode: G_ZEXT + result_type: { kind: scalar, bits: 64 } + target_predicates: [zba] + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + instructions: + - opcode: ADD_UW + root: true + operands: + - { operand: 0 } + - { operand: 1 } + - { physical_register: X0, flags: [use] } + - + id: select_g_zext_s64_s32 + priority: 400 + match: + opcode: G_ZEXT + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + temporaries: + - { id: extended, type_from_operand: 0 } + instructions: + - opcode: SLLI + root: true + operands: + - { temporary: extended, flags: [define] } + - { operand: 1 } + - { immediate: 32 } + - opcode: SRLI + root: false + operands: + - { operand: 0 } + - { temporary: extended, flags: [use] } + - { immediate: 32 } + - + id: select_g_zext_s64_s16 + priority: 400 + match: + opcode: G_ZEXT + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 16 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + temporaries: + - { id: extended, type_from_operand: 0 } + instructions: + - opcode: SLLI + root: true + operands: + - { temporary: extended, flags: [define] } + - { operand: 1 } + - { immediate: 48 } + - opcode: SRLI + root: false + operands: + - { operand: 0 } + - { temporary: extended, flags: [use] } + - { immediate: 48 } + - + id: select_g_zext_s32_s16 + priority: 400 + match: + opcode: G_ZEXT + result_type: { kind: scalar, bits: 32 } + operand_types: + - { index: 1, kind: scalar, bits: 16 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + apply: + temporaries: + - { id: extended, type_from_operand: 0 } + instructions: + - opcode: SLLIW + root: true + operands: + - { temporary: extended, flags: [define] } + - { operand: 1 } + - { immediate: 16 } + - opcode: SRLIW + root: false + operands: + - { operand: 0 } + - { temporary: extended, flags: [use] } + - { immediate: 16 } + - + id: select_g_sext_inreg_s64_32 + priority: 500 + match: + opcode: G_SEXT_INREG + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + operand_immediates: + - index: 2 + ranges: [{ min: 32, max: 32 }] + apply: + instructions: + - opcode: ADDIW + root: true + operands: + - { operand: 0 } + - { operand: 1 } + - { immediate: 0 } + - + id: select_g_ptr_add_immediate + priority: 400 + match: + opcode: G_PTR_ADD + result_type: { kind: pointer, bits: 64 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: -2048, max: 2047 }] + apply: + instructions: + - opcode: ADDI + root: true + operands: + - { operand: 0 } + - { operand: 1 } + - { captured_immediate: 2 } + erase_operand_definitions: [2] + - + id: select_g_ptrmask_immediate + priority: 400 + match: + opcode: G_PTRMASK + result_type: { kind: pointer, bits: 64 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: -2048, max: 2047 }] + apply: + instructions: + - opcode: ANDI + root: true + operands: + - { operand: 0 } + - { operand: 1 } + - { captured_immediate: 2 } + erase_operand_definitions: [2] + - + id: select_g_ptr_add_register + priority: 100 + match: + opcode: G_PTR_ADD + result_type: { kind: pointer, bits: 64 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: ADD + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_ptrmask_register + priority: 100 + match: + opcode: G_PTRMASK + result_type: { kind: pointer, bits: 64 } + operand_types: + - { index: 1, kind: pointer, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AND + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_add_immediate + priority: 400 + match: + opcode: G_ADD + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: -2048, max: 2047 }] + apply: + instructions: + - opcode: ADDI + root: true + operands: [{ operand: 0 }, { operand: 1 }, { captured_immediate: 2 }] + erase_operand_definitions: [2] + - + id: select_g_and_immediate + priority: 400 + match: + opcode: G_AND + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: -2048, max: 2047 }] + apply: + instructions: + - opcode: ANDI + root: true + operands: [{ operand: 0 }, { operand: 1 }, { captured_immediate: 2 }] + erase_operand_definitions: [2] + - + id: select_g_or_immediate + priority: 400 + match: + opcode: G_OR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: -2048, max: 2047 }] + apply: + instructions: + - opcode: ORI + root: true + operands: [{ operand: 0 }, { operand: 1 }, { captured_immediate: 2 }] + erase_operand_definitions: [2] + - + id: select_g_xor_immediate + priority: 400 + match: + opcode: G_XOR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: -2048, max: 2047 }] + apply: + instructions: + - opcode: XORI + root: true + operands: [{ operand: 0 }, { operand: 1 }, { captured_immediate: 2 }] + erase_operand_definitions: [2] + - + id: select_g_add_register + priority: 100 + match: + opcode: G_ADD + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: ADD + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_sub_register + priority: 100 + match: + opcode: G_SUB + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: SUB + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_mul_register + priority: 100 + match: + opcode: G_MUL + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: MUL + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_smulh_register + priority: 100 + match: + opcode: G_SMULH + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: MULH + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_umulh_register + priority: 100 + match: + opcode: G_UMULH + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: MULHU + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_sdiv_register + priority: 100 + match: + opcode: G_SDIV + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: DIV + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_udiv_register + priority: 100 + match: + opcode: G_UDIV + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: DIVU + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_srem_register + priority: 100 + match: + opcode: G_SREM + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: REM + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_urem_register + priority: 100 + match: + opcode: G_UREM + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: REMU + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_and_register + priority: 100 + match: + opcode: G_AND + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: AND + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_or_register + priority: 100 + match: + opcode: G_OR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: OR + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_xor_register + priority: 100 + match: + opcode: G_XOR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: XOR + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_sllw + priority: 100 + match: + opcode: G_SLLW + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: SLLW + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_sraw + priority: 100 + match: + opcode: G_SRAW + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: SRAW + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_srlw + priority: 100 + match: + opcode: G_SRLW + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: SRLW + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_divw + priority: 100 + match: + opcode: G_DIVW + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: DIVW + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_divuw + priority: 100 + match: + opcode: G_DIVUW + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: DIVUW + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_remuw + priority: 100 + match: + opcode: G_REMUW + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: REMUW + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fcvt_w_rv64_s32 + priority: 100 + match: + opcode: G_FCVT_W_RV64 + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_W_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fcvt_w_rv64_s64 + priority: 100 + match: + opcode: G_FCVT_W_RV64 + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_W_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fcvt_wu_rv64_s32 + priority: 100 + match: + opcode: G_FCVT_WU_RV64 + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_WU_S + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fcvt_wu_rv64_s64 + priority: 100 + match: + opcode: G_FCVT_WU_RV64 + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCVT_WU_D + root: true + operands: [{ operand: 0 }, { operand: 1 }, { operand: 2 }] + - + id: select_g_fclass_s32 + priority: 100 + match: + opcode: G_FCLASS + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 32 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCLASS_S + root: true + operands: [{ operand: 0 }, { operand: 1 }] + - + id: select_g_fclass_s64 + priority: 100 + match: + opcode: G_FCLASS + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: FPR } + apply: + instructions: + - opcode: FCLASS_D + root: true + operands: [{ operand: 0 }, { operand: 1 }] + - + id: select_g_icmp_eq_zero + priority: 500 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: eq } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: [{ min: 0, max: 0 }] + apply: + instructions: + - opcode: SLTIU + root: true + operands: [{ operand: 0 }, { operand: 2 }, { immediate: 1 }] + erase_operand_definitions: [3] + - + id: select_g_icmp_ne_zero + priority: 500 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: ne } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: [{ min: 0, max: 0 }] + apply: + instructions: + - opcode: SLTU + root: true + operands: + - { operand: 0 } + - { physical_register: X0, flags: [use] } + - { operand: 2 } + erase_operand_definitions: [3] + - + id: select_g_icmp_ult_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: ult } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: [{ min: -2048, max: 2047 }] + apply: + instructions: + - opcode: SLTIU + root: true + operands: + - { operand: 0 } + - { operand: 2 } + - { captured_immediate: 3 } + erase_operand_definitions: [3] + - + id: select_g_icmp_slt_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: slt } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: [{ min: -2048, max: 2047 }] + apply: + instructions: + - opcode: SLTI + root: true + operands: + - { operand: 0 } + - { operand: 2 } + - { captured_immediate: 3 } + erase_operand_definitions: [3] + - + id: select_g_icmp_ugt_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: ugt } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: + - { min: -2049, max: -2 } + - { min: 1, max: 2046 } + apply: + temporaries: + - { id: compare, type_from_operand: 2 } + instructions: + - opcode: SLTIU + root: true + operands: + - { temporary: compare, flags: [define] } + - { operand: 2 } + - { captured_immediate: 3, transform: plus_one } + - opcode: XORI + root: false + operands: + - { operand: 0 } + - { temporary: compare, flags: [use] } + - { immediate: 1 } + erase_operand_definitions: [3] + - + id: select_g_select_gpr + priority: 100 + match: + opcode: G_SELECT + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + instructions: + - opcode: Select_GPR_Using_CC_GPR + root: true + operands: + - { operand: 0 } + - { complex: select_condition_lhs, operand: 1 } + - { complex: select_condition_rhs, operand: 1 } + - { complex: select_condition_code, operand: 1 } + - { operand: 2 } + - { operand: 3 } + - + id: select_g_icmp_sgt_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: sgt } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: + - { min: -2049, max: -1 } + - { min: 1, max: 2046 } + apply: + temporaries: + - { id: compare, type_from_operand: 2 } + instructions: + - opcode: SLTI + root: true + operands: + - { temporary: compare, flags: [define] } + - { operand: 2 } + - { captured_immediate: 3, transform: plus_one } + - opcode: XORI + root: false + operands: + - { operand: 0 } + - { temporary: compare, flags: [use] } + - { immediate: 1 } + erase_operand_definitions: [3] + - + id: select_g_icmp_eq_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: eq } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: [{ min: -2047, max: 2048 }] + apply: + temporaries: + - { id: difference, type_from_operand: 2 } + instructions: + - opcode: ADDI + root: true + operands: + - { temporary: difference, flags: [define] } + - { operand: 2 } + - { captured_immediate: 3, transform: negate } + - opcode: SLTIU + root: false + operands: + - { operand: 0 } + - { temporary: difference, flags: [use] } + - { immediate: 1 } + erase_operand_definitions: [3] + - + id: select_g_icmp_ne_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: ne } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: [{ min: -2047, max: 2048 }] + apply: + temporaries: + - { id: difference, type_from_operand: 2 } + instructions: + - opcode: ADDI + root: true + operands: + - { temporary: difference, flags: [define] } + - { operand: 2 } + - { captured_immediate: 3, transform: negate } + - opcode: SLTU + root: false + operands: + - { operand: 0 } + - { physical_register: X0, flags: [use] } + - { temporary: difference, flags: [use] } + erase_operand_definitions: [3] + - + id: select_g_icmp_ule_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: ule } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: + - { min: -2049, max: -2 } + - { min: 1, max: 2046 } + apply: + instructions: + - opcode: SLTIU + root: true + operands: + - { operand: 0 } + - { operand: 2 } + - { captured_immediate: 3, transform: plus_one } + erase_operand_definitions: [3] + - + id: select_g_icmp_sle_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: sle } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: + - { min: -2049, max: -1 } + - { min: 1, max: 2046 } + apply: + instructions: + - opcode: SLTI + root: true + operands: + - { operand: 0 } + - { operand: 2 } + - { captured_immediate: 3, transform: plus_one } + erase_operand_definitions: [3] + - + id: select_g_icmp_uge_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: uge } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: [{ min: -2048, max: 2047 }] + apply: + temporaries: + - { id: compare, type_from_operand: 2 } + instructions: + - opcode: SLTIU + root: true + operands: + - { temporary: compare, flags: [define] } + - { operand: 2 } + - { captured_immediate: 3 } + - opcode: XORI + root: false + operands: + - { operand: 0 } + - { temporary: compare, flags: [use] } + - { immediate: 1 } + erase_operand_definitions: [3] + - + id: select_g_icmp_sge_immediate + priority: 400 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: sge } + operand_types: + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 3 + ranges: [{ min: -2048, max: 2047 }] + apply: + temporaries: + - { id: compare, type_from_operand: 2 } + instructions: + - opcode: SLTI + root: true + operands: + - { temporary: compare, flags: [define] } + - { operand: 2 } + - { captured_immediate: 3 } + - opcode: XORI + root: false + operands: + - { operand: 0 } + - { temporary: compare, flags: [use] } + - { immediate: 1 } + erase_operand_definitions: [3] + - + id: select_g_icmp_ult_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: ult } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + instructions: + - opcode: SLTU + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 3 }] + - + id: select_g_icmp_slt_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: slt } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + instructions: + - opcode: SLT + root: true + operands: [{ operand: 0 }, { operand: 2 }, { operand: 3 }] + - + id: select_g_icmp_ugt_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: ugt } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + instructions: + - opcode: SLTU + root: true + operands: [{ operand: 0 }, { operand: 3 }, { operand: 2 }] + - + id: select_g_icmp_sgt_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: sgt } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + instructions: + - opcode: SLT + root: true + operands: [{ operand: 0 }, { operand: 3 }, { operand: 2 }] + - + id: select_g_icmp_eq_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: eq } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + temporaries: + - { id: difference, type_from_operand: 2 } + instructions: + - opcode: XOR + root: true + operands: + - { temporary: difference, flags: [define] } + - { operand: 2 } + - { operand: 3 } + - opcode: SLTIU + root: false + operands: + - { operand: 0 } + - { temporary: difference, flags: [use] } + - { immediate: 1 } + - + id: select_g_icmp_ne_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: ne } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + temporaries: + - { id: difference, type_from_operand: 2 } + instructions: + - opcode: XOR + root: true + operands: + - { temporary: difference, flags: [define] } + - { operand: 2 } + - { operand: 3 } + - opcode: SLTU + root: false + operands: + - { operand: 0 } + - { physical_register: X0, flags: [use] } + - { temporary: difference, flags: [use] } + - + id: select_g_icmp_ule_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: ule } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + temporaries: + - { id: compare, type_from_operand: 2 } + instructions: + - opcode: SLTU + root: true + operands: + - { temporary: compare, flags: [define] } + - { operand: 3 } + - { operand: 2 } + - opcode: XORI + root: false + operands: + - { operand: 0 } + - { temporary: compare, flags: [use] } + - { immediate: 1 } + - + id: select_g_icmp_sle_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: sle } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + temporaries: + - { id: compare, type_from_operand: 2 } + instructions: + - opcode: SLT + root: true + operands: + - { temporary: compare, flags: [define] } + - { operand: 3 } + - { operand: 2 } + - opcode: XORI + root: false + operands: + - { operand: 0 } + - { temporary: compare, flags: [use] } + - { immediate: 1 } + - + id: select_g_icmp_uge_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: uge } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + temporaries: + - { id: compare, type_from_operand: 2 } + instructions: + - opcode: SLTU + root: true + operands: + - { temporary: compare, flags: [define] } + - { operand: 2 } + - { operand: 3 } + - opcode: XORI + root: false + operands: + - { operand: 0 } + - { temporary: compare, flags: [use] } + - { immediate: 1 } + - + id: select_g_icmp_sge_register + priority: 100 + match: + opcode: G_ICMP + result_type: { kind: scalar, bits: 64 } + integer_predicate: { index: 1, value: sge } + operand_types: + - { index: 2, kind: any, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 2, bank: GPR } + - { index: 3, bank: GPR } + apply: + temporaries: + - { id: compare, type_from_operand: 2 } + instructions: + - opcode: SLT + root: true + operands: + - { temporary: compare, flags: [define] } + - { operand: 2 } + - { operand: 3 } + - opcode: XORI + root: false + operands: + - { operand: 0 } + - { temporary: compare, flags: [use] } + - { immediate: 1 } diff --git a/backend/lib/Target/RISCV/RISCVGISelShift.rccdesc b/backend/lib/Target/RISCV/RISCVGISelShift.rccdesc new file mode 100644 index 0000000..bf9bf71 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVGISelShift.rccdesc @@ -0,0 +1,198 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +selection_rules: + - + id: select_g_lshr_s64_word_immediate + priority: 500 + match: + opcode: G_LSHR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: 0, max: 31 }] + def_use: + - operand: 1 + opcode: G_AND + captures: + - { id: source, operand: 1 } + operand_def_constants: + - index: 2 + ranges: [{ min: 4294967295, max: 4294967295 }] + apply: + instructions: + - opcode: SRLIW + root: true + operands: + - { operand: 0 } + - { captured_operand: source } + - { captured_immediate: 2 } + erase_operand_definitions: [1, 2] + - + id: select_g_ashr_s64_word_immediate + priority: 500 + match: + opcode: G_ASHR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: 0, max: 31 }] + def_use: + - operand: 1 + opcode: G_SEXT_INREG + captures: + - { id: source, operand: 1 } + operand_def_constants: + - index: 2 + ranges: [{ min: 32, max: 32 }] + apply: + instructions: + - opcode: SRAIW + root: true + operands: + - { operand: 0 } + - { captured_operand: source } + - { captured_immediate: 2 } + erase_operand_definitions: [1, 2] + - + id: select_g_shl_s64_immediate + priority: 400 + match: + opcode: G_SHL + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: 0, max: 63 }] + apply: + instructions: + - opcode: SLLI + root: true + operands: [{ operand: 0 }, { operand: 1 }, { captured_immediate: 2 }] + erase_operand_definitions: [2] + - + id: select_g_lshr_s64_immediate + priority: 400 + match: + opcode: G_LSHR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: 0, max: 63 }] + apply: + instructions: + - opcode: SRLI + root: true + operands: [{ operand: 0 }, { operand: 1 }, { captured_immediate: 2 }] + erase_operand_definitions: [2] + - + id: select_g_ashr_s64_immediate + priority: 400 + match: + opcode: G_ASHR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + operand_def_constants: + - index: 2 + ranges: [{ min: 0, max: 63 }] + apply: + instructions: + - opcode: SRAI + root: true + operands: [{ operand: 0 }, { operand: 1 }, { captured_immediate: 2 }] + erase_operand_definitions: [2] + - + id: select_g_shl_s64_register + priority: 100 + match: + opcode: G_SHL + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: SLL + root: true + operands: + - { operand: 0 } + - { operand: 1 } + - { complex: shift_mask_xlen, operand: 2 } + - + id: select_g_lshr_s64_register + priority: 100 + match: + opcode: G_LSHR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: SRL + root: true + operands: + - { operand: 0 } + - { operand: 1 } + - { complex: shift_mask_xlen, operand: 2 } + - + id: select_g_ashr_s64_register + priority: 100 + match: + opcode: G_ASHR + result_type: { kind: scalar, bits: 64 } + operand_types: + - { index: 1, kind: scalar, bits: 64 } + - { index: 2, kind: scalar, bits: 64 } + operand_banks: + - { index: 0, bank: GPR } + - { index: 1, bank: GPR } + - { index: 2, bank: GPR } + apply: + instructions: + - opcode: SRA + root: true + operands: + - { operand: 0 } + - { operand: 1 } + - { complex: shift_mask_xlen, operand: 2 } diff --git a/backend/lib/Target/RISCV/RISCVImmediateMaterialization.cpp b/backend/lib/Target/RISCV/RISCVImmediateMaterialization.cpp new file mode 100644 index 0000000..a3ed5ac --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVImmediateMaterialization.cpp @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCVImmediateMaterialization.h" + +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVSubtarget.h" + +#include <bit> +#include <cstddef> +#include <cstdint> +#include <optional> +#include <utility> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] constexpr bool fitsSigned(std::int64_t Value, std::uint32_t Bits) noexcept { + const std::int64_t Minimum = -(std::int64_t{1} << (Bits - 1U)); + const std::int64_t Maximum = (std::int64_t{1} << (Bits - 1U)) - 1; + return Value >= Minimum && Value <= Maximum; +} + +[[nodiscard]] constexpr std::int64_t signExtend(std::uint64_t Value, std::uint32_t Bits) noexcept { + if (Bits == 64) + return std::bit_cast<std::int64_t>(Value); + const std::uint64_t Mask = (std::uint64_t{1} << Bits) - 1U; + const std::uint64_t Sign = std::uint64_t{1} << (Bits - 1U); + Value &= Mask; + if ((Value & Sign) != 0) + Value |= ~Mask; + return std::bit_cast<std::int64_t>(Value); +} + +[[nodiscard]] constexpr std::uint64_t trailingBitMask(std::uint32_t Bits) noexcept { + return Bits == 64 ? ~std::uint64_t{0} : (std::uint64_t{1} << Bits) - 1U; +} + +[[nodiscard]] constexpr std::int64_t arithmeticShiftRight(std::uint64_t Value, + std::uint32_t Amount) noexcept { + return signExtend(Value >> Amount, 64U - Amount); +} + +void appendImmediate(ImmediateMaterializationSequence &Sequence, InstructionOpcode Opcode, + std::int64_t Immediate, ImmediateMaterializationOperandKind OperandKind) { + Sequence.push_back({.Opcode = Opcode, .Immediate = Immediate, .OperandKind = OperandKind}); +} + +void generateBaseSequence(std::int64_t Value, ImmediateMaterializationSequence &Sequence) { + const std::uint64_t Bits = std::bit_cast<std::uint64_t>(Value); + if (fitsSigned(Value, 32)) { + const std::int64_t High20 = static_cast<std::int64_t>(((Bits + 0x800U) >> 12U) & 0xFFFFFU); + const std::int64_t Low12 = signExtend(Bits, 12); + if (High20 != 0) { + appendImmediate(Sequence, InstructionOpcode::LUI, High20, + ImmediateMaterializationOperandKind::Immediate); + } + if (Low12 != 0 || High20 == 0) { + InstructionOpcode AddOpcode = InstructionOpcode::ADDI; + if (High20 != 0) { + const std::int64_t LUIResult = signExtend(static_cast<std::uint64_t>(High20) << 12U, 32); + if (!fitsSigned(LUIResult + Low12, 32)) + AddOpcode = InstructionOpcode::ADDIW; + } + appendImmediate(Sequence, AddOpcode, Low12, + ImmediateMaterializationOperandKind::RegisterImmediate); + } + return; + } + + const std::int64_t Low12 = signExtend(Bits, 12); + const std::uint64_t WithoutLow12 = Bits - static_cast<std::uint64_t>(Low12); + std::int64_t Remaining = std::bit_cast<std::int64_t>(WithoutLow12); + std::uint32_t ShiftAmount = 0; + if (!fitsSigned(Remaining, 32)) { + ShiftAmount = static_cast<std::uint32_t>(std::countr_zero(WithoutLow12)); + Remaining = arithmeticShiftRight(WithoutLow12, ShiftAmount); + if (ShiftAmount > 12 && !fitsSigned(Remaining, 12)) { + const std::int64_t ShiftedRemaining = + std::bit_cast<std::int64_t>(std::bit_cast<std::uint64_t>(Remaining) << 12U); + if (fitsSigned(ShiftedRemaining, 32)) { + ShiftAmount -= 12; + Remaining = ShiftedRemaining; + } + } + } + + generateBaseSequence(Remaining, Sequence); + if (ShiftAmount != 0) { + appendImmediate(Sequence, InstructionOpcode::SLLI, static_cast<std::int64_t>(ShiftAmount), + ImmediateMaterializationOperandKind::RegisterImmediate); + } + if (Low12 != 0) { + appendImmediate(Sequence, InstructionOpcode::ADDI, Low12, + ImmediateMaterializationOperandKind::RegisterImmediate); + } +} + +void considerLeadingZeroSequence(std::int64_t Value, ImmediateMaterializationSequence &Sequence) { + const std::uint64_t Bits = std::bit_cast<std::uint64_t>(Value); + const std::uint32_t LeadingZeros = static_cast<std::uint32_t>(std::countl_zero(Bits)); + const std::uint64_t Shifted = Bits << LeadingZeros; + const auto Consider = [&](std::uint64_t Candidate) { + ImmediateMaterializationSequence Temporary; + generateBaseSequence(std::bit_cast<std::int64_t>(Candidate), Temporary); + const bool Improves = Temporary.size() + 1U < Sequence.size(); + const bool InitializesSequence = Sequence.empty() && Temporary.size() < 8; + if (!Improves && !InitializesSequence) + return; + appendImmediate(Temporary, InstructionOpcode::SRLI, static_cast<std::int64_t>(LeadingZeros), + ImmediateMaterializationOperandKind::RegisterImmediate); + Sequence = std::move(Temporary); + }; + + Consider(Shifted | trailingBitMask(LeadingZeros)); + Consider(Shifted & ~trailingBitMask(LeadingZeros)); +} + +[[nodiscard]] std::optional<std::size_t> +twoRegisterBuildCost(std::int64_t Value, const RISCVSubtargetFeatures &Features) { + const std::uint64_t Bits = std::bit_cast<std::uint64_t>(Value); + const std::int64_t Low = signExtend(Bits, 32); + if (Low == 0) + return std::nullopt; + + const std::uint64_t HighContribution = Bits - static_cast<std::uint64_t>(Low); + if (HighContribution == 0) + return std::nullopt; + const std::uint32_t LowZeros = + static_cast<std::uint32_t>(std::countr_zero(static_cast<std::uint64_t>(Low))); + const std::uint32_t HighZeros = static_cast<std::uint32_t>(std::countr_zero(HighContribution)); + if (LowZeros >= 32 || HighZeros < 32 || HighZeros < LowZeros) + return std::nullopt; + const std::uint32_t ShiftAmount = HighZeros - LowZeros; + if (ShiftAmount >= 64 || HighContribution != (static_cast<std::uint64_t>(Low) << ShiftAmount)) { + return std::nullopt; + } + return generateRV64IntegerMaterialization(Low, Features).size() + 2U; +} + +} // namespace + +ImmediateMaterializationSequence +generateRV64IntegerMaterialization(std::int64_t Value, const RISCVSubtargetFeatures &Features) { + ImmediateMaterializationSequence Result; + generateBaseSequence(Value, Result); + + const std::uint64_t Bits = std::bit_cast<std::uint64_t>(Value); + if ((Bits & 0xFFFU) != 0 && (Bits & 1U) == 0 && Result.size() >= 2) { + const std::uint32_t TrailingZeros = static_cast<std::uint32_t>(std::countr_zero(Bits)); + const std::int64_t ShiftedValue = arithmeticShiftRight(Bits, TrailingZeros); + ImmediateMaterializationSequence Temporary; + generateBaseSequence(ShiftedValue, Temporary); + const bool PreferCompressibleShift = + fitsSigned(ShiftedValue, 6) && !Features.has(RISCVSubtargetOption::FusionLUIAddImmediate); + if (Temporary.size() + 1U < Result.size() || PreferCompressibleShift) { + appendImmediate(Temporary, InstructionOpcode::SLLI, static_cast<std::int64_t>(TrailingZeros), + ImmediateMaterializationOperandKind::RegisterImmediate); + Result = std::move(Temporary); + } + } + + if (Result.size() <= 2) + return Result; + + if ((Bits & 0xFFFU) != 0 && (Bits & 0x1800U) == 0x1000U) { + const std::int64_t LastAdd = -static_cast<std::int64_t>(0x800U - (Bits & 0xFFFU)); + const std::int64_t AdjustedValue = + std::bit_cast<std::int64_t>(Bits - static_cast<std::uint64_t>(LastAdd)); + ImmediateMaterializationSequence Temporary; + generateBaseSequence(AdjustedValue, Temporary); + if (Temporary.size() + 1U < Result.size()) { + appendImmediate(Temporary, InstructionOpcode::ADDI, LastAdd, + ImmediateMaterializationOperandKind::RegisterImmediate); + Result = std::move(Temporary); + } + } + + if (Value > 0 && Result.size() > 2) { + considerLeadingZeroSequence(Value, Result); + } else if (Value < 0 && Result.size() > 3) { + ImmediateMaterializationSequence Temporary; + considerLeadingZeroSequence(std::bit_cast<std::int64_t>(~Bits), Temporary); + if (!Temporary.empty() && Temporary.size() + 1U < Result.size()) { + appendImmediate(Temporary, InstructionOpcode::XORI, -1, + ImmediateMaterializationOperandKind::RegisterImmediate); + Result = std::move(Temporary); + } + } + return Result; +} + +std::size_t estimateRV64IntegerBuildCost(std::int64_t Value, + const RISCVSubtargetFeatures &Features) { + return generateRV64IntegerMaterialization(Value, Features).size(); +} + +bool shouldUseRV64IntegerConstantPool(std::int64_t Value, const RISCVSubtargetFeatures &Features, + std::size_t MaximumBuildCost) { + if (fitsSigned(Value, 32) || estimateRV64IntegerBuildCost(Value, Features) <= MaximumBuildCost) + return false; + const auto TwoRegisterCost = twoRegisterBuildCost(Value, Features); + return !TwoRegisterCost || *TwoRegisterCost > MaximumBuildCost; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVImmediateMaterialization.h b/backend/lib/Target/RISCV/RISCVImmediateMaterialization.h new file mode 100644 index 0000000..4d58ec2 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVImmediateMaterialization.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_LIB_TARGET_RISCV_RISCVIMMEDIATEMATERIALIZATION_H +#define RCC_LIB_TARGET_RISCV_RISCVIMMEDIATEMATERIALIZATION_H + +#include <cstddef> +#include <cstdint> +#include <vector> + +namespace rcc::riscv { + +enum class InstructionOpcode : std::uint32_t; +class RISCVSubtargetFeatures; + +enum class ImmediateMaterializationOperandKind : std::uint8_t { + Immediate, + RegisterImmediate, +}; + +struct ImmediateMaterializationInstruction final { + InstructionOpcode Opcode; + std::int64_t Immediate = 0; + ImmediateMaterializationOperandKind OperandKind = + ImmediateMaterializationOperandKind::RegisterImmediate; +}; + +using ImmediateMaterializationSequence = std::vector<ImmediateMaterializationInstruction>; + +inline constexpr std::size_t DefaultRV64IntegerBuildCost = 5; + +[[nodiscard]] ImmediateMaterializationSequence +generateRV64IntegerMaterialization(std::int64_t Value, const RISCVSubtargetFeatures &Features); + +[[nodiscard]] std::size_t estimateRV64IntegerBuildCost(std::int64_t Value, + const RISCVSubtargetFeatures &Features); + +[[nodiscard]] bool +shouldUseRV64IntegerConstantPool(std::int64_t Value, const RISCVSubtargetFeatures &Features, + std::size_t MaximumBuildCost = DefaultRV64IntegerBuildCost); + +} // namespace rcc::riscv + +#endif // RCC_LIB_TARGET_RISCV_RISCVIMMEDIATEMATERIALIZATION_H diff --git a/backend/lib/Target/RISCV/RISCVInstPrinter.cpp b/backend/lib/Target/RISCV/RISCVInstPrinter.cpp new file mode 100644 index 0000000..b66d909 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstPrinter.cpp @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVInstPrinter.h" + +#include "rcc/CodeGen/Register.h" +#include "rcc/MC/MCExpr.h" +#include "rcc/MC/MCInst.h" +#include "rcc/MC/MCInstPrinter.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <array> +#include <charconv> +#include <cstddef> +#include <cstdint> +#include <optional> +#include <ranges> +#include <string> +#include <string_view> + +namespace rcc::riscv { +namespace { + +struct LinearFormat final { + std::string_view Name; + std::size_t OperandCount = 0; +}; + +constexpr std::array LinearFormats{ + LinearFormat{"none", 0}, + LinearFormat{"rd-imm", 2}, + LinearFormat{"rd-rs1-imm", 3}, + LinearFormat{"rd-rs1-rs2", 3}, + LinearFormat{"rs1-rs2-target", 3}, + LinearFormat{"rd-target", 2}, + LinearFormat{"imm-imm", 2}, + LinearFormat{"rd-csr-rs1", 3}, + LinearFormat{"rd-csr-imm", 3}, + LinearFormat{"frd-frs1-frs2-rm", 4}, + LinearFormat{"frd-frs1-frs2-frs3-rm", 5}, + LinearFormat{"frd-frs1-rm", 3}, + LinearFormat{"frd-frs1-frs2", 3}, + LinearFormat{"rd-frs1-rm", 3}, + LinearFormat{"rd-frs1", 2}, + LinearFormat{"rd-frs1-frs2", 3}, + LinearFormat{"frd-rs1-rm", 3}, + LinearFormat{"frd-rs1", 2}, + LinearFormat{"target", 1}, + LinearFormat{"rs1-target", 2}, + LinearFormat{"rs1", 1}, + LinearFormat{"rd-rs2", 2}, + LinearFormat{"rd-rs1", 2}, +}; + +[[nodiscard]] const AssemblyFormatInfo *assemblyFormat(AssemblyFormatID ID) noexcept { + const auto Formats = assemblyFormats(); + const auto Position = std::ranges::find(Formats, ID, &AssemblyFormatInfo::ID); + return Position == Formats.end() ? nullptr : &*Position; +} + +[[nodiscard]] std::optional<std::size_t> linearOperandCount(std::string_view Name) noexcept { + const auto *const Position = std::ranges::find(LinearFormats, Name, &LinearFormat::Name); + return Position == LinearFormats.end() ? std::nullopt : std::optional(Position->OperandCount); +} + +[[nodiscard]] Expected<void> requireOperandCount(const mc::MCInst &Instruction, + std::size_t ExpectedCount) { + if (Instruction.operands().size() != ExpectedCount) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V assembly format has an unexpected operand count"); + } + return {}; +} + +[[nodiscard]] std::string hexValue(std::uint64_t Value) { + std::array<char, 16> Digits{}; + const auto [End, Error] = std::to_chars(Digits.data(), Digits.data() + Digits.size(), Value, 16); + if (Error != std::errc{}) + return {}; + return "0x" + std::string(Digits.data(), End); +} + +[[nodiscard]] Expected<std::string> formatOperand(const mc::MCOperand &Operand, + const TargetDescription &Target, + const mc::MCInstPrinter &Printer) { + if (const mc::MCRegister *Register = Operand.registerValue(); Register != nullptr) { + auto Physical = PhysicalRegister::create(Register->value()); + if (!Physical) + return std::unexpected(Physical.error()); + const auto Name = Target.registers().physicalRegisterMIRName(*Physical); + if (!Name) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V MC instruction references an unknown register"); + } + std::string AssemblyName(*Name); + if (AssemblyName.ends_with("_f") || AssemblyName.ends_with("_d")) + AssemblyName.resize(AssemblyName.size() - 2); + return AssemblyName; + } + if (const std::int64_t *Immediate = Operand.immediateValue(); Immediate != nullptr) + return std::to_string(*Immediate); + if (const std::uint32_t *Bits = Operand.float32BitsValue(); Bits != nullptr) + return hexValue(*Bits); + if (const std::uint64_t *Bits = Operand.float64BitsValue(); Bits != nullptr) + return hexValue(*Bits); + if (const mc::MCExprRef *Expression = Operand.expressionValue(); Expression != nullptr) { + if (!*Expression) + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V MC instruction has a null expression operand"); + return mc::printMCExpression(**Expression, Printer); + } + return makeUnexpected(ErrorCode::InternalError, "RISC-V MC operand storage is invalid"); +} + +[[nodiscard]] Expected<std::string> formatRegisterOperand(const mc::MCInst &Instruction, + std::size_t Index, + const TargetDescription &Target, + const mc::MCInstPrinter &Printer) { + if (Index >= Instruction.operands().size() || + Instruction.operands()[Index].registerValue() == nullptr) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V assembly format requires a register operand"); + } + return formatOperand(Instruction.operands()[Index], Target, Printer); +} + +[[nodiscard]] Expected<std::string> formatAnyOperand(const mc::MCInst &Instruction, + std::size_t Index, + const TargetDescription &Target, + const mc::MCInstPrinter &Printer) { + if (Index >= Instruction.operands().size()) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V assembly format references an absent operand"); + } + return formatOperand(Instruction.operands()[Index], Target, Printer); +} + +[[nodiscard]] Expected<std::string> formatAddress(const mc::MCInst &Instruction, + std::size_t OffsetIndex, std::size_t BaseIndex, + const TargetDescription &Target, + const mc::MCInstPrinter &Printer) { + auto Offset = formatAnyOperand(Instruction, OffsetIndex, Target, Printer); + if (!Offset) + return std::unexpected(Offset.error()); + auto Base = formatRegisterOperand(Instruction, BaseIndex, Target, Printer); + if (!Base) + return std::unexpected(Base.error()); + return *Offset + "(" + *Base + ")"; +} + +[[nodiscard]] Expected<std::string> joinOperands(const mc::MCInst &Instruction, std::size_t Count, + const TargetDescription &Target, + const mc::MCInstPrinter &Printer) { + auto CountResult = requireOperandCount(Instruction, Count); + if (!CountResult) + return std::unexpected(CountResult.error()); + std::string Result; + for (std::size_t Index = 0; Index < Count; ++Index) { + auto Operand = formatAnyOperand(Instruction, Index, Target, Printer); + if (!Operand) + return std::unexpected(Operand.error()); + if (!Result.empty()) + Result += ", "; + Result += *Operand; + } + return Result; +} + +[[nodiscard]] Expected<std::string> formatZcmpRegisterList(const mc::MCOperand &Operand) { + const std::int64_t *Encoding = Operand.immediateValue(); + if (Encoding == nullptr || *Encoding < 4 || *Encoding > 15) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "Zcmp register list encoding is out of range"); + } + std::string Result = "{ra"; + if (*Encoding > 4) { + Result += ", s0"; + if (*Encoding == 15) + Result += "-s11"; + else if (*Encoding > 5) + Result += "-s" + std::to_string(*Encoding - 5); + } + Result += "}"; + return Result; +} + +[[nodiscard]] Expected<std::string> formatZcmpStackAdjustment(const mc::MCInst &Instruction, + bool Negate) { + if (Instruction.operands().size() != 2) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "Zcmp stack instruction requires two operands"); + } + const std::int64_t *RegisterList = Instruction.operands()[0].immediateValue(); + const std::int64_t *ExtraAdjustment = Instruction.operands()[1].immediateValue(); + if (RegisterList == nullptr || ExtraAdjustment == nullptr || *RegisterList < 4 || + *RegisterList > 15 || *ExtraAdjustment < 0 || *ExtraAdjustment > 48 || + *ExtraAdjustment % 16 != 0) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "Zcmp stack instruction has invalid encoded operands"); + } + std::int64_t RegisterCount = *RegisterList - 3; + if (*RegisterList == 15) + ++RegisterCount; + const std::int64_t BaseAdjustment = ((RegisterCount * 8 + 15) / 16) * 16; + const std::int64_t Adjustment = BaseAdjustment + *ExtraAdjustment; + return std::to_string(Negate ? -Adjustment : Adjustment); +} + +} // namespace + +Expected<std::string> RISCVInstPrinter::printInstruction(const mc::MCInst &Instruction) const { + const AssemblyInstructionInfo *Assembly = + assemblyInstruction(Opcode::fromValue(Instruction.opcode().value())); + if (Assembly == nullptr || Assembly->Mnemonic.empty()) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V instruction has no generated assembly spelling"); + } + const AssemblyFormatInfo *Format = assemblyFormat(Assembly->Format); + if (Format == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V instruction references an unknown assembly format"); + } + + std::string Operands; + if (const auto LinearCount = linearOperandCount(Format->Name)) { + auto Joined = joinOperands(Instruction, *LinearCount, *Target, *this); + if (!Joined) + return std::unexpected(Joined.error()); + Operands = std::move(*Joined); + } else if (Format->Name == "rd-offset-rs1" || Format->Name == "frd-offset-rs1" || + Format->Name == "rs2-offset-rs1" || Format->Name == "frs2-offset-rs1") { + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto First = formatRegisterOperand(Instruction, 0, *Target, *this); + if (!First) + return std::unexpected(First.error()); + auto Address = formatAddress(Instruction, 2, 1, *Target, *this); + if (!Address) + return std::unexpected(Address.error()); + Operands = *First + ", " + *Address; + } else if (Format->Name == "rd-paren-rs1") { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = formatRegisterOperand(Instruction, 0, *Target, *this); + if (!Destination) + return std::unexpected(Destination.error()); + auto Base = formatRegisterOperand(Instruction, 1, *Target, *this); + if (!Base) + return std::unexpected(Base.error()); + Operands = *Destination + ", (" + *Base + ")"; + } else if (Format->Name == "rd-rs2-paren-rs1") { + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = formatRegisterOperand(Instruction, 0, *Target, *this); + if (!Destination) + return std::unexpected(Destination.error()); + auto Value = formatRegisterOperand(Instruction, 1, *Target, *this); + if (!Value) + return std::unexpected(Value.error()); + auto Base = formatRegisterOperand(Instruction, 2, *Target, *this); + if (!Base) + return std::unexpected(Base.error()); + Operands = *Destination + ", " + *Value + ", (" + *Base + ")"; + } else if (Format->Name == "imm-sp") { + auto Count = requireOperandCount(Instruction, 1); + if (!Count) + return std::unexpected(Count.error()); + auto Immediate = formatAnyOperand(Instruction, 0, *Target, *this); + if (!Immediate) + return std::unexpected(Immediate.error()); + Operands = "sp, " + *Immediate; + } else if (Format->Name == "rd-imm-sp") { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = formatRegisterOperand(Instruction, 0, *Target, *this); + if (!Destination) + return std::unexpected(Destination.error()); + auto Immediate = formatAnyOperand(Instruction, 1, *Target, *this); + if (!Immediate) + return std::unexpected(Immediate.error()); + Operands = *Destination + ", sp, " + *Immediate; + } else if (Format->Name == "rd-offset-sp" || Format->Name == "frd-offset-sp" || + Format->Name == "rs2-offset-sp" || Format->Name == "frs2-offset-sp") { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto First = formatRegisterOperand(Instruction, 0, *Target, *this); + if (!First) + return std::unexpected(First.error()); + auto Offset = formatAnyOperand(Instruction, 1, *Target, *this); + if (!Offset) + return std::unexpected(Offset.error()); + Operands = *First + ", " + *Offset + "(sp)"; + } else if (Format->Name == "rlist-stackadj") { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto RegisterList = formatZcmpRegisterList(Instruction.operands()[0]); + if (!RegisterList) + return std::unexpected(RegisterList.error()); + auto StackAdjustment = formatZcmpStackAdjustment(Instruction, Assembly->Mnemonic == "cm.push"); + if (!StackAdjustment) + return std::unexpected(StackAdjustment.error()); + Operands = *RegisterList + ", " + *StackAdjustment; + } else { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V assembly printer does not support generated format '" + + std::string(Format->Name) + "'"); + } + + std::string Result(Assembly->Mnemonic); + if (!Operands.empty()) + Result += " " + Operands; + return Result; +} + +Expected<std::string> RISCVInstPrinter::printTargetExpression(std::uint32_t Kind, + std::string_view Operand) const { + switch (static_cast<RISCVMCExprKind>(Kind)) { + case RISCVMCExprKind::CallPLT: + return std::string(Operand); + case RISCVMCExprKind::PCRelativeHigh: + return "%pcrel_hi(" + std::string(Operand) + ")"; + case RISCVMCExprKind::PCRelativeLow: + return "%pcrel_lo(" + std::string(Operand) + ")"; + case RISCVMCExprKind::AbsoluteHigh: + return "%hi(" + std::string(Operand) + ")"; + case RISCVMCExprKind::AbsoluteLow: + return "%lo(" + std::string(Operand) + ")"; + case RISCVMCExprKind::GOTPCRelativeHigh: + return "%got_pcrel_hi(" + std::string(Operand) + ")"; + } + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V MC expression has an unknown target kind"); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVInstrInfo.cpp b/backend/lib/Target/RISCV/RISCVInstrInfo.cpp new file mode 100644 index 0000000..6e356a6 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfo.cpp @@ -0,0 +1,1441 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCVInstrInfo.h" + +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <algorithm> +#include <array> +#include <charconv> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <optional> +#include <string> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +constexpr std::uint32_t PCRelativeHighFlag = 2; +constexpr std::uint32_t PCRelativeLowFlag = 4; + +[[nodiscard]] bool isAsmSpace(char Value) noexcept { + return Value == ' ' || Value == '\t' || Value == '\r' || Value == '\f' || Value == '\v'; +} + +[[nodiscard]] std::string_view trimAsmSpace(std::string_view Text) noexcept { + while (!Text.empty() && isAsmSpace(Text.front())) + Text.remove_prefix(1); + while (!Text.empty() && isAsmSpace(Text.back())) + Text.remove_suffix(1); + return Text; +} + +[[nodiscard]] Expected<std::uint32_t> inlineAsmSize(const MachineInstr &Instruction) { + const MachineOperand *Operand = Instruction.operandAt(0); + const auto *Symbol = Operand == nullptr ? nullptr : Operand->getIf<ExternalSymbolOperand>(); + if (Symbol == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "RISC-V INLINEASM has no assembly text"); + + std::uint64_t Size = 0; + std::string_view Remaining = Symbol->Name; + while (!Remaining.empty()) { + const std::size_t Separator = Remaining.find_first_of("\n;"); + std::string_view Statement = trimAsmSpace(Remaining.substr(0, Separator)); + Remaining = + Separator == std::string_view::npos ? std::string_view{} : Remaining.substr(Separator + 1U); + if (Statement.empty() || Statement.front() == '#') + continue; + + std::uint64_t StatementSize = 4; + constexpr std::string_view SpaceDirective = ".space"; + if (Statement.starts_with(SpaceDirective) && (Statement.size() == SpaceDirective.size() || + isAsmSpace(Statement[SpaceDirective.size()]))) { + std::string_view Argument = trimAsmSpace(Statement.substr(SpaceDirective.size())); + std::int64_t Count = 0; + const auto Parsed = + std::from_chars(Argument.data(), Argument.data() + Argument.size(), Count); + if (Parsed.ec == std::errc{}) { + std::string_view Trailing( + Parsed.ptr, static_cast<std::size_t>(Argument.data() + Argument.size() - Parsed.ptr)); + Trailing = trimAsmSpace(Trailing); + if (Trailing.empty() || Trailing.front() == '#') + StatementSize = Count < 0 ? 0 : static_cast<std::uint64_t>(Count); + } + } + if (StatementSize > std::numeric_limits<std::uint32_t>::max() - Size) + return makeUnexpected(ErrorCode::VerificationError, "RISC-V INLINEASM size overflowed"); + Size += StatementSize; + } + return static_cast<std::uint32_t>(Size); +} + +[[nodiscard]] bool isConditionalBranch(Opcode Value) noexcept { + return Value == instructionOpcode(InstructionOpcode::BEQ) || + Value == instructionOpcode(InstructionOpcode::BNE) || + Value == instructionOpcode(InstructionOpcode::BLT) || + Value == instructionOpcode(InstructionOpcode::BGE) || + Value == instructionOpcode(InstructionOpcode::BLTU) || + Value == instructionOpcode(InstructionOpcode::BGEU) || + Value == instructionOpcode(InstructionOpcode::C_BEQZ) || + Value == instructionOpcode(InstructionOpcode::C_BNEZ); +} + +[[nodiscard]] std::optional<Opcode> reversedOpcode(Opcode Value) noexcept { + constexpr std::array<std::pair<InstructionOpcode, InstructionOpcode>, 4> Pairs = {{ + {InstructionOpcode::BEQ, InstructionOpcode::BNE}, + {InstructionOpcode::BLT, InstructionOpcode::BGE}, + {InstructionOpcode::BLTU, InstructionOpcode::BGEU}, + {InstructionOpcode::C_BEQZ, InstructionOpcode::C_BNEZ}, + }}; + for (const auto &[Left, Right] : Pairs) { + if (Value == instructionOpcode(Left)) + return instructionOpcode(Right); + if (Value == instructionOpcode(Right)) + return instructionOpcode(Left); + } + return std::nullopt; +} + +[[nodiscard]] bool isZeroRegister(const MachineOperand *Operand) noexcept { + const Register *Reg = Operand == nullptr ? nullptr : Operand->getIf<Register>(); + const std::optional<PhysicalRegister> Physical = + Reg == nullptr ? std::nullopt : Reg->physicalRegister(); + return Physical && Physical->number() == static_cast<std::uint32_t>(PhysicalRegisterID::X0); +} + +[[nodiscard]] bool sameRegister(const MachineOperand *Left, const MachineOperand *Right) noexcept { + const Register *LeftReg = Left == nullptr ? nullptr : Left->getIf<Register>(); + const Register *RightReg = Right == nullptr ? nullptr : Right->getIf<Register>(); + return LeftReg != nullptr && RightReg != nullptr && *LeftReg == *RightReg && + Left->subReg() == Right->subReg(); +} + +[[nodiscard]] bool isPhysicalRegister(const MachineOperand *Operand, + PhysicalRegisterID Expected) noexcept { + const Register *Reg = Operand == nullptr ? nullptr : Operand->getIf<Register>(); + const std::optional<PhysicalRegister> Physical = + Reg == nullptr ? std::nullopt : Reg->physicalRegister(); + return Physical && Physical->number() == static_cast<std::uint32_t>(Expected); +} + +[[nodiscard]] Expected<void> swapRegisters(MachineOperand &Left, MachineOperand &Right) { + const Register *LeftReg = Left.getIf<Register>(); + const Register *RightReg = Right.getIf<Register>(); + if (LeftReg == nullptr || RightReg == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, "register swap requires two registers"); + const Register LeftValue = *LeftReg; + const Register RightValue = *RightReg; + const RegisterOperandFlags LeftFlags = Left.registerFlags(); + const RegisterOperandFlags RightFlags = Right.registerFlags(); + const SubRegIndex LeftSubReg = Left.subReg(); + const SubRegIndex RightSubReg = Right.subReg(); + const std::uint32_t LeftTargetFlags = Left.targetFlags(); + const std::uint32_t RightTargetFlags = Right.targetFlags(); + auto SetLeft = Left.setRegister(RightValue); + if (!SetLeft) + return std::unexpected(SetLeft.error()); + auto SetRight = Right.setRegister(LeftValue); + if (!SetRight) + return std::unexpected(SetRight.error()); + SetLeft = Left.setRegisterFlags(RightFlags); + if (!SetLeft) + return std::unexpected(SetLeft.error()); + SetRight = Right.setRegisterFlags(LeftFlags); + if (!SetRight) + return std::unexpected(SetRight.error()); + SetLeft = Left.setSubReg(RightSubReg); + if (!SetLeft) + return std::unexpected(SetLeft.error()); + SetRight = Right.setSubReg(LeftSubReg); + if (!SetRight) + return std::unexpected(SetRight.error()); + Left.setTargetFlags(RightTargetFlags); + Right.setTargetFlags(LeftTargetFlags); + return {}; +} + +[[nodiscard]] bool isUnconditionalBranch(const MachineInstr &Instruction) noexcept { + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::C_J) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoBR)) { + return true; + } + return Instruction.opcode() == instructionOpcode(InstructionOpcode::JAL) && + isZeroRegister(Instruction.operandAt(0)); +} + +[[nodiscard]] std::size_t branchTargetIndex(Opcode Value) noexcept { + if (Value == instructionOpcode(InstructionOpcode::C_J) || + Value == instructionOpcode(InstructionOpcode::PseudoBR)) { + return 0; + } + if (Value == instructionOpcode(InstructionOpcode::C_BEQZ) || + Value == instructionOpcode(InstructionOpcode::C_BNEZ)) { + return 1; + } + if (Value == instructionOpcode(InstructionOpcode::JAL)) + return 1; + return 2; +} + +[[nodiscard]] Expected<MachineBasicBlock *> directTarget(MachineInstr &Instruction) { + if (!isConditionalBranch(Instruction.opcode()) && + Instruction.opcode() != instructionOpcode(InstructionOpcode::JAL) && + Instruction.opcode() != instructionOpcode(InstructionOpcode::C_J) && + Instruction.opcode() != instructionOpcode(InstructionOpcode::PseudoBR)) { + return static_cast<MachineBasicBlock *>(nullptr); + } + MachineOperand *Operand = Instruction.operandAt(branchTargetIndex(Instruction.opcode())); + const BasicBlockOperand *Block = + Operand == nullptr ? nullptr : Operand->getIf<BasicBlockOperand>(); + if (Block == nullptr || Block->Block == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V direct branch has no basic-block target"); + } + return Block->Block; +} + +[[nodiscard]] Expected<void> setDirectTarget(MachineInstr &Instruction, + MachineBasicBlock &TargetBlock) { + MachineOperand *Operand = Instruction.operandAt(branchTargetIndex(Instruction.opcode())); + if (Operand == nullptr || Operand->kind() != MachineOperand::Kind::BasicBlock) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V direct branch has no mutable basic-block target"); + } + return Operand->setBasicBlock(TargetBlock); +} + +[[nodiscard]] Expected<MachineInstr *> +buildJAL(MachineFunction &Function, const TargetDescription &Target, MachineBasicBlock &Block, + MachineInstr *Before, PhysicalRegister Destination, MachineBasicBlock &TargetBlock) { + MachineIRBuilder Builder(Function, Target); + Expected<void> Point = + Before == nullptr ? Builder.setInsertPoint(Block) : Builder.setInsertPointBefore(*Before); + if (!Point) + return std::unexpected(Point.error()); + auto Pending = Builder.buildInstr(instructionOpcode(InstructionOpcode::JAL)); + if (!Pending) + return std::unexpected(Pending.error()); + auto Def = Pending->addDef(Register::fromPhysical(Destination)); + if (!Def) + return std::unexpected(Def.error()); + auto TargetOperand = Pending->addBasicBlock(TargetBlock); + if (!TargetOperand) + return std::unexpected(TargetOperand.error()); + return Pending->build(); +} + +[[nodiscard]] Expected<void> copySourceIdentity(const MachineInstr &Source, + MachineInstr &Destination) { + if (Source.sourceLocation()) + Destination.setSourceLocation(*Source.sourceLocation()); + if (Source.debugLocation()) { + auto Debug = Destination.setDebugLocation(*Source.debugLocation()); + if (!Debug) + return std::unexpected(Debug.error()); + } + if (Source.origin()) + Destination.setOrigin(*Source.origin()); + return {}; +} + +[[nodiscard]] Expected<void> copyTrailingAnnotations(const MachineInstr &Source, + MachineInstr &Destination) { + if (Source.debugInstrNumber() != 0) { + auto DebugNumber = Destination.setDebugInstrNumber(Source.debugInstrNumber()); + if (!DebugNumber) + return std::unexpected(DebugNumber.error()); + } + if (Source.postInstrSymbol()) { + auto Symbol = Destination.setPostInstrSymbol(std::string(*Source.postInstrSymbol())); + if (!Symbol) + return std::unexpected(Symbol.error()); + } + if (Source.heapAllocMarker()) { + auto Marker = Destination.setHeapAllocMarker(*Source.heapAllocMarker()); + if (!Marker) + return std::unexpected(Marker.error()); + } + if (Source.pcSections()) { + auto Sections = Destination.setPCSections(*Source.pcSections()); + if (!Sections) + return std::unexpected(Sections.error()); + } + if (Source.mmra()) { + auto Metadata = Destination.setMMRA(*Source.mmra()); + if (!Metadata) + return std::unexpected(Metadata.error()); + } + if (Source.cfiType()) { + auto Type = Destination.setCFIType(*Source.cfiType()); + if (!Type) + return std::unexpected(Type.error()); + } + if (Source.deactivationSymbol()) { + auto Symbol = Destination.setDeactivationSymbol(std::string(*Source.deactivationSymbol())); + if (!Symbol) + return std::unexpected(Symbol.error()); + } + return {}; +} + +[[nodiscard]] Expected<void> copySingleInstructionAnnotations(const MachineInstr &Source, + MachineInstr &Destination) { + Destination.setFlags(Source.flags()); + auto Identity = copySourceIdentity(Source, Destination); + if (!Identity) + return std::unexpected(Identity.error()); + if (Source.preInstrSymbol()) { + auto Symbol = Destination.setPreInstrSymbol(std::string(*Source.preInstrSymbol())); + if (!Symbol) + return std::unexpected(Symbol.error()); + } + return copyTrailingAnnotations(Source, Destination); +} + +[[nodiscard]] bool isZeroPhysicalRegister(PhysicalRegister Reg) noexcept { + return Reg.number() == static_cast<std::uint32_t>(PhysicalRegisterID::X0); +} + +[[nodiscard]] Expected<PhysicalRegister> +selectLongBranchScratch(const MachineInstr &Instruction, const MachineBasicBlock &TargetBlock, + const MachineFunction &Function, const TargetDescription &Target) { + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const std::optional<PhysicalRegister> DestinationPhysical = + Destination == nullptr ? std::nullopt : Destination->physicalRegister(); + if (!DestinationPhysical) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V long JAL has no physical destination"); + } + if (!isZeroPhysicalRegister(*DestinationPhysical)) + return *DestinationPhysical; + + const auto GPRClass = + RegisterClassID::fromValue(static_cast<std::uint32_t>(RegisterClass::GPR_NX0)); + const RegisterClassDesc *Candidates = Target.registers().registerClass(GPRClass); + if (Candidates == nullptr) { + return makeUnexpected(ErrorCode::InternalError, "RISC-V target has no nonzero GPR class"); + } + for (const PhysicalRegister Candidate : Candidates->AllocationOrder) { + if (Target.registers().isReservedRegister(Candidate) || + Function.registerInfo().isReserved(Candidate)) { + continue; + } + const bool LiveAtTarget = + std::ranges::any_of(TargetBlock.liveIns(), [&](const MachineBasicBlockLiveIn &LiveIn) { + return Target.registers().regsOverlap(Candidate, LiveIn.PhysReg); + }); + if (!LiveAtTarget) + return Candidate; + } + return makeUnexpected(ErrorCode::BranchRelaxationFailed, + "RISC-V long branch has no dead physical scratch register"); +} + +[[nodiscard]] Expected<bool> expandCompressedBranch(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target) { + MachineBasicBlock *Block = Instruction.parent(); + if (Block == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, "compressed branch is not linked"); + auto TargetBlock = directTarget(Instruction); + if (!TargetBlock) + return std::unexpected(TargetBlock.error()); + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::C_J)) { + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + auto Replacement = buildJAL(Function, Target, *Block, &Instruction, *Zero, **TargetBlock); + if (!Replacement) + return std::unexpected(Replacement.error()); + auto Annotations = copySingleInstructionAnnotations(Instruction, **Replacement); + if (!Annotations) + return std::unexpected(Annotations.error()); + auto Removed = Instruction.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; + } + + const MachineOperand *SourceOperand = Instruction.operandAt(0); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Source == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "compressed conditional branch has no source register"); + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + const InstructionOpcode Expanded = + Instruction.opcode() == instructionOpcode(InstructionOpcode::C_BEQZ) ? InstructionOpcode::BEQ + : InstructionOpcode::BNE; + auto Pending = Builder.buildInstr(instructionOpcode(Expanded)); + if (!Pending) + return std::unexpected(Pending.error()); + auto First = Pending->addUse(*Source); + if (!First) + return std::unexpected(First.error()); + auto Second = Pending->addUse(Register::fromPhysical(*Zero)); + if (!Second) + return std::unexpected(Second.error()); + auto Destination = Pending->addBasicBlock(**TargetBlock); + if (!Destination) + return std::unexpected(Destination.error()); + auto Replacement = Pending->build(); + if (!Replacement) + return std::unexpected(Replacement.error()); + auto Annotations = copySingleInstructionAnnotations(Instruction, **Replacement); + if (!Annotations) + return std::unexpected(Annotations.error()); + auto Removed = Instruction.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; +} + +[[nodiscard]] Expected<bool> relaxConditionalBranch(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target) { + MachineBasicBlock *Block = Instruction.parent(); + if (Block == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, "conditional branch is not linked"); + auto TrueTarget = directTarget(Instruction); + if (!TrueTarget) + return std::unexpected(TrueTarget.error()); + MachineInstr *Following = Instruction.next(); + if (Following != nullptr && isUnconditionalBranch(*Following)) { + auto FalseTarget = directTarget(*Following); + if (!FalseTarget) + return std::unexpected(FalseTarget.error()); + if (*FalseTarget == *TrueTarget) { + auto Removed = Instruction.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; + } + } + MachineBasicBlock *FalseTarget = Block->next(); + if (FalseTarget == nullptr || FalseTarget == *TrueTarget || + std::ranges::none_of(Block->successors(), [FalseTarget](const MachineSuccessor &Successor) { + return Successor.Block == FalseTarget; + })) { + return makeUnexpected(ErrorCode::BranchRelaxationFailed, + "RISC-V conditional relaxation requires its false edge as fallthrough"); + } + + auto Reversed = reversedOpcode(Instruction.opcode()); + if (!Reversed) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V conditional branch cannot be inverted"); + auto SetOpcode = Instruction.setOpcode(*Reversed); + if (!SetOpcode) + return std::unexpected(SetOpcode.error()); + auto SetTarget = setDirectTarget(Instruction, *FalseTarget); + if (!SetTarget) + return std::unexpected(SetTarget.error()); + + if (Following != nullptr && isUnconditionalBranch(*Following)) { + auto Retargeted = setDirectTarget(*Following, **TrueTarget); + if (!Retargeted) + return std::unexpected(Retargeted.error()); + return true; + } + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointAfter(Instruction); + if (!Point) + return std::unexpected(Point.error()); + auto Pending = Builder.buildInstr(instructionOpcode(InstructionOpcode::JAL)); + if (!Pending) + return std::unexpected(Pending.error()); + auto Def = Pending->addDef(Register::fromPhysical(*Zero)); + if (!Def) + return std::unexpected(Def.error()); + auto Destination = Pending->addBasicBlock(**TrueTarget); + if (!Destination) + return std::unexpected(Destination.error()); + auto LongJump = Pending->build(); + if (!LongJump) + return std::unexpected(LongJump.error()); + auto Identity = copySourceIdentity(Instruction, **LongJump); + if (!Identity) + return std::unexpected(Identity.error()); + MachineInstrFlags OriginalFlags = Instruction.flags(); + const bool HadBundledSuccessor = OriginalFlags.has(MachineInstrFlag::BundledWithSucc); + OriginalFlags.set(MachineInstrFlag::BundledWithSucc); + Instruction.setFlags(OriginalFlags); + MachineInstrFlags LongFlags; + LongFlags.set(MachineInstrFlag::BundledWithPred); + if (HadBundledSuccessor) + LongFlags.set(MachineInstrFlag::BundledWithSucc); + if (OriginalFlags.has(MachineInstrFlag::NoMerge)) + LongFlags.set(MachineInstrFlag::NoMerge); + (*LongJump)->setFlags(LongFlags); + return true; +} + +[[nodiscard]] Expected<bool> relaxJAL(MachineInstr &Instruction, std::int64_t Offset, + MachineFunction &Function, const TargetDescription &Target) { + MachineBasicBlock *Block = Instruction.parent(); + if (Block == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, "RISC-V JAL is not linked"); + if (Offset < std::numeric_limits<std::int32_t>::min() || + Offset > std::numeric_limits<std::int32_t>::max()) { + return makeUnexpected(ErrorCode::BranchRelaxationFailed, + "RISC-V PC-relative long branch exceeds the signed 32-bit range"); + } + auto DestinationBlock = directTarget(Instruction); + if (!DestinationBlock) + return std::unexpected(DestinationBlock.error()); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr || !Destination->isPhysical()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V long JAL requires a physical destination register"); + } + auto Scratch = selectLongBranchScratch(Instruction, **DestinationBlock, Function, Target); + if (!Scratch) + return std::unexpected(Scratch.error()); + const std::string Anchor = Instruction.preInstrSymbol() + ? std::string(*Instruction.preInstrSymbol()) + : ".Lrcc_long_" + std::to_string(Function.stableID().value()) + + "_" + std::to_string(Instruction.stableID().value()); + const MachineInstrFlags OriginalFlags = Instruction.flags(); + + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + auto HighPending = Builder.buildInstr(instructionOpcode(InstructionOpcode::AUIPC)); + if (!HighPending) + return std::unexpected(HighPending.error()); + auto HighDef = HighPending->addDef(Register::fromPhysical(*Scratch)); + if (!HighDef) + return std::unexpected(HighDef.error()); + auto HighTarget = HighPending->addBasicBlock(**DestinationBlock, PCRelativeHighFlag); + if (!HighTarget) + return std::unexpected(HighTarget.error()); + auto High = HighPending->build(); + if (!High) + return std::unexpected(High.error()); + auto HighSymbol = (*High)->setPreInstrSymbol(Anchor); + if (!HighSymbol) + return std::unexpected(HighSymbol.error()); + auto HighIdentity = copySourceIdentity(Instruction, **High); + if (!HighIdentity) + return std::unexpected(HighIdentity.error()); + + auto LowPending = Builder.buildInstr(instructionOpcode(InstructionOpcode::JALR)); + if (!LowPending) + return std::unexpected(LowPending.error()); + auto LowDef = LowPending->addDef(*Destination); + if (!LowDef) + return std::unexpected(LowDef.error()); + auto LowBase = LowPending->addUse(Register::fromPhysical(*Scratch)); + if (!LowBase) + return std::unexpected(LowBase.error()); + auto LowSymbol = LowPending->addMCSymbol(Anchor, 0, PCRelativeLowFlag); + if (!LowSymbol) + return std::unexpected(LowSymbol.error()); + auto Low = LowPending->build(); + if (!Low) + return std::unexpected(Low.error()); + auto LowIdentity = copySourceIdentity(Instruction, **Low); + if (!LowIdentity) + return std::unexpected(LowIdentity.error()); + auto LowAnnotations = copyTrailingAnnotations(Instruction, **Low); + if (!LowAnnotations) + return std::unexpected(LowAnnotations.error()); + + MachineInstrFlags HighFlags; + if (OriginalFlags.has(MachineInstrFlag::BundledWithPred)) + HighFlags.set(MachineInstrFlag::BundledWithPred); + HighFlags.set(MachineInstrFlag::BundledWithSucc); + if (OriginalFlags.has(MachineInstrFlag::NoMerge)) + HighFlags.set(MachineInstrFlag::NoMerge); + (*High)->setFlags(HighFlags); + MachineInstrFlags LowFlags = OriginalFlags; + LowFlags.clear(MachineInstrFlag::BundledWithPred); + LowFlags.set(MachineInstrFlag::BundledWithPred); + (*Low)->setFlags(LowFlags); + auto Removed = Instruction.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; +} + +[[nodiscard]] bool loadImmediate(const MachineInstr *Instruction, std::int64_t &Immediate) { + if (Instruction == nullptr || + Instruction->opcode() != instructionOpcode(InstructionOpcode::ADDI) || + !isPhysicalRegister(Instruction->operandAt(1), PhysicalRegisterID::X0)) { + return false; + } + const auto *Value = Instruction->operandAt(2) == nullptr + ? nullptr + : Instruction->operandAt(2)->getIf<std::int64_t>(); + if (Value == nullptr) + return false; + Immediate = *Value; + return true; +} + +[[nodiscard]] bool loadImmediateSource(const MachineRegisterInfo &Registers, + const MachineOperand &Operand, std::int64_t &Immediate) { + const Register *Reg = Operand.getIf<Register>(); + if (Reg == nullptr) + return false; + if (const std::optional<PhysicalRegister> Physical = Reg->physicalRegister()) { + if (Physical->number() != static_cast<std::uint32_t>(PhysicalRegisterID::X0)) + return false; + Immediate = 0; + return true; + } + return Reg->isVirtual() && loadImmediate(Registers.getVRegDef(*Reg), Immediate); +} + +[[nodiscard]] std::optional<BranchConditionCode> branchCondition(Opcode Value) noexcept { + if (Value == instructionOpcode(InstructionOpcode::BEQ)) + return BranchConditionCode::Equal; + if (Value == instructionOpcode(InstructionOpcode::BNE)) + return BranchConditionCode::NotEqual; + if (Value == instructionOpcode(InstructionOpcode::BLT)) + return BranchConditionCode::SignedLessThan; + if (Value == instructionOpcode(InstructionOpcode::BGE)) + return BranchConditionCode::SignedGreaterEqual; + if (Value == instructionOpcode(InstructionOpcode::BLTU)) + return BranchConditionCode::UnsignedLessThan; + if (Value == instructionOpcode(InstructionOpcode::BGEU)) + return BranchConditionCode::UnsignedGreaterEqual; + return std::nullopt; +} + +[[nodiscard]] bool evaluateBranch(BranchConditionCode Condition, std::int64_t Left, + std::int64_t Right) noexcept { + switch (Condition) { + case BranchConditionCode::Equal: + return Left == Right; + case BranchConditionCode::NotEqual: + return Left != Right; + case BranchConditionCode::SignedLessThan: + return Left < Right; + case BranchConditionCode::SignedGreaterEqual: + return Left >= Right; + case BranchConditionCode::UnsignedLessThan: + return static_cast<std::uint64_t>(Left) < static_cast<std::uint64_t>(Right); + case BranchConditionCode::UnsignedGreaterEqual: + return static_cast<std::uint64_t>(Left) >= static_cast<std::uint64_t>(Right); + } + return false; +} + +[[nodiscard]] Expected<void> setRegisterUse(MachineOperand &Operand, Register Reg) { + auto Changed = Operand.setRegister(Reg); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = Operand.setRegisterFlags({RegisterOperandFlag::Use}); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = Operand.setSubReg({}); + if (!Changed) + return std::unexpected(Changed.error()); + Operand.setTargetFlags(0); + return {}; +} + +[[nodiscard]] Expected<void> copyRegisterUse(MachineOperand &Destination, + const MachineOperand &Source) { + const Register *Reg = Source.getIf<Register>(); + if (Reg == nullptr || !Source.isUse()) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V branch optimization requires register uses"); + auto Changed = Destination.setRegister(*Reg); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = Destination.setRegisterFlags(Source.registerFlags()); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = Destination.setSubReg(Source.subReg()); + if (!Changed) + return std::unexpected(Changed.error()); + Destination.setTargetFlags(Source.targetFlags()); + return {}; +} + +[[nodiscard]] Expected<void> clearKillFlags(MachineRegisterInfo &Registers, Register Reg) { + for (MachineOperand &Operand : Registers.useOperands(Reg)) { + RegisterOperandFlags Flags = Operand.registerFlags(); + if (!Flags.has(RegisterOperandFlag::Kill)) + continue; + Flags.clear(RegisterOperandFlag::Kill); + auto Changed = Operand.setRegisterFlags(Flags); + if (!Changed) + return std::unexpected(Changed.error()); + } + return {}; +} + +class RISCVInstrInfo final : public TargetInstrInfo { +public: + [[nodiscard]] Expected<MachineBranchAnalysis> + analyzeBranch(MachineBasicBlock &Block, MachineFunction &Function, + const TargetDescription &Target) const override { + if (&Block.function() != &Function) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch analysis references another function"); + std::vector<MachineInstr *> Terminators; + for (MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); + if (Descriptor == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V branch analysis encountered an unknown opcode"); + if (Descriptor->Flags.has(InstructionFlag::Terminator)) + Terminators.push_back(Instruction); + } + + MachineBranchAnalysis Result; + if (Terminators.empty()) + return Result; + if (Terminators.size() > 2) { + Result.HasUnanalyzableTerminator = true; + return Result; + } + MachineInstr *First = Terminators.front(); + if (isConditionalBranch(First->opcode())) { + auto TrueTarget = directTarget(*First); + if (!TrueTarget) + return std::unexpected(TrueTarget.error()); + Result.Conditional = First; + Result.TrueTarget = *TrueTarget; + if (Terminators.size() == 2) { + MachineInstr *Second = Terminators.back(); + if (!isUnconditionalBranch(*Second)) { + Result.HasUnanalyzableTerminator = true; + return Result; + } + auto FalseTarget = directTarget(*Second); + if (!FalseTarget) + return std::unexpected(FalseTarget.error()); + Result.Unconditional = Second; + Result.FalseTarget = *FalseTarget; + } else { + for (const MachineSuccessor &Successor : Block.successors()) { + if (Successor.Block == nullptr || Successor.Block == Result.TrueTarget) + continue; + if (Result.FalseTarget != nullptr) { + Result.HasUnanalyzableTerminator = true; + return Result; + } + Result.FalseTarget = Successor.Block; + } + if (Result.FalseTarget == nullptr) + Result.FalseTarget = Block.next(); + } + return Result; + } + if (Terminators.size() == 1 && isUnconditionalBranch(*First)) { + auto TargetBlock = directTarget(*First); + if (!TargetBlock) + return std::unexpected(TargetBlock.error()); + Result.Unconditional = First; + Result.TrueTarget = *TargetBlock; + return Result; + } + Result.HasUnanalyzableTerminator = true; + return Result; + } + + [[nodiscard]] Expected<void> reverseBranchCondition(MachineInstr &Branch, + MachineBasicBlock &NewTarget, + MachineFunction &Function, + const TargetDescription &) const override { + if (&Branch.function() != &Function || &NewTarget.function() != &Function) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch reversal references another function"); + const std::optional<Opcode> Reversed = reversedOpcode(Branch.opcode()); + if (!Reversed) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch opcode has no inverse condition"); + auto OpcodeResult = Branch.setOpcode(*Reversed); + if (!OpcodeResult) + return std::unexpected(OpcodeResult.error()); + return setDirectTarget(Branch, NewTarget); + } + + [[nodiscard]] Expected<void> removeBranch(MachineInstr &Branch, MachineFunction &Function, + const TargetDescription &) const override { + if (&Branch.function() != &Function) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch removal references another function"); + return Branch.eraseFromParent(); + } + + [[nodiscard]] Expected<bool> rewriteBranchTarget(MachineBasicBlock &Block, + MachineBasicBlock &OldTarget, + MachineBasicBlock &NewTarget, + MachineFunction &Function, + const TargetDescription &) const override { + if (&Block.function() != &Function || &OldTarget.function() != &Function || + &NewTarget.function() != &Function) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch rewrite references another function"); + } + bool Changed = false; + for (MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (!isConditionalBranch(Instruction->opcode()) && !isUnconditionalBranch(*Instruction)) { + continue; + } + auto CurrentTarget = directTarget(*Instruction); + if (!CurrentTarget) + return std::unexpected(CurrentTarget.error()); + if (*CurrentTarget != &OldTarget) + continue; + auto Rewritten = setDirectTarget(*Instruction, NewTarget); + if (!Rewritten) + return std::unexpected(Rewritten.error()); + Changed = true; + } + return Changed; + } + + [[nodiscard]] Expected<MachineInstr *> + insertBranch(MachineBasicBlock &Block, MachineBasicBlock &TargetBlock, MachineFunction &Function, + const TargetDescription &Target) const override { + if (&Block.function() != &Function || &TargetBlock.function() != &Function) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch insertion references another function"); + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + return buildJAL(Function, Target, Block, nullptr, *Zero, TargetBlock); + } + + [[nodiscard]] Expected<bool> isFallthroughEdge(const MachineBasicBlock &From, + const MachineBasicBlock &To, + const MachineFunction &Function, + const TargetDescription &Target) const override { + if (&From.function() != &Function || &To.function() != &Function) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V fallthrough query references another machine function"); + if (From.next() != &To || + std::ranges::none_of(From.successors(), [&To](const MachineSuccessor &Successor) { + return Successor.Block == &To; + })) { + return false; + } + + bool CanFallThrough = true; + for (const MachineInstr *Instruction = From.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); + if (Descriptor == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V fallthrough analysis encountered an unknown opcode"); + if (Descriptor->Flags.has(InstructionFlag::Terminator)) + CanFallThrough = isConditionalBranch(Instruction->opcode()); + } + return CanFallThrough; + } + + [[nodiscard]] Expected<void> validateCopy(Register Destination, SubRegIndex DestinationSubReg, + Register Source, SubRegIndex SourceSubReg, + const MachineFunction &Function, + const TargetDescription &Target) const override { + if (Destination.isNoRegister() || Source.isNoRegister()) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V COPY requires two nonzero registers"); + if (DestinationSubReg.value() != 0 || SourceSubReg.value() != 0) + return makeUnexpected(ErrorCode::Unsupported, "RV64GC does not model register subindices"); + + auto DestinationShape = copyShape(Destination, Function, Target); + if (!DestinationShape) + return std::unexpected(DestinationShape.error()); + auto SourceShape = copyShape(Source, Function, Target); + if (!SourceShape) + return std::unexpected(SourceShape.error()); + if (DestinationShape->Bank == SourceShape->Bank) { + if (DestinationShape->Width && SourceShape->Width && + DestinationShape->Width != SourceShape->Width) + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V same-bank COPY has incompatible register widths"); + return {}; + } + + const RegisterBankID GPR = + RegisterBankID::fromValue(static_cast<std::uint32_t>(RegisterBank::GPR)); + const RegisterBankID FPR = + RegisterBankID::fromValue(static_cast<std::uint32_t>(RegisterBank::FPR)); + const bool IsCrossBankMove = (DestinationShape->Bank == GPR && SourceShape->Bank == FPR) || + (DestinationShape->Bank == FPR && SourceShape->Bank == GPR); + if (!IsCrossBankMove) + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V COPY crosses unsupported register banks"); + const auto SupportedWidth = [](std::optional<std::uint32_t> Width) { + return !Width || *Width == 32 || *Width == 64; + }; + if (!SupportedWidth(DestinationShape->Width) || !SupportedWidth(SourceShape->Width)) + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V GPR/FPR COPY requires 32-bit or 64-bit values"); + return {}; + } + + [[nodiscard]] std::optional<MachineCopyOperands> + getCopyOperands(MachineInstr &Instruction) const noexcept override { + auto Pair = [&Instruction](std::size_t Destination, + std::size_t Source) -> std::optional<MachineCopyOperands> { + MachineOperand *DestinationOperand = Instruction.operandAt(Destination); + MachineOperand *SourceOperand = Instruction.operandAt(Source); + return DestinationOperand != nullptr && SourceOperand != nullptr + ? std::optional<MachineCopyOperands>{MachineCopyOperands{DestinationOperand, + SourceOperand}} + : std::nullopt; + }; + if (Instruction.opcode() == opcode(GenericOpcode::COPY)) + return Pair(0, 1); + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::ADD) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::OR) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::XOR)) { + if (isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) + return Pair(0, 2); + if (isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0)) + return Pair(0, 1); + } + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::ADDI)) { + const auto *Immediate = Instruction.operandAt(2) == nullptr + ? nullptr + : Instruction.operandAt(2)->getIf<std::int64_t>(); + if (Immediate != nullptr && *Immediate == 0) + return Pair(0, 1); + } + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::SUB) && + isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0)) { + return Pair(0, 1); + } + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::SH1ADD) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::SH1ADD_UW) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::SH2ADD) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::SH2ADD_UW) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::SH3ADD) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::SH3ADD_UW)) { + if (isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) + return Pair(0, 2); + } + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::FSGNJ_D) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::FSGNJ_S)) { + if (sameRegister(Instruction.operandAt(1), Instruction.operandAt(2))) + return Pair(0, 1); + } + return std::nullopt; + } + + [[nodiscard]] Expected<bool> simplifyInstruction(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &) const override { + if (&Instruction.function() != &Function) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V instruction simplification references another function"); + auto SetOpcode = [&Instruction](InstructionOpcode NewOpcode) -> Expected<bool> { + auto Result = Instruction.setOpcode(instructionOpcode(NewOpcode)); + if (!Result) + return std::unexpected(Result.error()); + return true; + }; + auto SetZeroImmediate = [&Instruction, &SetOpcode]( + std::size_t Index, InstructionOpcode NewOpcode) -> Expected<bool> { + MachineOperand *Operand = Instruction.operandAt(Index); + if (Operand == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V simplification has no source operand"); + auto Changed = Operand->changeToImmediate(0); + if (!Changed) + return std::unexpected(Changed.error()); + return SetOpcode(NewOpcode); + }; + const Opcode Value = Instruction.opcode(); + if (Value == instructionOpcode(InstructionOpcode::ADD) || + Value == instructionOpcode(InstructionOpcode::OR) || + Value == instructionOpcode(InstructionOpcode::XOR)) { + if (isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) { + auto Swapped = swapRegisters(*Instruction.operandAt(1), *Instruction.operandAt(2)); + if (!Swapped) + return std::unexpected(Swapped.error()); + } + if (isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0)) + return SetZeroImmediate(2, InstructionOpcode::ADDI); + if (Value == instructionOpcode(InstructionOpcode::XOR) && + sameRegister(Instruction.operandAt(1), Instruction.operandAt(2))) { + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + auto SetSource = Instruction.operandAt(1)->setRegister(Register::fromPhysical(*Zero)); + if (!SetSource) + return std::unexpected(SetSource.error()); + return SetZeroImmediate(2, InstructionOpcode::ADDI); + } + return false; + } + if ((Value == instructionOpcode(InstructionOpcode::ORI) || + Value == instructionOpcode(InstructionOpcode::XORI)) && + isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) { + return SetOpcode(InstructionOpcode::ADDI); + } + if (Value == instructionOpcode(InstructionOpcode::SUB) && + isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0)) { + return SetZeroImmediate(2, InstructionOpcode::ADDI); + } + if (Value == instructionOpcode(InstructionOpcode::SUBW) && + isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0)) { + return SetZeroImmediate(2, InstructionOpcode::ADDIW); + } + if (Value == instructionOpcode(InstructionOpcode::ADDW)) { + if (isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) { + auto Swapped = swapRegisters(*Instruction.operandAt(1), *Instruction.operandAt(2)); + if (!Swapped) + return std::unexpected(Swapped.error()); + } + if (isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0)) + return SetZeroImmediate(2, InstructionOpcode::ADDIW); + return false; + } + if (Value == instructionOpcode(InstructionOpcode::SH1ADD) || + Value == instructionOpcode(InstructionOpcode::SH1ADD_UW) || + Value == instructionOpcode(InstructionOpcode::SH2ADD) || + Value == instructionOpcode(InstructionOpcode::SH2ADD_UW) || + Value == instructionOpcode(InstructionOpcode::SH3ADD) || + Value == instructionOpcode(InstructionOpcode::SH3ADD_UW)) { + if (isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) { + auto Removed = Instruction.eraseOperand(1); + if (!Removed) + return std::unexpected(Removed.error()); + Removed = Instruction.addOperand(MachineOperand::immediate(0)); + if (!Removed) + return std::unexpected(Removed.error()); + return SetOpcode(InstructionOpcode::ADDI); + } + if (isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0)) { + const bool IsUnsignedWord = Value == instructionOpcode(InstructionOpcode::SH1ADD_UW) || + Value == instructionOpcode(InstructionOpcode::SH2ADD_UW) || + Value == instructionOpcode(InstructionOpcode::SH3ADD_UW); + const std::int64_t Shift = Value == instructionOpcode(InstructionOpcode::SH1ADD) || + Value == instructionOpcode(InstructionOpcode::SH1ADD_UW) + ? 1 + : Value == instructionOpcode(InstructionOpcode::SH2ADD) || + Value == instructionOpcode(InstructionOpcode::SH2ADD_UW) + ? 2 + : 3; + auto Removed = Instruction.eraseOperand(2); + if (!Removed) + return std::unexpected(Removed.error()); + Removed = Instruction.addOperand(MachineOperand::immediate(Shift)); + if (!Removed) + return std::unexpected(Removed.error()); + return SetOpcode(IsUnsignedWord ? InstructionOpcode::SLLI_UW : InstructionOpcode::SLLI); + } + return false; + } + if (Value == instructionOpcode(InstructionOpcode::AND) || + Value == instructionOpcode(InstructionOpcode::MUL) || + Value == instructionOpcode(InstructionOpcode::MULH) || + Value == instructionOpcode(InstructionOpcode::MULHSU) || + Value == instructionOpcode(InstructionOpcode::MULHU) || + Value == instructionOpcode(InstructionOpcode::MULW)) { + if (!isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0) && + !isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0)) { + return false; + } + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + auto SetSource = Instruction.operandAt(1)->setRegister(Register::fromPhysical(*Zero)); + if (!SetSource) + return std::unexpected(SetSource.error()); + return SetZeroImmediate(2, InstructionOpcode::ADDI); + } + if (Value == instructionOpcode(InstructionOpcode::ANDI) && + isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) { + MachineOperand *Immediate = Instruction.operandAt(2); + if (Immediate == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V ANDI simplification has no immediate operand"); + auto SetImmediate = Immediate->setImmediate(0); + if (!SetImmediate) + return std::unexpected(SetImmediate.error()); + return SetOpcode(InstructionOpcode::ADDI); + } + if (Value == instructionOpcode(InstructionOpcode::SLL) || + Value == instructionOpcode(InstructionOpcode::SRL) || + Value == instructionOpcode(InstructionOpcode::SRA)) { + if (isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0) || + isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0)) { + return SetZeroImmediate(2, InstructionOpcode::ADDI); + } + return false; + } + if ((Value == instructionOpcode(InstructionOpcode::SLLW) || + Value == instructionOpcode(InstructionOpcode::SRLW) || + Value == instructionOpcode(InstructionOpcode::SRAW)) && + isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) { + return SetZeroImmediate(2, InstructionOpcode::ADDI); + } + if ((Value == instructionOpcode(InstructionOpcode::SLLI) || + Value == instructionOpcode(InstructionOpcode::SRLI) || + Value == instructionOpcode(InstructionOpcode::SRAI) || + Value == instructionOpcode(InstructionOpcode::SLLIW) || + Value == instructionOpcode(InstructionOpcode::SRLIW) || + Value == instructionOpcode(InstructionOpcode::SRAIW) || + Value == instructionOpcode(InstructionOpcode::SLLI_UW)) && + isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) { + MachineOperand *Immediate = Instruction.operandAt(2); + if (Immediate == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V shift simplification has no immediate operand"); + auto SetImmediate = Immediate->setImmediate(0); + if (!SetImmediate) + return std::unexpected(SetImmediate.error()); + return SetOpcode(InstructionOpcode::ADDI); + } + if (Value == instructionOpcode(InstructionOpcode::SLTU) || + Value == instructionOpcode(InstructionOpcode::ADD_UW)) { + const bool LeftZero = isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0); + const bool RightZero = isPhysicalRegister(Instruction.operandAt(2), PhysicalRegisterID::X0); + if (LeftZero && RightZero) + return SetZeroImmediate(2, InstructionOpcode::ADDI); + if (Value == instructionOpcode(InstructionOpcode::ADD_UW) && LeftZero) { + auto Removed = Instruction.eraseOperand(1); + if (!Removed) + return std::unexpected(Removed.error()); + Removed = Instruction.addOperand(MachineOperand::immediate(0)); + if (!Removed) + return std::unexpected(Removed.error()); + return SetOpcode(InstructionOpcode::ADDI); + } + return false; + } + if (Value == instructionOpcode(InstructionOpcode::SLTIU) && + isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) { + MachineOperand *Immediate = Instruction.operandAt(2); + const auto *ImmediateValue = + Immediate == nullptr ? nullptr : Immediate->getIf<std::int64_t>(); + if (ImmediateValue == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V SLTIU simplification has no immediate operand"); + const std::int64_t Result = *ImmediateValue != 0 ? 1 : 0; + auto SetImmediate = Immediate->setImmediate(Result); + if (!SetImmediate) + return std::unexpected(SetImmediate.error()); + return SetOpcode(InstructionOpcode::ADDI); + } + if ((Value == instructionOpcode(InstructionOpcode::SEXT_H) || + Value == instructionOpcode(InstructionOpcode::SEXT_B) || + Value == instructionOpcode(InstructionOpcode::ZEXT_H_RV32) || + Value == instructionOpcode(InstructionOpcode::ZEXT_H_RV64)) && + isPhysicalRegister(Instruction.operandAt(1), PhysicalRegisterID::X0)) { + auto Added = Instruction.addOperand(MachineOperand::immediate(0)); + if (!Added) + return std::unexpected(Added.error()); + return SetOpcode(InstructionOpcode::ADDI); + } + if ((Value == instructionOpcode(InstructionOpcode::MIN) || + Value == instructionOpcode(InstructionOpcode::MINU) || + Value == instructionOpcode(InstructionOpcode::MAX) || + Value == instructionOpcode(InstructionOpcode::MAXU)) && + sameRegister(Instruction.operandAt(1), Instruction.operandAt(2))) { + return SetZeroImmediate(2, InstructionOpcode::ADDI); + } + if ((Value == instructionOpcode(InstructionOpcode::BEQ) || + Value == instructionOpcode(InstructionOpcode::BNE)) && + isPhysicalRegister(Instruction.operandAt(0), PhysicalRegisterID::X0)) { + auto Swapped = swapRegisters(*Instruction.operandAt(0), *Instruction.operandAt(1)); + if (!Swapped) + return std::unexpected(Swapped.error()); + return true; + } + if ((Value == instructionOpcode(InstructionOpcode::BLTU) || + Value == instructionOpcode(InstructionOpcode::BGEU)) && + isPhysicalRegister(Instruction.operandAt(0), PhysicalRegisterID::X0)) { + auto Swapped = swapRegisters(*Instruction.operandAt(0), *Instruction.operandAt(1)); + if (!Swapped) + return std::unexpected(Swapped.error()); + return SetOpcode(Value == instructionOpcode(InstructionOpcode::BLTU) + ? InstructionOpcode::BNE + : InstructionOpcode::BEQ); + } + return false; + } + + [[nodiscard]] Expected<bool> optimizeCondBranch(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &) const override { + if (&Instruction.function() != &Function) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch optimization references another function"); + const auto Condition = branchCondition(Instruction.opcode()); + if (!Condition) + return false; + MachineOperand *Left = Instruction.operandAt(0); + MachineOperand *Right = Instruction.operandAt(1); + if (Left == nullptr || Right == nullptr || Instruction.operandAt(2) == nullptr || + Instruction.operandAt(2)->getIf<BasicBlockOperand>() == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V conditional branch has malformed operands"); + } + + MachineRegisterInfo &Registers = Function.registerInfo(); + std::int64_t LeftConstant = 0; + std::int64_t RightConstant = 0; + if (loadImmediateSource(Registers, *Left, LeftConstant) && + loadImmediateSource(Registers, *Right, RightConstant)) { + const InstructionOpcode NewOpcode = evaluateBranch(*Condition, LeftConstant, RightConstant) + ? InstructionOpcode::BEQ + : InstructionOpcode::BNE; + auto Zero = physicalRegister(PhysicalRegisterID::X0); + if (!Zero) + return std::unexpected(Zero.error()); + auto Changed = Instruction.setOpcode(instructionOpcode(NewOpcode)); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = setRegisterUse(*Left, Register::fromPhysical(*Zero)); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = setRegisterUse(*Right, Register::fromPhysical(*Zero)); + if (!Changed) + return std::unexpected(Changed.error()); + return true; + } + + const bool IsEquality = + *Condition == BranchConditionCode::Equal || *Condition == BranchConditionCode::NotEqual; + if (IsEquality) + return false; + const bool IsSigned = *Condition == BranchConditionCode::SignedLessThan || + *Condition == BranchConditionCode::SignedGreaterEqual; + const auto SearchConstant = [&Instruction](std::int64_t Value) -> Register { + for (MachineInstr *Candidate = Instruction.previous(); Candidate != nullptr; + Candidate = Candidate->previous()) { + std::int64_t CandidateValue = 0; + if (!loadImmediate(Candidate, CandidateValue) || CandidateValue != Value) + continue; + const MachineOperand *Definition = Candidate->operandAt(0); + const Register *Reg = Definition == nullptr ? nullptr : Definition->getIf<Register>(); + if (Reg != nullptr && Reg->isVirtual()) + return *Reg; + } + return {}; + }; + const std::optional<Opcode> NewOpcode = reversedOpcode(Instruction.opcode()); + if (!NewOpcode) + return false; + + const Register *LeftReg = Left->getIf<Register>(); + if (loadImmediateSource(Registers, *Left, LeftConstant) && LeftConstant != 0 && + LeftReg != nullptr && LeftReg->isVirtual() && Registers.hasOneUse(*LeftReg, false) && + (IsSigned || LeftConstant != -1)) { + const Register Replacement = SearchConstant(LeftConstant + 1); + if (!Replacement.isNoRegister()) { + MachineOperand SavedRight = Right->cloneDetached(); + auto Changed = Instruction.setOpcode(*NewOpcode); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = copyRegisterUse(*Left, SavedRight); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = setRegisterUse(*Right, Replacement); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = clearKillFlags(Registers, Replacement); + if (!Changed) + return std::unexpected(Changed.error()); + return true; + } + } + + const Register *RightReg = Right->getIf<Register>(); + if (loadImmediateSource(Registers, *Right, RightConstant) && RightConstant != 0 && + RightReg != nullptr && RightReg->isVirtual() && Registers.hasOneUse(*RightReg, false)) { + const Register Replacement = SearchConstant(RightConstant - 1); + if (!Replacement.isNoRegister()) { + MachineOperand SavedLeft = Left->cloneDetached(); + auto Changed = Instruction.setOpcode(*NewOpcode); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = setRegisterUse(*Left, Replacement); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = copyRegisterUse(*Right, SavedLeft); + if (!Changed) + return std::unexpected(Changed.error()); + Changed = clearKillFlags(Registers, Replacement); + if (!Changed) + return std::unexpected(Changed.error()); + return true; + } + } + return false; + } + + [[nodiscard]] Expected<std::uint32_t> + instructionSize(const MachineInstr &Instruction, const MachineFunction &, + const TargetDescription &Target) const override { + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction.opcode()); + if (Descriptor == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V instruction-size query encountered an unknown opcode"); + if (!Descriptor->Flags.has(InstructionFlag::ProducesCode)) + return 0; + if (Instruction.opcode() == opcode(GenericOpcode::INLINEASM) || + Instruction.opcode() == opcode(GenericOpcode::INLINEASM_BR)) { + return inlineAsmSize(Instruction); + } + if (Descriptor->SizeInBytes != 0) + return Descriptor->SizeInBytes; + const auto Category = + std::ranges::find_if(encodingCategories(), [Descriptor](const EncodingCategoryInfo &Info) { + return Info.ID == Descriptor->EncodingCategory; + }); + if (Category == encodingCategories().end() || Category->SizeInBytes == 0) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V producing instruction has no exact encoding size"); + return Category->SizeInBytes; + } + + [[nodiscard]] Expected<MachineBasicBlock *> + branchTarget(MachineInstr &Instruction, MachineFunction &Function, + const TargetDescription &) const override { + if (&Instruction.function() != &Function) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch target query references another function"); + return directTarget(Instruction); + } + + [[nodiscard]] Expected<std::optional<bool>> + evaluateBranchCondition(const MachineInstr &Instruction, const MachineFunction &Function, + const TargetDescription &) const override { + if (&Instruction.function() != &Function) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch evaluation references another function"); + } + const Opcode Value = Instruction.opcode(); + if (!isConditionalBranch(Value)) + return std::optional<bool>{}; + if (Value == instructionOpcode(InstructionOpcode::C_BEQZ) || + Value == instructionOpcode(InstructionOpcode::C_BNEZ)) { + if (!isZeroRegister(Instruction.operandAt(0))) + return std::optional<bool>{}; + return std::optional<bool>{Value == instructionOpcode(InstructionOpcode::C_BEQZ)}; + } + if (!sameRegister(Instruction.operandAt(0), Instruction.operandAt(1))) + return std::optional<bool>{}; + if (Value == instructionOpcode(InstructionOpcode::BEQ) || + Value == instructionOpcode(InstructionOpcode::BGE) || + Value == instructionOpcode(InstructionOpcode::BGEU)) { + return std::optional<bool>{true}; + } + return std::optional<bool>{false}; + } + + [[nodiscard]] bool isBranchOffsetInRange(const MachineInstr &Instruction, + std::int64_t Offset) const noexcept override { + if ((Offset & 1) != 0) + return false; + const Opcode Value = Instruction.opcode(); + if (Value == instructionOpcode(InstructionOpcode::C_BEQZ) || + Value == instructionOpcode(InstructionOpcode::C_BNEZ)) { + return Offset >= -256 && Offset <= 254; + } + if (Value == instructionOpcode(InstructionOpcode::C_J)) + return Offset >= -2048 && Offset <= 2046; + if (isConditionalBranch(Value)) + return Offset >= -4096 && Offset <= 4094; + if (Value == instructionOpcode(InstructionOpcode::JAL) || + Value == instructionOpcode(InstructionOpcode::PseudoBR)) { + return Offset >= -1048576 && Offset <= 1048574; + } + return true; + } + + [[nodiscard]] Expected<bool> relaxBranch(MachineInstr &Instruction, std::int64_t Offset, + MachineFunction &Function, + const TargetDescription &Target) const override { + if (&Instruction.function() != &Function) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V branch relaxation references another function"); + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::C_J) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::C_BEQZ) || + Instruction.opcode() == instructionOpcode(InstructionOpcode::C_BNEZ)) { + return expandCompressedBranch(Instruction, Function, Target); + } + if (isConditionalBranch(Instruction.opcode())) + return relaxConditionalBranch(Instruction, Function, Target); + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::JAL)) + return relaxJAL(Instruction, Offset, Function, Target); + return false; + } + +private: + struct CopyShape final { + RegisterBankID Bank; + std::optional<std::uint32_t> Width; + }; + + [[nodiscard]] static Expected<CopyShape> copyShape(Register Reg, const MachineFunction &Function, + const TargetDescription &Target) { + if (Reg.isVirtual()) { + auto Info = Function.registerInfo().virtualRegisterInfo(Reg); + if (!Info) + return std::unexpected(Info.error()); + const std::optional<RegisterBankID> &Bank = Info->get().Bank; + const std::optional<RegisterClassID> &RegClass = Info->get().RegClass; + if (!Bank || !RegClass) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V COPY virtual register is not fully constrained"); + const RegisterClassDesc *Class = Target.registers().registerClass(*RegClass); + if (Class == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V COPY references an unknown register class"); + return CopyShape{*Bank, Class->SizeInBits}; + } + + const std::optional<PhysicalRegister> Physical = Reg.physicalRegister(); + if (!Physical || !Target.registers().isKnownPhysicalRegister(*Physical)) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V COPY references an unknown physical register"); + for (const RegisterBankDesc &Bank : Target.registers().registerBanks()) { + if (Target.registers().bankContainsRegister(Bank.ID, *Physical)) + return CopyShape{Bank.ID, std::nullopt}; + } + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V physical register is not assigned to a register bank"); + } +}; + +} // namespace + +const TargetInstrInfo &instrInfo() noexcept { + static const RISCVInstrInfo Value; + return Value; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVInstrInfo.h b/backend/lib/Target/RISCV/RISCVInstrInfo.h new file mode 100644 index 0000000..171a9f3 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfo.h @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_LIB_TARGET_RISCV_RISCVINSTRINFO_H +#define RCC_LIB_TARGET_RISCV_RISCVINSTRINFO_H + +#include "rcc/CodeGen/TargetInstrInfo.h" + +#include <cstdint> + +namespace rcc::riscv { + +enum class BranchConditionCode : std::uint32_t { + Equal = 0, + NotEqual = 1, + SignedLessThan = 2, + SignedGreaterEqual = 3, + UnsignedLessThan = 4, + UnsignedGreaterEqual = 5, +}; + +[[nodiscard]] const TargetInstrInfo &instrInfo() noexcept; + +} // namespace rcc::riscv + +#endif // RCC_LIB_TARGET_RISCV_RISCVINSTRINFO_H diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoA.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoA.rccdesc new file mode 100644 index 0000000..c954f8e --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoA.rccdesc @@ -0,0 +1,329 @@ +instructions: + - + id: 1096 + symbol: LR_W + name: LR_W + assembly_mnemonic: lr.w + encoding: 268739631 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_PAREN_RS1 + - + id: 1097 + symbol: SC_W + name: SC_W + assembly_mnemonic: sc.w + encoding: 413443119 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1098 + symbol: AMOSWAP_W + name: AMOSWAP_W + assembly_mnemonic: amoswap.w + encoding: 145007663 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1099 + symbol: AMOADD_W + name: AMOADD_W + assembly_mnemonic: amoadd.w + encoding: 10789935 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1100 + symbol: AMOXOR_W + name: AMOXOR_W + assembly_mnemonic: amoxor.w + encoding: 547660847 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1101 + symbol: AMOAND_W + name: AMOAND_W + assembly_mnemonic: amoand.w + encoding: 1621402671 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1102 + symbol: AMOOR_W + name: AMOOR_W + assembly_mnemonic: amoor.w + encoding: 1084531759 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1103 + symbol: AMOMIN_W + name: AMOMIN_W + assembly_mnemonic: amomin.w + encoding: 2158273583 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1104 + symbol: AMOMAX_W + name: AMOMAX_W + assembly_mnemonic: amomax.w + encoding: 2695144495 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1105 + symbol: AMOMINU_W + name: AMOMINU_W + assembly_mnemonic: amominu.w + encoding: 3232015407 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1106 + symbol: AMOMAXU_W + name: AMOMAXU_W + assembly_mnemonic: amomaxu.w + encoding: 3768886319 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1107 + symbol: LR_D + name: LR_D + assembly_mnemonic: lr.d + encoding: 268743727 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_PAREN_RS1 + - + id: 1108 + symbol: SC_D + name: SC_D + assembly_mnemonic: sc.d + encoding: 413447215 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1109 + symbol: AMOSWAP_D + name: AMOSWAP_D + assembly_mnemonic: amoswap.d + encoding: 145011759 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1110 + symbol: AMOADD_D + name: AMOADD_D + assembly_mnemonic: amoadd.d + encoding: 10794031 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1111 + symbol: AMOXOR_D + name: AMOXOR_D + assembly_mnemonic: amoxor.d + encoding: 547664943 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1112 + symbol: AMOAND_D + name: AMOAND_D + assembly_mnemonic: amoand.d + encoding: 1621406767 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1113 + symbol: AMOOR_D + name: AMOOR_D + assembly_mnemonic: amoor.d + encoding: 1084535855 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1114 + symbol: AMOMIN_D + name: AMOMIN_D + assembly_mnemonic: amomin.d + encoding: 2158277679 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1115 + symbol: AMOMAX_D + name: AMOMAX_D + assembly_mnemonic: amomax.d + encoding: 2695148591 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1116 + symbol: AMOMINU_D + name: AMOMINU_D + assembly_mnemonic: amominu.d + encoding: 3232019503 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 + - + id: 1117 + symbol: AMOMAXU_D + name: AMOMAXU_D + assembly_mnemonic: amomaxu.d + encoding: 3768890415 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + num_explicit_defs: 1 + flags: [may_load, may_store, produces_code] + scheduling_class: ATOMIC + encoding_category: AMO + assembly_format: RD_RS2_PAREN_RS1 diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoBase.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoBase.rccdesc new file mode 100644 index 0000000..c4c6bf4 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoBase.rccdesc @@ -0,0 +1,107 @@ +named_operands: + - { id: 1, symbol: RD, name: rd } + - { id: 2, symbol: RS1, name: rs1 } + - { id: 3, symbol: RS2, name: rs2 } + - { id: 4, symbol: IMM, name: imm } + - { id: 5, symbol: TARGET, name: target } + - { id: 6, symbol: CSR, name: csr } + - { id: 7, symbol: ZIMM, name: zimm } + - { id: 8, symbol: PRED, name: pred } + - { id: 9, symbol: SUCC, name: succ } + - { id: 10, symbol: FRD, name: frd } + - { id: 11, symbol: FRS1, name: frs1 } + - { id: 12, symbol: FRS2, name: frs2 } + - { id: 13, symbol: FRS3, name: frs3 } + - { id: 14, symbol: RM, name: rm } + - { id: 15, symbol: RLIST, name: rlist } + - { id: 16, symbol: STACKADJ, name: stackadj } + +immediate_predicates: + - { id: 1, symbol: SIMM12, name: simm12, kind: signed, bits: 12 } + - { id: 2, symbol: UIMM20, name: uimm20, kind: unsigned, bits: 20 } + - { id: 3, symbol: UIMM5, name: uimm5, kind: unsigned, bits: 5 } + - { id: 4, symbol: UIMM6, name: uimm6, kind: unsigned, bits: 6 } + - { id: 5, symbol: UIMM12, name: uimm12, kind: unsigned, bits: 12 } + - { id: 6, symbol: UIMM4, name: uimm4, kind: unsigned, bits: 4 } + - { id: 7, symbol: RM, name: rm, kind: unsigned, bits: 3, allowed_values: [0, 1, 2, 3, 4, 7] } + - { id: 8, symbol: C_UIMM10_NZ_A4, name: c-uimm10-nz-a4, kind: unsigned, bits: 10, alignment: 4, nonzero: true } + - { id: 9, symbol: C_UIMM7_A4, name: c-uimm7-a4, kind: unsigned, bits: 7, alignment: 4 } + - { id: 10, symbol: C_UIMM8_A8, name: c-uimm8-a8, kind: unsigned, bits: 8, alignment: 8 } + - { id: 11, symbol: C_UIMM8_A4, name: c-uimm8-a4, kind: unsigned, bits: 8, alignment: 4 } + - { id: 12, symbol: C_UIMM9_A8, name: c-uimm9-a8, kind: unsigned, bits: 9, alignment: 8 } + - { id: 13, symbol: C_SIMM6, name: c-simm6, kind: signed, bits: 6 } + - { id: 14, symbol: C_SIMM6_NZ, name: c-simm6-nz, kind: signed, bits: 6, nonzero: true } + - { id: 15, symbol: C_SIMM9_A2, name: c-simm9-a2, kind: signed, bits: 9, alignment: 2 } + - { id: 16, symbol: C_SIMM12_A2, name: c-simm12-a2, kind: signed, bits: 12, alignment: 2 } + - { id: 17, symbol: C_SIMM10_A16_NZ, name: c-simm10-a16-nz, kind: signed, bits: 10, alignment: 16, nonzero: true } + - { id: 18, symbol: ZCMP_RLIST, name: zcmp-rlist, kind: unsigned, bits: 4, allowed_values: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] } + - { id: 19, symbol: ZCMP_STACKADJ, name: zcmp-stackadj, kind: unsigned, bits: 6, alignment: 16 } + +scheduling_classes: + - { id: 1, symbol: ALU, name: alu, micro_ops: 1, latency: 1 } + - { id: 2, symbol: LOAD, name: load, micro_ops: 1, latency: 3 } + - { id: 3, symbol: STORE, name: store, micro_ops: 1, latency: 1 } + - { id: 4, symbol: BRANCH, name: branch, micro_ops: 1, latency: 1 } + - { id: 5, symbol: SYSTEM, name: system, micro_ops: 1, latency: 1 } + - { id: 6, symbol: MUL, name: mul, micro_ops: 1, latency: 3 } + - { id: 7, symbol: DIV, name: div, micro_ops: 1, latency: 20 } + - { id: 8, symbol: FENCE, name: fence, micro_ops: 1, latency: 1 } + - { id: 9, symbol: ATOMIC, name: atomic, micro_ops: 1, latency: 4 } + - { id: 10, symbol: FP_ADD, name: fp-add, micro_ops: 1, latency: 4 } + - { id: 11, symbol: FP_MUL, name: fp-mul, micro_ops: 1, latency: 4 } + - { id: 12, symbol: FP_DIV, name: fp-div, micro_ops: 1, latency: 20 } + - { id: 13, symbol: FP_SQRT, name: fp-sqrt, micro_ops: 1, latency: 20 } + - { id: 14, symbol: FP_CONVERT, name: fp-convert, micro_ops: 1, latency: 4 } + - { id: 15, symbol: COMPRESSED, name: compressed, micro_ops: 1, latency: 1 } + +encoding_categories: + - { id: 1, symbol: R, name: r, size_bytes: 4 } + - { id: 2, symbol: I, name: i, size_bytes: 4 } + - { id: 3, symbol: S, name: s, size_bytes: 4 } + - { id: 4, symbol: B, name: b, size_bytes: 4 } + - { id: 5, symbol: U, name: u, size_bytes: 4 } + - { id: 6, symbol: J, name: j, size_bytes: 4 } + - { id: 7, symbol: SYSTEM, name: system, size_bytes: 4 } + - { id: 8, symbol: AMO, name: amo, size_bytes: 4 } + - { id: 9, symbol: R4, name: r4, size_bytes: 4 } + - { id: 10, symbol: C, name: c, size_bytes: 2 } + - { id: 11, symbol: ZCMP_MOVE, name: zcmp-move, size_bytes: 2 } + - { id: 12, symbol: ZCMP_STACK, name: zcmp-stack, size_bytes: 2 } + +assembly_formats: + - { id: 1, symbol: RD_IMM, name: rd-imm } + - { id: 2, symbol: RD_RS1_IMM, name: rd-rs1-imm } + - { id: 3, symbol: RD_RS1_RS2, name: rd-rs1-rs2 } + - { id: 4, symbol: RD_OFFSET_RS1, name: rd-offset-rs1 } + - { id: 5, symbol: RS2_OFFSET_RS1, name: rs2-offset-rs1 } + - { id: 6, symbol: RS1_RS2_TARGET, name: rs1-rs2-target } + - { id: 7, symbol: RD_TARGET, name: rd-target } + - { id: 8, symbol: NONE, name: none } + - { id: 9, symbol: IMM_IMM, name: imm-imm } + - { id: 10, symbol: RD_CSR_RS1, name: rd-csr-rs1 } + - { id: 11, symbol: RD_CSR_IMM, name: rd-csr-imm } + - { id: 12, symbol: RD_PAREN_RS1, name: rd-paren-rs1 } + - { id: 13, symbol: RD_RS2_PAREN_RS1, name: rd-rs2-paren-rs1 } + - { id: 14, symbol: FRD_OFFSET_RS1, name: frd-offset-rs1 } + - { id: 15, symbol: FRS2_OFFSET_RS1, name: frs2-offset-rs1 } + - { id: 16, symbol: FRD_FRS1_FRS2_RM, name: frd-frs1-frs2-rm } + - { id: 17, symbol: FRD_FRS1_FRS2_FRS3_RM, name: frd-frs1-frs2-frs3-rm } + - { id: 18, symbol: FRD_FRS1_RM, name: frd-frs1-rm } + - { id: 19, symbol: FRD_FRS1_FRS2, name: frd-frs1-frs2 } + - { id: 20, symbol: RD_FRS1_RM, name: rd-frs1-rm } + - { id: 21, symbol: RD_FRS1, name: rd-frs1 } + - { id: 22, symbol: RD_FRS1_FRS2, name: rd-frs1-frs2 } + - { id: 23, symbol: FRD_RS1_RM, name: frd-rs1-rm } + - { id: 24, symbol: FRD_RS1, name: frd-rs1 } + - { id: 25, symbol: TARGET, name: target } + - { id: 26, symbol: RS1_TARGET, name: rs1-target } + - { id: 27, symbol: RS1, name: rs1 } + - { id: 28, symbol: RD_RS2, name: rd-rs2 } + - { id: 29, symbol: IMM_SP, name: imm-sp } + - { id: 30, symbol: RD_IMM_SP, name: rd-imm-sp } + - { id: 31, symbol: RD_OFFSET_SP, name: rd-offset-sp } + - { id: 32, symbol: RS2_OFFSET_SP, name: rs2-offset-sp } + - { id: 33, symbol: FRD_OFFSET_SP, name: frd-offset-sp } + - { id: 34, symbol: FRS2_OFFSET_SP, name: frs2-offset-sp } + - { id: 35, symbol: RD_RS1, name: rd-rs1 } + - { id: 36, symbol: RLIST_STACKADJ, name: rlist-stackadj } diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoC.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoC.rccdesc new file mode 100644 index 0000000..fa1b75a --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoC.rccdesc @@ -0,0 +1,515 @@ +instructions: + - + id: 1180 + symbol: C_ADDI4SPN + name: C_ADDI4SPN + assembly_mnemonic: c.addi4spn + encoding: 2048 + operands: + - { kinds: [register], role: def, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: C_UIMM10_NZ_A4, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + implicit_uses: [{ register: X2 }] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_IMM_SP + - + id: 1181 + symbol: C_FLD + name: C_FLD + assembly_mnemonic: c.fld + encoding: 9344 + operands: + - { kinds: [register], role: def, register_class: FPR64C, register_bank: FPR, memory_role: value, name: FRD } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate], immediate_predicate: C_UIMM8_A8, name: IMM } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: C + assembly_format: FRD_OFFSET_RS1 + - + id: 1182 + symbol: C_LW + name: C_LW + assembly_mnemonic: c.lw + encoding: 17536 + operands: + - { kinds: [register], role: def, register_class: GPRC, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate], immediate_predicate: C_UIMM7_A4, name: IMM } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: C + assembly_format: RD_OFFSET_RS1 + - + id: 1183 + symbol: C_LD + name: C_LD + assembly_mnemonic: c.ld + encoding: 25728 + operands: + - { kinds: [register], role: def, register_class: GPRC, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate], immediate_predicate: C_UIMM8_A8, name: IMM } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: C + assembly_format: RD_OFFSET_RS1 + - + id: 1184 + symbol: C_FSD + name: C_FSD + assembly_mnemonic: c.fsd + encoding: 42120 + operands: + - { kinds: [register], role: use, register_class: FPR64C, register_bank: FPR, memory_role: value, name: FRS2 } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate], immediate_predicate: C_UIMM8_A8, name: IMM } + num_explicit_defs: 0 + flags: [may_store, produces_code] + scheduling_class: STORE + encoding_category: C + assembly_format: FRS2_OFFSET_RS1 + - + id: 1185 + symbol: C_SW + name: C_SW + assembly_mnemonic: c.sw + encoding: 50312 + operands: + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate], immediate_predicate: C_UIMM7_A4, name: IMM } + num_explicit_defs: 0 + flags: [may_store, produces_code] + scheduling_class: STORE + encoding_category: C + assembly_format: RS2_OFFSET_RS1 + - + id: 1186 + symbol: C_SD + name: C_SD + assembly_mnemonic: c.sd + encoding: 58504 + operands: + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate], immediate_predicate: C_UIMM8_A8, name: IMM } + num_explicit_defs: 0 + flags: [may_store, produces_code] + scheduling_class: STORE + encoding_category: C + assembly_format: RS2_OFFSET_RS1 + - + id: 1187 + symbol: C_ADDI + name: C_ADDI + assembly_mnemonic: c.addi + encoding: 1029 + operands: + - { kinds: [register], role: def_use, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: C_SIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_IMM + - + id: 1188 + symbol: C_ADDIW + name: C_ADDIW + assembly_mnemonic: c.addiw + encoding: 9221 + operands: + - { kinds: [register], role: def_use, register_class: GPR_NX0, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: C_SIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_IMM + - + id: 1189 + symbol: C_LI + name: C_LI + assembly_mnemonic: c.li + encoding: 17413 + operands: + - { kinds: [register], role: def, register_class: GPR_NX0, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: C_SIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_IMM + - + id: 1190 + symbol: C_ADDI16SP + name: C_ADDI16SP + assembly_mnemonic: c.addi16sp + encoding: 24897 + operands: + - { kinds: [immediate], immediate_predicate: C_SIMM10_A16_NZ, name: IMM } + num_explicit_defs: 0 + flags: [produces_code] + implicit_defs: [{ register: X2 }] + implicit_uses: [{ register: X2 }] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: IMM_SP + - + id: 1191 + symbol: C_LUI + name: C_LUI + assembly_mnemonic: c.lui + encoding: 25605 + operands: + - { kinds: [register], role: def, register_class: GPR_NX0_NX2, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: C_SIMM6_NZ, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_IMM + - + id: 1192 + symbol: C_SRLI + name: C_SRLI + assembly_mnemonic: c.srli + encoding: 32773 + operands: + - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_IMM + - + id: 1193 + symbol: C_SRAI + name: C_SRAI + assembly_mnemonic: c.srai + encoding: 33797 + operands: + - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_IMM + - + id: 1194 + symbol: C_ANDI + name: C_ANDI + assembly_mnemonic: c.andi + encoding: 34821 + operands: + - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: C_SIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_IMM + - + id: 1195 + symbol: C_SUB + name: C_SUB + assembly_mnemonic: c.sub + encoding: 35845 + operands: + - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_RS2 + - + id: 1196 + symbol: C_XOR + name: C_XOR + assembly_mnemonic: c.xor + encoding: 35877 + operands: + - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_RS2 + - + id: 1197 + symbol: C_OR + name: C_OR + assembly_mnemonic: c.or + encoding: 35909 + operands: + - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_RS2 + - + id: 1198 + symbol: C_AND + name: C_AND + assembly_mnemonic: c.and + encoding: 35941 + operands: + - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_RS2 + - + id: 1199 + symbol: C_SUBW + name: C_SUBW + assembly_mnemonic: c.subw + encoding: 39941 + operands: + - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_RS2 + - + id: 1200 + symbol: C_ADDW + name: C_ADDW + assembly_mnemonic: c.addw + encoding: 39973 + operands: + - { kinds: [register], role: def_use, register_class: GPRC, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_RS2 + - + id: 1201 + symbol: C_J + name: C_J + assembly_mnemonic: c.j + encoding: 40961 + operands: + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [terminator, branch, produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: TARGET + - + id: 1202 + symbol: C_BEQZ + name: C_BEQZ + assembly_mnemonic: c.beqz + encoding: 49153 + operands: + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS1 } + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [terminator, branch, produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RS1_TARGET + - + id: 1203 + symbol: C_BNEZ + name: C_BNEZ + assembly_mnemonic: c.bnez + encoding: 57345 + operands: + - { kinds: [register], role: use, register_class: GPRC, register_bank: GPR, name: RS1 } + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [terminator, branch, produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RS1_TARGET + - + id: 1204 + symbol: C_SLLI + name: C_SLLI + assembly_mnemonic: c.slli + encoding: 1030 + operands: + - { kinds: [register], role: def_use, register_class: GPR_NX0, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_IMM + - + id: 1205 + symbol: C_FLDSP + name: C_FLDSP + assembly_mnemonic: c.fldsp + encoding: 9250 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, memory_role: value, name: FRD } + - { kinds: [immediate], immediate_predicate: C_UIMM9_A8, name: IMM } + num_explicit_defs: 1 + flags: [may_load, produces_code] + implicit_uses: [{ register: X2 }] + scheduling_class: LOAD + encoding_category: C + assembly_format: FRD_OFFSET_SP + - + id: 1206 + symbol: C_LWSP + name: C_LWSP + assembly_mnemonic: c.lwsp + encoding: 17442 + operands: + - { kinds: [register], role: def, register_class: GPR_NX0, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [immediate], immediate_predicate: C_UIMM8_A4, name: IMM } + num_explicit_defs: 1 + flags: [may_load, produces_code] + implicit_uses: [{ register: X2 }] + scheduling_class: LOAD + encoding_category: C + assembly_format: RD_OFFSET_SP + - + id: 1207 + symbol: C_LDSP + name: C_LDSP + assembly_mnemonic: c.ldsp + encoding: 25634 + operands: + - { kinds: [register], role: def, register_class: GPR_NX0, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [immediate], immediate_predicate: C_UIMM9_A8, name: IMM } + num_explicit_defs: 1 + flags: [may_load, produces_code] + implicit_uses: [{ register: X2 }] + scheduling_class: LOAD + encoding_category: C + assembly_format: RD_OFFSET_SP + - + id: 1208 + symbol: C_JR + name: C_JR + assembly_mnemonic: c.jr + encoding: 33794 + operands: + - { kinds: [register], role: use, register_class: GPR_NX0, register_bank: GPR, name: RS1 } + num_explicit_defs: 0 + flags: [terminator, branch, indirect_branch, produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RS1 + - + id: 1209 + symbol: C_MV + name: C_MV + assembly_mnemonic: c.mv + encoding: 33830 + operands: + - { kinds: [register], role: def, register_class: GPR_NX0, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR_NX0, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_RS2 + - + id: 1210 + symbol: C_EBREAK + name: C_EBREAK + assembly_mnemonic: c.ebreak + encoding: 36866 + operands: [] + num_explicit_defs: 0 + flags: [has_side_effects, produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: NONE + - + id: 1211 + symbol: C_JALR + name: C_JALR + assembly_mnemonic: c.jalr + encoding: 37890 + operands: + - { kinds: [register], role: use, register_class: GPR_NX0, register_bank: GPR, name: RS1 } + num_explicit_defs: 0 + flags: [terminator, branch, indirect_branch, produces_code] + implicit_defs: [{ register: X1 }] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RS1 + - + id: 1212 + symbol: C_ADD + name: C_ADD + assembly_mnemonic: c.add + encoding: 37926 + operands: + - { kinds: [register], role: def_use, register_class: GPR_NX0, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR_NX0, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: COMPRESSED + encoding_category: C + assembly_format: RD_RS2 + - + id: 1213 + symbol: C_FSDSP + name: C_FSDSP + assembly_mnemonic: c.fsdsp + encoding: 42018 + operands: + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, memory_role: value, name: FRS2 } + - { kinds: [immediate], immediate_predicate: C_UIMM9_A8, name: IMM } + num_explicit_defs: 0 + flags: [may_store, produces_code] + implicit_uses: [{ register: X2 }] + scheduling_class: STORE + encoding_category: C + assembly_format: FRS2_OFFSET_SP + - + id: 1214 + symbol: C_SWSP + name: C_SWSP + assembly_mnemonic: c.swsp + encoding: 50210 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [immediate], immediate_predicate: C_UIMM8_A4, name: IMM } + num_explicit_defs: 0 + flags: [may_store, produces_code] + implicit_uses: [{ register: X2 }] + scheduling_class: STORE + encoding_category: C + assembly_format: RS2_OFFSET_SP + - + id: 1215 + symbol: C_SDSP + name: C_SDSP + assembly_mnemonic: c.sdsp + encoding: 58402 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [immediate], immediate_predicate: C_UIMM9_A8, name: IMM } + num_explicit_defs: 0 + flags: [may_store, produces_code] + implicit_uses: [{ register: X2 }] + scheduling_class: STORE + encoding_category: C + assembly_format: RS2_OFFSET_SP diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoD.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoD.rccdesc new file mode 100644 index 0000000..15e4ab1 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoD.rccdesc @@ -0,0 +1,509 @@ +instructions: + - + id: 1148 + symbol: FLD + name: FLD + assembly_mnemonic: fld + encoding: 8696839 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, memory_role: value, name: FRD } + - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: I + assembly_format: FRD_OFFSET_RS1 + - + id: 1149 + symbol: FSD + name: FSD + assembly_mnemonic: fsd + encoding: 10794023 + operands: + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, memory_role: value, name: FRS2 } + - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 0 + flags: [may_store, produces_code] + scheduling_class: STORE + encoding_category: S + assembly_format: FRS2_OFFSET_RS1 + - + id: 1150 + symbol: FMADD_D + name: FMADD_D + assembly_mnemonic: fmadd.d + encoding: 1520731203 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS3 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R4 + assembly_format: FRD_FRS1_FRS2_FRS3_RM + - + id: 1151 + symbol: FMSUB_D + name: FMSUB_D + assembly_mnemonic: fmsub.d + encoding: 1520731207 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS3 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R4 + assembly_format: FRD_FRS1_FRS2_FRS3_RM + - + id: 1152 + symbol: FNMSUB_D + name: FNMSUB_D + assembly_mnemonic: fnmsub.d + encoding: 1520731211 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS3 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R4 + assembly_format: FRD_FRS1_FRS2_FRS3_RM + - + id: 1153 + symbol: FNMADD_D + name: FNMADD_D + assembly_mnemonic: fnmadd.d + encoding: 1520731215 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS3 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R4 + assembly_format: FRD_FRS1_FRS2_FRS3_RM + - + id: 1154 + symbol: FADD_D + name: FADD_D + assembly_mnemonic: fadd.d + encoding: 44336211 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2_RM + - + id: 1155 + symbol: FSUB_D + name: FSUB_D + assembly_mnemonic: fsub.d + encoding: 178553939 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2_RM + - + id: 1156 + symbol: FMUL_D + name: FMUL_D + assembly_mnemonic: fmul.d + encoding: 312771667 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R + assembly_format: FRD_FRS1_FRS2_RM + - + id: 1157 + symbol: FDIV_D + name: FDIV_D + assembly_mnemonic: fdiv.d + encoding: 446989395 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_DIV + encoding_category: R + assembly_format: FRD_FRS1_FRS2_RM + - + id: 1158 + symbol: FSQRT_D + name: FSQRT_D + assembly_mnemonic: fsqrt.d + encoding: 1510245459 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_SQRT + encoding_category: R + assembly_format: FRD_FRS1_RM + - + id: 1159 + symbol: FSGNJ_D + name: FSGNJ_D + assembly_mnemonic: fsgnj.d + encoding: 581207123 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1160 + symbol: FSGNJN_D + name: FSGNJN_D + assembly_mnemonic: fsgnjn.d + encoding: 581211219 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1161 + symbol: FSGNJX_D + name: FSGNJX_D + assembly_mnemonic: fsgnjx.d + encoding: 581215315 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1162 + symbol: FMIN_D + name: FMIN_D + assembly_mnemonic: fmin.d + encoding: 715424851 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1163 + symbol: FMAX_D + name: FMAX_D + assembly_mnemonic: fmax.d + encoding: 715428947 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1164 + symbol: FCVT_W_D + name: FCVT_W_D + assembly_mnemonic: fcvt.w.d + encoding: 3255075923 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1_RM + - + id: 1165 + symbol: FCVT_WU_D + name: FCVT_WU_D + assembly_mnemonic: fcvt.wu.d + encoding: 3256124499 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1_RM + - + id: 1166 + symbol: FCVT_L_D + name: FCVT_L_D + assembly_mnemonic: fcvt.l.d + encoding: 3257173075 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1_RM + - + id: 1167 + symbol: FCVT_LU_D + name: FCVT_LU_D + assembly_mnemonic: fcvt.lu.d + encoding: 3258221651 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1_RM + - + id: 1168 + symbol: FMV_X_D + name: FMV_X_D + assembly_mnemonic: fmv.x.d + encoding: 3791946835 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1 + - + id: 1169 + symbol: FEQ_D + name: FEQ_D + assembly_mnemonic: feq.d + encoding: 2728698963 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: RD_FRS1_FRS2 + - + id: 1170 + symbol: FLT_D + name: FLT_D + assembly_mnemonic: flt.d + encoding: 2728694867 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: RD_FRS1_FRS2 + - + id: 1171 + symbol: FLE_D + name: FLE_D + assembly_mnemonic: fle.d + encoding: 2728690771 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: RD_FRS1_FRS2 + - + id: 1172 + symbol: FCLASS_D + name: FCLASS_D + assembly_mnemonic: fclass.d + encoding: 3791950931 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1 + - + id: 1173 + symbol: FCVT_D_W + name: FCVT_D_W + assembly_mnemonic: fcvt.d.w + encoding: 3523511379 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1_RM + - + id: 1174 + symbol: FCVT_D_WU + name: FCVT_D_WU + assembly_mnemonic: fcvt.d.wu + encoding: 3524559955 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1_RM + - + id: 1175 + symbol: FCVT_D_L + name: FCVT_D_L + assembly_mnemonic: fcvt.d.l + encoding: 3525608531 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1_RM + - + id: 1176 + symbol: FCVT_D_LU + name: FCVT_D_LU + assembly_mnemonic: fcvt.d.lu + encoding: 3526657107 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1_RM + - + id: 1177 + symbol: FMV_D_X + name: FMV_D_X + assembly_mnemonic: fmv.d.x + encoding: 4060382291 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1 + - + id: 1178 + symbol: FCVT_S_D + name: FCVT_S_D + assembly_mnemonic: fcvt.s.d + encoding: 1075086419 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_FRS1_RM + - + id: 1179 + symbol: FCVT_D_S + name: FCVT_D_S + assembly_mnemonic: fcvt.d.s + encoding: 1107592275 + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_FRS1_RM diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoF.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoF.rccdesc new file mode 100644 index 0000000..3149207 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoF.rccdesc @@ -0,0 +1,477 @@ +instructions: + - + id: 1118 + symbol: FLW + name: FLW + assembly_mnemonic: flw + encoding: 8692743 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, memory_role: value, name: FRD } + - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: I + assembly_format: FRD_OFFSET_RS1 + - + id: 1119 + symbol: FSW + name: FSW + assembly_mnemonic: fsw + encoding: 10789927 + operands: + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, memory_role: value, name: FRS2 } + - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 0 + flags: [may_store, produces_code] + scheduling_class: STORE + encoding_category: S + assembly_format: FRS2_OFFSET_RS1 + - + id: 1120 + symbol: FMADD_S + name: FMADD_S + assembly_mnemonic: fmadd.s + encoding: 1487176771 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS3 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R4 + assembly_format: FRD_FRS1_FRS2_FRS3_RM + - + id: 1121 + symbol: FMSUB_S + name: FMSUB_S + assembly_mnemonic: fmsub.s + encoding: 1487176775 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS3 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R4 + assembly_format: FRD_FRS1_FRS2_FRS3_RM + - + id: 1122 + symbol: FNMSUB_S + name: FNMSUB_S + assembly_mnemonic: fnmsub.s + encoding: 1487176779 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS3 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R4 + assembly_format: FRD_FRS1_FRS2_FRS3_RM + - + id: 1123 + symbol: FNMADD_S + name: FNMADD_S + assembly_mnemonic: fnmadd.s + encoding: 1487176783 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS3 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R4 + assembly_format: FRD_FRS1_FRS2_FRS3_RM + - + id: 1124 + symbol: FADD_S + name: FADD_S + assembly_mnemonic: fadd.s + encoding: 10781779 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2_RM + - + id: 1125 + symbol: FSUB_S + name: FSUB_S + assembly_mnemonic: fsub.s + encoding: 144999507 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2_RM + - + id: 1126 + symbol: FMUL_S + name: FMUL_S + assembly_mnemonic: fmul.s + encoding: 279217235 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_MUL + encoding_category: R + assembly_format: FRD_FRS1_FRS2_RM + - + id: 1127 + symbol: FDIV_S + name: FDIV_S + assembly_mnemonic: fdiv.s + encoding: 413434963 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_DIV + encoding_category: R + assembly_format: FRD_FRS1_FRS2_RM + - + id: 1128 + symbol: FSQRT_S + name: FSQRT_S + assembly_mnemonic: fsqrt.s + encoding: 1476691027 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_SQRT + encoding_category: R + assembly_format: FRD_FRS1_RM + - + id: 1129 + symbol: FSGNJ_S + name: FSGNJ_S + assembly_mnemonic: fsgnj.s + encoding: 547652691 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1130 + symbol: FSGNJN_S + name: FSGNJN_S + assembly_mnemonic: fsgnjn.s + encoding: 547656787 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1131 + symbol: FSGNJX_S + name: FSGNJX_S + assembly_mnemonic: fsgnjx.s + encoding: 547660883 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1132 + symbol: FMIN_S + name: FMIN_S + assembly_mnemonic: fmin.s + encoding: 681870419 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1133 + symbol: FMAX_S + name: FMAX_S + assembly_mnemonic: fmax.s + encoding: 681874515 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: FRD_FRS1_FRS2 + - + id: 1134 + symbol: FCVT_W_S + name: FCVT_W_S + assembly_mnemonic: fcvt.w.s + encoding: 3221521491 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1_RM + - + id: 1135 + symbol: FCVT_WU_S + name: FCVT_WU_S + assembly_mnemonic: fcvt.wu.s + encoding: 3222570067 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1_RM + - + id: 1136 + symbol: FCVT_L_S + name: FCVT_L_S + assembly_mnemonic: fcvt.l.s + encoding: 3223618643 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1_RM + - + id: 1137 + symbol: FCVT_LU_S + name: FCVT_LU_S + assembly_mnemonic: fcvt.lu.s + encoding: 3224667219 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1_RM + - + id: 1138 + symbol: FMV_X_W + name: FMV_X_W + assembly_mnemonic: fmv.x.w + encoding: 3758392403 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1 + - + id: 1139 + symbol: FEQ_S + name: FEQ_S + assembly_mnemonic: feq.s + encoding: 2695144531 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: RD_FRS1_FRS2 + - + id: 1140 + symbol: FLT_S + name: FLT_S + assembly_mnemonic: flt.s + encoding: 2695140435 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: RD_FRS1_FRS2 + - + id: 1141 + symbol: FLE_S + name: FLE_S + assembly_mnemonic: fle.s + encoding: 2695136339 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS2 } + num_explicit_defs: 1 + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_ADD + encoding_category: R + assembly_format: RD_FRS1_FRS2 + - + id: 1142 + symbol: FCLASS_S + name: FCLASS_S + assembly_mnemonic: fclass.s + encoding: 3758396499 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR, name: FRS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: RD_FRS1 + - + id: 1143 + symbol: FCVT_S_W + name: FCVT_S_W + assembly_mnemonic: fcvt.s.w + encoding: 3489956947 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1_RM + - + id: 1144 + symbol: FCVT_S_WU + name: FCVT_S_WU + assembly_mnemonic: fcvt.s.wu + encoding: 3491005523 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1_RM + - + id: 1145 + symbol: FCVT_S_L + name: FCVT_S_L + assembly_mnemonic: fcvt.s.l + encoding: 3492054099 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1_RM + - + id: 1146 + symbol: FCVT_S_LU + name: FCVT_S_LU + assembly_mnemonic: fcvt.s.lu + encoding: 3493102675 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: RM, name: RM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [may_raise_fp_exception, produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1_RM + - + id: 1147 + symbol: FMV_W_X + name: FMV_W_X + assembly_mnemonic: fmv.w.x + encoding: 4026827859 + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR, name: FRD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: FP_CONVERT + encoding_category: R + assembly_format: FRD_RS1 diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoI.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoI.rccdesc new file mode 100644 index 0000000..13ea700 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoI.rccdesc @@ -0,0 +1,887 @@ +instructions: + - + id: 1024 + symbol: LUI + name: LUI + assembly_mnemonic: lui + encoding: 5175 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: UIMM20, allowed_target_flags: 32, name: IMM } + num_explicit_defs: 1 + flags: [produces_code, rematerializable] + scheduling_class: ALU + encoding_category: U + assembly_format: RD_IMM + - + id: 1025 + symbol: AUIPC + name: AUIPC + assembly_mnemonic: auipc + encoding: 5143 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [immediate, basic_block, global_symbol, external_symbol, mc_symbol], immediate_predicate: UIMM20, allowed_target_flags: 130, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: U + assembly_format: RD_IMM + - + id: 1026 + symbol: ADDI + name: ADDI + assembly_mnemonic: addi + encoding: 1344531 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, allowed_target_flags: 68, name: IMM } + num_explicit_defs: 1 + flags: [produces_code, rematerializable] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1042 + symbol: SRLI + name: SRLI + assembly_mnemonic: srli + encoding: 1365011 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1043 + symbol: SRAI + name: SRAI + assembly_mnemonic: srai + encoding: 1075106835 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1044 + symbol: ADDIW + name: ADDIW + assembly_mnemonic: addiw + encoding: 1344539 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: SIMM12, name: IMM } + num_explicit_defs: 1 + flags: [produces_code, rematerializable] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1045 + symbol: SLLIW + name: SLLIW + assembly_mnemonic: slliw + encoding: 1348635 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: UIMM5, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1046 + symbol: SRLIW + name: SRLIW + assembly_mnemonic: srliw + encoding: 1365019 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: UIMM5, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1047 + symbol: SRAIW + name: SRAIW + assembly_mnemonic: sraiw + encoding: 1075106843 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: UIMM5, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1048 + symbol: SLL + name: SLL + assembly_mnemonic: sll + encoding: 10785843 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1049 + symbol: SLT + name: SLT + assembly_mnemonic: slt + encoding: 10789939 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1050 + symbol: SLTU + name: SLTU + assembly_mnemonic: sltu + encoding: 10794035 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1051 + symbol: XOR + name: XOR + assembly_mnemonic: xor + encoding: 10798131 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1052 + symbol: SRL + name: SRL + assembly_mnemonic: srl + encoding: 10802227 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1053 + symbol: SRA + name: SRA + assembly_mnemonic: sra + encoding: 1084544051 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1054 + symbol: OR + name: OR + assembly_mnemonic: or + encoding: 10806323 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1055 + symbol: AND + name: AND + assembly_mnemonic: and + encoding: 10810419 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1056 + symbol: ADDW + name: ADDW + assembly_mnemonic: addw + encoding: 10781755 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1057 + symbol: SUBW + name: SUBW + assembly_mnemonic: subw + encoding: 1084523579 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1058 + symbol: SLLW + name: SLLW + assembly_mnemonic: sllw + encoding: 10785851 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1059 + symbol: SRLW + name: SRLW + assembly_mnemonic: srlw + encoding: 10802235 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1060 + symbol: SRAW + name: SRAW + assembly_mnemonic: sraw + encoding: 1084544059 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1061 + symbol: LB + name: LB + assembly_mnemonic: lb + encoding: 8684547 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: I + assembly_format: RD_OFFSET_RS1 + - + id: 1062 + symbol: LH + name: LH + assembly_mnemonic: lh + encoding: 8688643 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: I + assembly_format: RD_OFFSET_RS1 + - + id: 1063 + symbol: LW + name: LW + assembly_mnemonic: lw + encoding: 8692739 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: I + assembly_format: RD_OFFSET_RS1 + - + id: 1064 + symbol: LBU + name: LBU + assembly_mnemonic: lbu + encoding: 8700931 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: I + assembly_format: RD_OFFSET_RS1 + - + id: 1065 + symbol: LHU + name: LHU + assembly_mnemonic: lhu + encoding: 8705027 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: I + assembly_format: RD_OFFSET_RS1 + - + id: 1066 + symbol: LWU + name: LWU + assembly_mnemonic: lwu + encoding: 8709123 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: I + assembly_format: RD_OFFSET_RS1 + - + id: 1067 + symbol: SB + name: SB + assembly_mnemonic: sb + encoding: 10781731 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 0 + flags: [may_store, produces_code] + scheduling_class: STORE + encoding_category: S + assembly_format: RS2_OFFSET_RS1 + - + id: 1068 + symbol: SH + name: SH + assembly_mnemonic: sh + encoding: 10785827 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 0 + flags: [may_store, produces_code] + scheduling_class: STORE + encoding_category: S + assembly_format: RS2_OFFSET_RS1 + - + id: 1069 + symbol: SW + name: SW + assembly_mnemonic: sw + encoding: 10789923 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 0 + flags: [may_store, produces_code] + scheduling_class: STORE + encoding_category: S + assembly_format: RS2_OFFSET_RS1 + - + id: 1070 + symbol: BLT + name: BLT + assembly_mnemonic: blt + encoding: 9715811 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [terminator, branch, produces_code] + scheduling_class: BRANCH + encoding_category: B + assembly_format: RS1_RS2_TARGET + - + id: 1071 + symbol: BGE + name: BGE + assembly_mnemonic: bge + encoding: 9719907 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [terminator, branch, produces_code] + scheduling_class: BRANCH + encoding_category: B + assembly_format: RS1_RS2_TARGET + - + id: 1072 + symbol: BLTU + name: BLTU + assembly_mnemonic: bltu + encoding: 9724003 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [terminator, branch, produces_code] + scheduling_class: BRANCH + encoding_category: B + assembly_format: RS1_RS2_TARGET + - + id: 1073 + symbol: BGEU + name: BGEU + assembly_mnemonic: bgeu + encoding: 9728099 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [terminator, branch, produces_code] + scheduling_class: BRANCH + encoding_category: B + assembly_format: RS1_RS2_TARGET + - + id: 1074 + symbol: JALR + name: JALR + assembly_mnemonic: jalr + encoding: 1344615 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate, basic_block, global_symbol, external_symbol, mc_symbol], immediate_predicate: SIMM12, allowed_target_flags: 4, name: IMM } + num_explicit_defs: 1 + flags: [terminator, branch, indirect_branch, produces_code] + scheduling_class: BRANCH + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1075 + symbol: FENCE + name: FENCE + assembly_mnemonic: fence + encoding: 15 + operands: + - { kinds: [immediate], immediate_predicate: UIMM4, name: PRED } + - { kinds: [immediate], immediate_predicate: UIMM4, name: SUCC } + num_explicit_defs: 0 + flags: [has_side_effects, produces_code] + scheduling_class: FENCE + encoding_category: I + assembly_format: IMM_IMM + - + id: 1076 + symbol: FENCE_I + name: FENCE_I + assembly_mnemonic: fence.i + encoding: 4111 + operands: [] + num_explicit_defs: 0 + flags: [has_side_effects, produces_code] + scheduling_class: FENCE + encoding_category: I + assembly_format: NONE + - + id: 1077 + symbol: CSRRW + name: CSRRW + assembly_mnemonic: csrrw + encoding: 1348723 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [has_side_effects, produces_code] + scheduling_class: SYSTEM + encoding_category: SYSTEM + assembly_format: RD_CSR_RS1 + - + id: 1078 + symbol: CSRRS + name: CSRRS + assembly_mnemonic: csrrs + encoding: 1352819 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [has_side_effects, produces_code] + scheduling_class: SYSTEM + encoding_category: SYSTEM + assembly_format: RD_CSR_RS1 + - + id: 1079 + symbol: CSRRC + name: CSRRC + assembly_mnemonic: csrrc + encoding: 1356915 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + num_explicit_defs: 1 + flags: [has_side_effects, produces_code] + scheduling_class: SYSTEM + encoding_category: SYSTEM + assembly_format: RD_CSR_RS1 + - + id: 1080 + symbol: CSRRWI + name: CSRRWI + assembly_mnemonic: csrrwi + encoding: 1102963 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } + - { kinds: [immediate], immediate_predicate: UIMM5, name: ZIMM } + num_explicit_defs: 1 + flags: [has_side_effects, produces_code] + scheduling_class: SYSTEM + encoding_category: SYSTEM + assembly_format: RD_CSR_IMM + - + id: 1081 + symbol: CSRRSI + name: CSRRSI + assembly_mnemonic: csrrsi + encoding: 1107059 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } + - { kinds: [immediate], immediate_predicate: UIMM5, name: ZIMM } + num_explicit_defs: 1 + flags: [has_side_effects, produces_code] + scheduling_class: SYSTEM + encoding_category: SYSTEM + assembly_format: RD_CSR_IMM + - + id: 1082 + symbol: CSRRCI + name: CSRRCI + assembly_mnemonic: csrrci + encoding: 1111155 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [immediate], immediate_predicate: UIMM12, name: CSR } + - { kinds: [immediate], immediate_predicate: UIMM5, name: ZIMM } + num_explicit_defs: 1 + flags: [has_side_effects, produces_code] + scheduling_class: SYSTEM + encoding_category: SYSTEM + assembly_format: RD_CSR_IMM + - + id: 1027 + symbol: ADD + name: ADD + assembly_mnemonic: add + encoding: 10781747 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1028 + symbol: SUB + name: SUB + assembly_mnemonic: sub + encoding: 1084523571 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1029 + symbol: LD + name: LD + assembly_mnemonic: ld + encoding: 8696835 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, memory_role: value, name: RD } + - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 1 + flags: [may_load, produces_code] + scheduling_class: LOAD + encoding_category: I + assembly_format: RD_OFFSET_RS1 + - + id: 1030 + symbol: SD + name: SD + assembly_mnemonic: sd + encoding: 10794019 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, memory_role: value, name: RS2 } + - { kinds: [register, frame_index], role: use, register_class: GPR, register_bank: GPR, memory_role: address, name: RS1 } + - { kinds: [immediate, constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol], immediate_predicate: SIMM12, name: IMM, allowed_target_flags: 68 } + num_explicit_defs: 0 + flags: [may_store, produces_code] + scheduling_class: STORE + encoding_category: S + assembly_format: RS2_OFFSET_RS1 + - + id: 1031 + symbol: BEQ + name: BEQ + assembly_mnemonic: beq + encoding: 9699427 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [terminator, branch, produces_code] + scheduling_class: BRANCH + encoding_category: B + assembly_format: RS1_RS2_TARGET + - + id: 1032 + symbol: BNE + name: BNE + assembly_mnemonic: bne + encoding: 9703523 + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [terminator, branch, produces_code] + scheduling_class: BRANCH + encoding_category: B + assembly_format: RS1_RS2_TARGET + - + id: 1033 + symbol: JAL + name: JAL + assembly_mnemonic: jal + encoding: 1135 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 1 + flags: [terminator, branch, produces_code] + scheduling_class: BRANCH + encoding_category: J + assembly_format: RD_TARGET + - + id: 1034 + symbol: ECALL + name: ECALL + assembly_mnemonic: ecall + encoding: 115 + operands: [] + num_explicit_defs: 0 + flags: [has_side_effects, produces_code] + scheduling_class: SYSTEM + encoding_category: SYSTEM + assembly_format: NONE + - + id: 1035 + symbol: EBREAK + name: EBREAK + assembly_mnemonic: ebreak + encoding: 1048691 + operands: [] + num_explicit_defs: 0 + flags: [has_side_effects, produces_code] + scheduling_class: SYSTEM + encoding_category: SYSTEM + assembly_format: NONE + - + id: 1266 + symbol: UNIMP + name: UNIMP + assembly_mnemonic: unimp + encoding: 3221229683 + operands: [] + num_explicit_defs: 0 + flags: [has_side_effects, produces_code] + scheduling_class: SYSTEM + encoding_category: SYSTEM + assembly_format: NONE + - + id: 1036 + symbol: SLTI + name: SLTI + assembly_mnemonic: slti + encoding: 1352723 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: SIMM12, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1037 + symbol: SLTIU + name: SLTIU + assembly_mnemonic: sltiu + encoding: 1356819 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: SIMM12, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1038 + symbol: XORI + name: XORI + assembly_mnemonic: xori + encoding: 1360915 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: SIMM12, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1039 + symbol: ORI + name: ORI + assembly_mnemonic: ori + encoding: 1369107 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: SIMM12, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1040 + symbol: ANDI + name: ANDI + assembly_mnemonic: andi + encoding: 1373203 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: SIMM12, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1041 + symbol: SLLI + name: SLLI + assembly_mnemonic: slli + encoding: 1348627 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoM.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoM.rccdesc new file mode 100644 index 0000000..62e8e6c --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoM.rccdesc @@ -0,0 +1,196 @@ +instructions: + - + id: 1083 + symbol: MUL + name: MUL + assembly_mnemonic: mul + encoding: 44336179 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: MUL + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1084 + symbol: MULH + name: MULH + assembly_mnemonic: mulh + encoding: 44340275 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: MUL + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1085 + symbol: MULHSU + name: MULHSU + assembly_mnemonic: mulhsu + encoding: 44344371 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: MUL + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1086 + symbol: MULHU + name: MULHU + assembly_mnemonic: mulhu + encoding: 44348467 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: MUL + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1087 + symbol: DIV + name: DIV + assembly_mnemonic: div + encoding: 44352563 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: DIV + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1088 + symbol: DIVU + name: DIVU + assembly_mnemonic: divu + encoding: 44356659 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: DIV + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1089 + symbol: REM + name: REM + assembly_mnemonic: rem + encoding: 44360755 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: DIV + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1090 + symbol: REMU + name: REMU + assembly_mnemonic: remu + encoding: 44364851 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: DIV + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1091 + symbol: MULW + name: MULW + assembly_mnemonic: mulw + encoding: 44336187 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: MUL + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1092 + symbol: DIVW + name: DIVW + assembly_mnemonic: divw + encoding: 44352571 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: DIV + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1093 + symbol: DIVUW + name: DIVUW + assembly_mnemonic: divuw + encoding: 44356667 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: DIV + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1094 + symbol: REMW + name: REMW + assembly_mnemonic: remw + encoding: 44360763 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: DIV + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1095 + symbol: REMUW + name: REMUW + assembly_mnemonic: remuw + encoding: 44364859 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: DIV + encoding_category: R + assembly_format: RD_RS1_RS2 diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoPseudo.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoPseudo.rccdesc new file mode 100644 index 0000000..19a0975 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoPseudo.rccdesc @@ -0,0 +1,461 @@ +instructions: + - + id: 1229 + symbol: G_SLLW + name: G_SLLW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1230 + symbol: G_SRAW + name: G_SRAW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1231 + symbol: G_DIVW + name: G_DIVW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1232 + symbol: G_CLZW + name: G_CLZW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1233 + symbol: G_CTZW + name: G_CTZW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1234 + symbol: G_FCVT_W_RV64 + name: G_FCVT_W_RV64 + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 1 } + - { kinds: [immediate] } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1235 + symbol: G_FCLASS + name: G_FCLASS + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 1 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1236 + symbol: G_ROLW + name: G_ROLW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1237 + symbol: G_SRLW + name: G_SRLW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1238 + symbol: G_DIVUW + name: G_DIVUW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1239 + symbol: G_REMUW + name: G_REMUW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1240 + symbol: G_RORW + name: G_RORW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1241 + symbol: G_BREV8 + name: G_BREV8 + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1242 + symbol: G_FCVT_WU_RV64 + name: G_FCVT_WU_RV64 + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 1 } + - { kinds: [immediate] } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1243 + symbol: G_CLSW + name: G_CLSW + operands: + - { kinds: [register], role: def, type_index: 0 } + - { kinds: [register], role: use, type_index: 0 } + num_explicit_defs: 1 + flags: [generic, pseudo, allowed_pre_legalizer] + owner: instruction_select + deadline: selected + - + id: 1244 + symbol: Select_GPR_Using_CC_GPR + name: Select_GPR_Using_CC_GPR + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR } + - { kinds: [immediate] } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR } + num_explicit_defs: 1 + flags: [pseudo, produces_code, allowed_pre_legalizer, uses_custom_inserter] + owner: instruction_select + deadline: selected + - + id: 1264 + symbol: Select_FPR32_Using_CC_GPR + name: Select_FPR32_Using_CC_GPR + operands: + - { kinds: [register], role: def, register_class: FPR32, register_bank: FPR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR } + - { kinds: [immediate] } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR } + - { kinds: [register], role: use, register_class: FPR32, register_bank: FPR } + num_explicit_defs: 1 + flags: [pseudo, produces_code, allowed_pre_legalizer, uses_custom_inserter] + owner: instruction_select + deadline: selected + - + id: 1265 + symbol: Select_FPR64_Using_CC_GPR + name: Select_FPR64_Using_CC_GPR + operands: + - { kinds: [register], role: def, register_class: FPR64, register_bank: FPR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR } + - { kinds: [immediate] } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR } + - { kinds: [register], role: use, register_class: FPR64, register_bank: FPR } + num_explicit_defs: 1 + flags: [pseudo, produces_code, allowed_pre_legalizer, uses_custom_inserter] + owner: instruction_select + deadline: selected + - + id: 1245 + symbol: ReadFFLAGS + name: ReadFFLAGS + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR } + num_explicit_defs: 1 + implicit_uses: [{ register: FFLAGS }] + flags: [pseudo, produces_code, allowed_pre_legalizer, has_side_effects, terminal_emission] + owner: asm_printer + deadline: emission + + - + id: 1246 + symbol: WriteFFLAGS + name: WriteFFLAGS + operands: + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR } + num_explicit_defs: 0 + implicit_defs: [{ register: FFLAGS }] + flags: [pseudo, produces_code, allowed_pre_legalizer, has_side_effects, terminal_emission] + owner: asm_printer + deadline: emission + - + id: 1217 + symbol: PseudoLOAD_STACK_ARG + name: PseudoLOAD_STACK_ARG + operands: + - { kinds: [register], role: def, memory_role: value } + - { kinds: [frame_index] } + - { kinds: [immediate, integer_immediate] } + - { kinds: [immediate] } + num_explicit_defs: 1 + flags: [pseudo, produces_code, allowed_pre_legalizer, may_load] + owner: instruction_select + deadline: selected + - + id: 1218 + symbol: PseudoSTORE_STACK_ARG + name: PseudoSTORE_STACK_ARG + operands: + - { kinds: [register], role: use, memory_role: value } + - { kinds: [frame_index] } + - { kinds: [immediate, integer_immediate] } + - { kinds: [immediate] } + num_explicit_defs: 0 + flags: [pseudo, produces_code, allowed_pre_legalizer, may_store] + owner: instruction_select + deadline: selected + - + id: 1219 + symbol: PseudoBYVAL_COPY + name: PseudoBYVAL_COPY + operands: + - { kinds: [register], role: def } + - { kinds: [register], role: use, memory_role: address } + - { kinds: [frame_index] } + - { kinds: [immediate, integer_immediate] } + - { kinds: [immediate, integer_immediate] } + num_explicit_defs: 1 + flags: [pseudo, produces_code, allowed_pre_legalizer, may_load, may_store] + owner: instruction_select + deadline: selected + - + id: 1220 + symbol: ADJCALLSTACKDOWN + name: ADJCALLSTACKDOWN + operands: + - { kinds: [immediate, integer_immediate] } + - { kinds: [immediate, integer_immediate] } + num_explicit_defs: 0 + implicit_defs: [{ register: X2 }] + implicit_uses: [{ register: X2 }] + flags: [pseudo, allowed_pre_legalizer, has_side_effects] + owner: prologue_epilogue_insertion + deadline: frame_finalized + - + id: 1221 + symbol: ADJCALLSTACKUP + name: ADJCALLSTACKUP + operands: + - { kinds: [immediate, integer_immediate] } + - { kinds: [immediate, integer_immediate] } + num_explicit_defs: 0 + implicit_defs: [{ register: X2 }] + implicit_uses: [{ register: X2 }] + flags: [pseudo, allowed_pre_legalizer, has_side_effects] + owner: prologue_epilogue_insertion + deadline: frame_finalized + - + id: 1222 + symbol: PseudoCALL + name: PseudoCALL + operands: + - { kinds: [global_symbol, external_symbol, register], role: use, allowed_target_flags: 1 } + - { kinds: [register_mask], optional: true } + num_explicit_defs: 0 + implicit_defs: [{ register: X1 }] + allow_extra_implicit_operands: true + flags: [pseudo, produces_code, allowed_pre_legalizer, call, may_load, may_store, has_side_effects, not_duplicable, unmodeled_side_effects] + size: 8 + owner: target_late_expansion + deadline: post_ra_finalized + - + id: 1223 + symbol: PseudoTAIL + name: PseudoTAIL + operands: + - { kinds: [global_symbol, external_symbol, register], role: use, allowed_target_flags: 1 } + - { kinds: [register_mask], optional: true } + num_explicit_defs: 0 + allow_extra_implicit_operands: true + flags: [pseudo, produces_code, allowed_pre_legalizer, terminator, branch, call, barrier, may_load, may_store, has_side_effects, not_duplicable, unmodeled_side_effects] + size: 8 + owner: target_late_expansion + deadline: post_ra_finalized + - + id: 1269 + symbol: PseudoCALLReg + name: PseudoCALLReg + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [global_symbol, external_symbol], allowed_target_flags: 1 } + - { kinds: [register_mask], optional: true } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [pseudo, produces_code, allowed_pre_legalizer, call, may_load, may_store, has_side_effects, not_duplicable, unmodeled_side_effects] + size: 8 + owner: target_late_expansion + deadline: post_ra_finalized + - + id: 1224 + symbol: PseudoRET + name: PseudoRET + operands: [] + num_explicit_defs: 0 + allow_extra_implicit_operands: true + flags: [pseudo, produces_code, allowed_pre_legalizer, terminator, return, barrier, terminal_emission] + size: 4 + owner: asm_printer + deadline: emission + + - + id: 1225 + symbol: JALR_CALL + name: JALR_CALL + assembly_mnemonic: jalr + encoding: 1344615 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate, mc_symbol], immediate_predicate: SIMM12, allowed_target_flags: 4, name: IMM } + - { kinds: [register_mask], optional: true } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [call, may_load, may_store, has_side_effects, produces_code, not_duplicable, unmodeled_side_effects] + scheduling_class: BRANCH + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1226 + symbol: JALR_TAIL + name: JALR_TAIL + assembly_mnemonic: jalr + encoding: 1344615 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate, mc_symbol], immediate_predicate: SIMM12, allowed_target_flags: 4, name: IMM } + - { kinds: [register_mask], optional: true } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [terminator, branch, indirect_branch, call, barrier, may_load, may_store, has_side_effects, produces_code, not_duplicable, unmodeled_side_effects] + scheduling_class: BRANCH + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1227 + symbol: JALR_RET + name: JALR_RET + assembly_mnemonic: jalr + encoding: 1344615 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: SIMM12, name: IMM } + num_explicit_defs: 1 + allow_extra_implicit_operands: true + flags: [terminator, return, barrier, produces_code] + scheduling_class: BRANCH + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1228 + symbol: PseudoBR + name: PseudoBR + operands: + - { kinds: [basic_block], name: TARGET } + num_explicit_defs: 0 + flags: [pseudo, produces_code, allowed_pre_legalizer, terminator, branch, barrier, terminal_emission] + size: 4 + owner: asm_printer + deadline: emission + - + id: 1267 + symbol: PseudoLLA + name: PseudoLLA + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR } + - { kinds: [constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol] } + num_explicit_defs: 1 + flags: [pseudo, produces_code, allowed_pre_legalizer] + size: 8 + owner: pre_ra_expansion + deadline: tied_ops_rewritten + - + id: 1268 + symbol: PseudoLGA + name: PseudoLGA + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR } + - { kinds: [constant_pool_index, jump_table_index, global_symbol, external_symbol, block_address, mc_symbol] } + num_explicit_defs: 1 + flags: [pseudo, produces_code, allowed_pre_legalizer, may_load] + size: 8 + requires_memory_operands: true + owner: pre_ra_expansion + deadline: tied_ops_rewritten + - + id: 1263 + symbol: PseudoBRIND + name: PseudoBRIND + operands: + - { kinds: [register], role: use, register_class: GPRJALRNonX7, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: SIMM12, name: IMM } + num_explicit_defs: 0 + flags: [pseudo, produces_code, allowed_pre_legalizer, terminator, branch, indirect_branch, barrier, terminal_emission] + size: 4 + owner: asm_printer + deadline: emission diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoZb.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoZb.rccdesc new file mode 100644 index 0000000..103c3ab --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoZb.rccdesc @@ -0,0 +1,237 @@ +instructions: + - + id: 1247 + symbol: SH1ADD + name: SH1ADD + assembly_mnemonic: sh1add + encoding: 547660851 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1248 + symbol: SH2ADD + name: SH2ADD + assembly_mnemonic: sh2add + encoding: 547669043 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1249 + symbol: SH3ADD + name: SH3ADD + assembly_mnemonic: sh3add + encoding: 547677235 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1250 + symbol: SH1ADD_UW + name: SH1ADD_UW + assembly_mnemonic: sh1add.uw + encoding: 547660859 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1251 + symbol: SH2ADD_UW + name: SH2ADD_UW + assembly_mnemonic: sh2add.uw + encoding: 547669051 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1252 + symbol: SH3ADD_UW + name: SH3ADD_UW + assembly_mnemonic: sh3add.uw + encoding: 547677243 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1253 + symbol: SLLI_UW + name: SLLI_UW + assembly_mnemonic: slli.uw + encoding: 135566363 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [immediate], immediate_predicate: UIMM6, name: IMM } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1_IMM + - + id: 1254 + symbol: ADD_UW + name: ADD_UW + assembly_mnemonic: add.uw + encoding: 144999483 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1255 + symbol: SEXT_B + name: SEXT_B + assembly_mnemonic: sext.b + encoding: 1615107091 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1 + - + id: 1256 + symbol: SEXT_H + name: SEXT_H + assembly_mnemonic: sext.h + encoding: 1616155667 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: I + assembly_format: RD_RS1 + - + id: 1257 + symbol: ZEXT_H_RV32 + name: ZEXT_H_RV32 + assembly_mnemonic: zext.h + encoding: 134530099 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1 + - + id: 1258 + symbol: ZEXT_H_RV64 + name: ZEXT_H_RV64 + assembly_mnemonic: zext.h + encoding: 134530107 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + num_explicit_defs: 1 + flags: [produces_code] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1 + - + id: 1259 + symbol: MIN + name: MIN + assembly_mnemonic: min + encoding: 178570291 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1260 + symbol: MINU + name: MINU + assembly_mnemonic: minu + encoding: 178574387 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1261 + symbol: MAX + name: MAX + assembly_mnemonic: max + encoding: 178578483 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 + - + id: 1262 + symbol: MAXU + name: MAXU + assembly_mnemonic: maxu + encoding: 178582579 + operands: + - { kinds: [register], role: def, register_class: GPR, register_bank: GPR, name: RD } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: GPR, register_bank: GPR, name: RS2 } + num_explicit_defs: 1 + flags: [produces_code, commutable] + scheduling_class: ALU + encoding_category: R + assembly_format: RD_RS1_RS2 diff --git a/backend/lib/Target/RISCV/RISCVInstrInfoZcmp.rccdesc b/backend/lib/Target/RISCV/RISCVInstrInfoZcmp.rccdesc new file mode 100644 index 0000000..eb0f822 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVInstrInfoZcmp.rccdesc @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +instructions: + - + id: 1270 + symbol: CM_MVA01S + name: CM_MVA01S + assembly_mnemonic: cm.mva01s + encoding: 44130 + operands: + - { kinds: [register], role: use, register_class: SR07, register_bank: GPR, name: RS1 } + - { kinds: [register], role: use, register_class: SR07, register_bank: GPR, name: RS2 } + num_explicit_defs: 0 + flags: [produces_code] + implicit_defs: [{ register: X10 }, { register: X11 }] + scheduling_class: COMPRESSED + encoding_category: ZCMP_MOVE + assembly_format: RD_RS2 + - + id: 1271 + symbol: CM_MVSA01 + name: CM_MVSA01 + assembly_mnemonic: cm.mvsa01 + encoding: 44066 + operands: + - { kinds: [register], role: def, register_class: SR07, register_bank: GPR, name: RD } + - { kinds: [register], role: def, register_class: SR07, register_bank: GPR, name: RS2 } + num_explicit_defs: 2 + flags: [produces_code] + implicit_uses: [{ register: X10 }, { register: X11 }] + scheduling_class: COMPRESSED + encoding_category: ZCMP_MOVE + assembly_format: RD_RS2 + - + id: 1272 + symbol: CM_PUSH + name: CM_PUSH + assembly_mnemonic: cm.push + encoding: 47106 + operands: + - { kinds: [immediate], immediate_predicate: ZCMP_RLIST, name: RLIST } + - { kinds: [immediate], immediate_predicate: ZCMP_STACKADJ, name: STACKADJ } + num_explicit_defs: 0 + allow_extra_implicit_operands: true + flags: [may_store, produces_code] + implicit_defs: [{ register: X2 }] + implicit_uses: [{ register: X2 }] + scheduling_class: STORE + encoding_category: ZCMP_STACK + assembly_format: RLIST_STACKADJ + - + id: 1273 + symbol: CM_POPRET + name: CM_POPRET + assembly_mnemonic: cm.popret + encoding: 48642 + operands: + - { kinds: [immediate], immediate_predicate: ZCMP_RLIST, name: RLIST } + - { kinds: [immediate], immediate_predicate: ZCMP_STACKADJ, name: STACKADJ } + num_explicit_defs: 0 + allow_extra_implicit_operands: true + flags: [terminator, return, barrier, may_load, produces_code] + implicit_defs: [{ register: X2 }] + implicit_uses: [{ register: X2 }] + scheduling_class: LOAD + encoding_category: ZCMP_STACK + assembly_format: RLIST_STACKADJ + - + id: 1274 + symbol: CM_POPRETZ + name: CM_POPRETZ + assembly_mnemonic: cm.popretz + encoding: 48130 + operands: + - { kinds: [immediate], immediate_predicate: ZCMP_RLIST, name: RLIST } + - { kinds: [immediate], immediate_predicate: ZCMP_STACKADJ, name: STACKADJ } + num_explicit_defs: 0 + allow_extra_implicit_operands: true + flags: [terminator, return, barrier, may_load, produces_code] + implicit_defs: [{ register: X2 }, { register: X10 }] + implicit_uses: [{ register: X2 }] + scheduling_class: LOAD + encoding_category: ZCMP_STACK + assembly_format: RLIST_STACKADJ + - + id: 1275 + symbol: CM_POP + name: CM_POP + assembly_mnemonic: cm.pop + encoding: 47618 + operands: + - { kinds: [immediate], immediate_predicate: ZCMP_RLIST, name: RLIST } + - { kinds: [immediate], immediate_predicate: ZCMP_STACKADJ, name: STACKADJ } + num_explicit_defs: 0 + allow_extra_implicit_operands: true + flags: [may_load, produces_code] + implicit_defs: [{ register: X2 }] + implicit_uses: [{ register: X2 }] + scheduling_class: LOAD + encoding_category: ZCMP_STACK + assembly_format: RLIST_STACKADJ diff --git a/backend/lib/Target/RISCV/RISCVMCCodeEmitter.cpp b/backend/lib/Target/RISCV/RISCVMCCodeEmitter.cpp new file mode 100644 index 0000000..48de3e4 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMCCodeEmitter.cpp @@ -0,0 +1,1075 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVMCCodeEmitter.h" + +#include "rcc/CodeGen/Register.h" +#include "rcc/MC/MCExpr.h" +#include "rcc/Target/RISCV/RISCVFixupKinds.h" +#include "rcc/Target/RISCV/RISCVInstPrinter.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <cstddef> +#include <cstdint> +#include <string> +#include <string_view> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] Expected<void> requireOperandCount(const mc::MCInst &Instruction, std::size_t Count) { + if (Instruction.operands().size() != Count) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction has an unexpected encoding operand count"); + } + return {}; +} + +[[nodiscard]] Expected<std::uint32_t> registerEncoding(const mc::MCInst &Instruction, + std::size_t Index, + const TargetDescription &Target) { + if (Index >= Instruction.operands().size()) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding references an absent register operand"); + } + const mc::MCRegister *Register = Instruction.operands()[Index].registerValue(); + if (Register == nullptr) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding requires a register operand"); + } + auto Physical = PhysicalRegister::create(Register->value()); + if (!Physical) + return std::unexpected(Physical.error()); + const PhysicalRegisterDesc *Description = Target.registers().physicalRegister(*Physical); + if (Description == nullptr || Description->Encoding >= 32) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding references an unknown physical register"); + } + return Description->Encoding; +} + +[[nodiscard]] Expected<void> validateRegisterOperands(const mc::MCInst &Instruction, + const InstructionDescriptor &Descriptor, + const TargetDescription &Target) { + for (std::size_t Index = 0; Index < Instruction.operands().size(); ++Index) { + const mc::MCRegister *Register = Instruction.operands()[Index].registerValue(); + if (Register == nullptr) + continue; + if (Index >= Descriptor.Operands.size()) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V register operand has no target descriptor"); + } + auto Physical = PhysicalRegister::create(Register->value()); + if (!Physical) + return std::unexpected(Physical.error()); + const OperandDescriptor &Operand = Descriptor.Operands[Index]; + if (Operand.RegClass && + !Target.registers().registerClassContains(*Operand.RegClass, *Physical)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V register is outside its required register class"); + } + if (Operand.RegBank && !Target.registers().bankContainsRegister(*Operand.RegBank, *Physical)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V register is outside its required register bank"); + } + } + return {}; +} + +[[nodiscard]] Expected<std::uint32_t> compactRegisterEncoding(const mc::MCInst &Instruction, + std::size_t Index, + const TargetDescription &Target) { + auto Encoding = registerEncoding(Instruction, Index, Target); + if (!Encoding) + return std::unexpected(Encoding.error()); + if (*Encoding < 8 || *Encoding > 15) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "compressed RISC-V instruction requires an x8-x15 register"); + } + return *Encoding - 8U; +} + +[[nodiscard]] Expected<std::uint32_t> savedRegisterEncoding(const mc::MCInst &Instruction, + std::size_t Index, + const TargetDescription &Target) { + auto Encoding = registerEncoding(Instruction, Index, Target); + if (!Encoding) + return std::unexpected(Encoding.error()); + if (*Encoding == 8 || *Encoding == 9) + return *Encoding - 8U; + if (*Encoding >= 18 && *Encoding <= 23) + return *Encoding - 16U; + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "Zcmp move requires an s0-s7 register"); +} + +[[nodiscard]] Expected<std::uint16_t> encodeZcmpMove(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto First = savedRegisterEncoding(Instruction, 0, Target); + auto Second = savedRegisterEncoding(Instruction, 1, Target); + if (!First) + return std::unexpected(First.error()); + if (!Second) + return std::unexpected(Second.error()); + const auto Encoding = static_cast<std::uint16_t>(Assembly.Encoding); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x039c}) | *First << 7U | + *Second << 2U); +} + +[[nodiscard]] bool fitsSigned(std::int64_t Value, unsigned Bits) noexcept { + const std::int64_t Minimum = -(std::int64_t{1} << (Bits - 1U)); + const std::int64_t Maximum = (std::int64_t{1} << (Bits - 1U)) - 1; + return Value >= Minimum && Value <= Maximum; +} + +[[nodiscard]] bool fitsUnsigned(std::int64_t Value, unsigned Bits) noexcept { + return Value >= 0 && static_cast<std::uint64_t>(Value) < (std::uint64_t{1} << Bits); +} + +[[nodiscard]] Expected<void> requireSigned(std::int64_t Value, unsigned Bits, + std::string_view Name) { + if (!fitsSigned(Value, Bits)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + std::string(Name) + " immediate is out of range"); + } + return {}; +} + +[[nodiscard]] Expected<void> requireUnsigned(std::int64_t Value, unsigned Bits, + std::string_view Name) { + if (!fitsUnsigned(Value, Bits)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + std::string(Name) + " immediate is out of range"); + } + return {}; +} + +[[nodiscard]] Expected<void> requireRoundingMode(std::int64_t Value) { + if ((Value >= 0 && Value <= 4) || Value == 7) + return {}; + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V rounding mode is not an ISA-defined value"); +} + +[[nodiscard]] Expected<void> requireAligned(std::int64_t Value, std::uint64_t Alignment, + std::string_view Name) { + if (Value % static_cast<std::int64_t>(Alignment) != 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + std::string(Name) + " immediate is not aligned"); + } + return {}; +} + +[[nodiscard]] Expected<std::uint32_t> expressionTargetKind(const mc::MCOperand &Operand) { + const mc::MCExprRef *Expression = Operand.expressionValue(); + if (Expression == nullptr) + return 0U; + if (!*Expression) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction has a null expression operand"); + } + auto Value = mc::evaluateAsRelocatable(**Expression); + if (!Value) + return std::unexpected(Value.error()); + return Value->TargetKind; +} + +[[nodiscard]] Expected<std::int64_t> immediateOrFixup(const mc::MCInst &Instruction, + std::size_t Index, mc::MCFixupKind Kind, + bool PCRelative, + mc::MCEncodedInstruction &Result) { + if (Index >= Instruction.operands().size()) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding references an absent immediate operand"); + } + const mc::MCOperand &Operand = Instruction.operands()[Index]; + if (const std::int64_t *Immediate = Operand.immediateValue(); Immediate != nullptr) + return *Immediate; + const mc::MCExprRef *Expression = Operand.expressionValue(); + if (Expression == nullptr || !*Expression) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V encoding requires an immediate or expression operand"); + } + if (const auto Absolute = (*Expression)->evaluateAbsolute()) + return *Absolute; + if (Kind == mc::fixup_kind::None) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction does not permit a symbolic immediate here"); + } + Result.Fixups.push_back(mc::MCFixup{.Offset = 0, + .Value = *Expression, + .Kind = Kind, + .PCRelative = PCRelative, + .LinkerRelaxable = false}); + return 0; +} + +[[nodiscard]] Expected<std::uint16_t> encodeZcmpStack(const mc::MCInst &Instruction, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto RegisterList = immediateOrFixup(Instruction, 0, mc::fixup_kind::None, false, Result); + auto StackAdjustment = immediateOrFixup(Instruction, 1, mc::fixup_kind::None, false, Result); + if (!RegisterList) + return std::unexpected(RegisterList.error()); + if (!StackAdjustment) + return std::unexpected(StackAdjustment.error()); + if (*RegisterList < 4 || *RegisterList > 15) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "Zcmp register list encoding is out of range"); + } + if (*StackAdjustment < 0 || *StackAdjustment > 48 || *StackAdjustment % 16 != 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "Zcmp stack adjustment must be 0, 16, 32, or 48"); + } + const auto Encoding = static_cast<std::uint16_t>(Assembly.Encoding); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x00fc}) | + static_cast<std::uint16_t>(*RegisterList) << 4U | + static_cast<std::uint16_t>(*StackAdjustment >> 4U) << 2U); +} + +[[nodiscard]] Expected<mc::MCFixupKind> uFixupKind(const mc::MCOperand &Operand) { + auto Kind = expressionTargetKind(Operand); + if (!Kind) + return std::unexpected(Kind.error()); + if (*Kind == 0) + return fixupKind(RISCVFixupKind::High20); + if (*Kind == static_cast<std::uint32_t>(RISCVMCExprKind::AbsoluteHigh)) + return fixupKind(RISCVFixupKind::High20); + if (*Kind == static_cast<std::uint32_t>(RISCVMCExprKind::PCRelativeHigh)) + return fixupKind(RISCVFixupKind::PCRelativeHigh20); + if (*Kind == static_cast<std::uint32_t>(RISCVMCExprKind::GOTPCRelativeHigh)) + return fixupKind(RISCVFixupKind::GOTPCRelativeHigh20); + if (*Kind == static_cast<std::uint32_t>(RISCVMCExprKind::CallPLT)) + return fixupKind(RISCVFixupKind::CallPLT); + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V U-type operand has an incompatible target expression"); +} + +[[nodiscard]] Expected<mc::MCFixupKind> lowFixupKind(const mc::MCOperand &Operand, bool Store) { + auto Kind = expressionTargetKind(Operand); + if (!Kind) + return std::unexpected(Kind.error()); + if (*Kind == 0) + return fixupKind(Store ? RISCVFixupKind::Low12S : RISCVFixupKind::Low12I); + if (*Kind == static_cast<std::uint32_t>(RISCVMCExprKind::AbsoluteLow)) + return fixupKind(Store ? RISCVFixupKind::Low12S : RISCVFixupKind::Low12I); + if (*Kind == static_cast<std::uint32_t>(RISCVMCExprKind::PCRelativeLow)) { + return fixupKind(Store ? RISCVFixupKind::PCRelativeLow12S : RISCVFixupKind::PCRelativeLow12I); + } + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V low immediate has an incompatible target expression"); +} + +[[nodiscard]] Expected<void> requirePlainExpression(const mc::MCOperand &Operand, + std::string_view Name) { + auto Kind = expressionTargetKind(Operand); + if (!Kind) + return std::unexpected(Kind.error()); + if (*Kind != 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + std::string(Name) + " does not accept a target expression modifier"); + } + return {}; +} + +[[nodiscard]] std::uint32_t encodeBranchImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint32_t>(Value); + return ((Bits >> 12U) & 1U) << 31U | ((Bits >> 5U) & 0x3fU) << 25U | ((Bits >> 1U) & 0xfU) << 8U | + ((Bits >> 11U) & 1U) << 7U; +} + +[[nodiscard]] std::uint32_t encodeJALImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint32_t>(Value); + return ((Bits >> 20U) & 1U) << 31U | ((Bits >> 1U) & 0x3ffU) << 21U | + ((Bits >> 11U) & 1U) << 20U | ((Bits >> 12U) & 0xffU) << 12U; +} + +[[nodiscard]] std::uint16_t encodeRVCJumpImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint16_t>(Value); + return static_cast<std::uint16_t>(((Bits >> 11U) & 1U) << 12U | ((Bits >> 4U) & 1U) << 11U | + ((Bits >> 8U) & 3U) << 9U | ((Bits >> 10U) & 1U) << 8U | + ((Bits >> 6U) & 1U) << 7U | ((Bits >> 7U) & 1U) << 6U | + ((Bits >> 1U) & 7U) << 3U | ((Bits >> 5U) & 1U) << 2U); +} + +[[nodiscard]] std::uint16_t encodeRVCBranchImmediate(std::int64_t Value) noexcept { + const auto Bits = static_cast<std::uint16_t>(Value); + return static_cast<std::uint16_t>(((Bits >> 8U) & 1U) << 12U | ((Bits >> 3U) & 3U) << 10U | + ((Bits >> 6U) & 3U) << 5U | ((Bits >> 1U) & 3U) << 3U | + ((Bits >> 5U) & 1U) << 2U); +} + +[[nodiscard]] Expected<std::uint32_t> encodeRType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly) { + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + std::size_t RegisterCount = 0; + bool HasRoundingMode = false; + switch (Format) { + case AssemblyFormat::RD_RS1_RS2: + case AssemblyFormat::FRD_FRS1_FRS2: + case AssemblyFormat::RD_FRS1_FRS2: + RegisterCount = 3; + break; + case AssemblyFormat::FRD_FRS1_FRS2_RM: + RegisterCount = 3; + HasRoundingMode = true; + break; + case AssemblyFormat::FRD_FRS1_RM: + case AssemblyFormat::RD_FRS1_RM: + case AssemblyFormat::FRD_RS1_RM: + RegisterCount = 2; + HasRoundingMode = true; + break; + case AssemblyFormat::RD_FRS1: + case AssemblyFormat::FRD_RS1: + case AssemblyFormat::RD_RS1: + RegisterCount = 2; + break; + default: + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported RISC-V R-type assembly format"); + } + auto Count = requireOperandCount(Instruction, RegisterCount + (HasRoundingMode ? 1U : 0U)); + if (!Count) + return std::unexpected(Count.error()); + + std::uint32_t Encoding = Assembly.Encoding & ~std::uint32_t{0x000f8f80}; + auto Destination = registerEncoding(Instruction, 0, Target); + auto Source1 = registerEncoding(Instruction, 1, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source1) + return std::unexpected(Source1.error()); + Encoding |= *Destination << 7U | *Source1 << 15U; + if (RegisterCount == 3) { + auto Source2 = registerEncoding(Instruction, 2, Target); + if (!Source2) + return std::unexpected(Source2.error()); + Encoding = (Encoding & ~std::uint32_t{0x01f00000}) | *Source2 << 20U; + } + return Encoding; +} + +[[nodiscard]] Expected<std::uint32_t> encodeRTypeWithResult(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Encoding = encodeRType(Instruction, Target, Assembly); + if (!Encoding) + return std::unexpected(Encoding.error()); + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + std::size_t ModeIndex = 0; + switch (Format) { + case AssemblyFormat::FRD_FRS1_FRS2_RM: + ModeIndex = 3; + break; + case AssemblyFormat::FRD_FRS1_RM: + case AssemblyFormat::RD_FRS1_RM: + case AssemblyFormat::FRD_RS1_RM: + ModeIndex = 2; + break; + default: + return *Encoding; + } + auto Mode = immediateOrFixup(Instruction, ModeIndex, mc::fixup_kind::None, false, Result); + if (!Mode) + return std::unexpected(Mode.error()); + auto Range = requireRoundingMode(*Mode); + if (!Range) + return std::unexpected(Range.error()); + return (*Encoding & ~std::uint32_t{0x7000}) | static_cast<std::uint32_t>(*Mode) << 12U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeIType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + if (Format == AssemblyFormat::NONE) { + auto Count = requireOperandCount(Instruction, 0); + if (!Count) + return std::unexpected(Count.error()); + return Assembly.Encoding; + } + if (Format == AssemblyFormat::IMM_IMM) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Predecessor = immediateOrFixup(Instruction, 0, mc::fixup_kind::None, false, Result); + auto Successor = immediateOrFixup(Instruction, 1, mc::fixup_kind::None, false, Result); + if (!Predecessor) + return std::unexpected(Predecessor.error()); + if (!Successor) + return std::unexpected(Successor.error()); + auto PredRange = requireUnsigned(*Predecessor, 4, "RISC-V fence predecessor"); + auto SuccRange = requireUnsigned(*Successor, 4, "RISC-V fence successor"); + if (!PredRange) + return std::unexpected(PredRange.error()); + if (!SuccRange) + return std::unexpected(SuccRange.error()); + return (Assembly.Encoding & ~std::uint32_t{0x0ff00000}) | + static_cast<std::uint32_t>(*Predecessor) << 24U | + static_cast<std::uint32_t>(*Successor) << 20U; + } + if (Format == AssemblyFormat::RD_RS1) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + auto Source = registerEncoding(Instruction, 1, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source) + return std::unexpected(Source.error()); + return (Assembly.Encoding & ~std::uint32_t{0x000f8f80}) | *Destination << 7U | *Source << 15U; + } + if (Format != AssemblyFormat::RD_RS1_IMM && Format != AssemblyFormat::RD_OFFSET_RS1 && + Format != AssemblyFormat::FRD_OFFSET_RS1) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported RISC-V I-type assembly format"); + } + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + auto Source = registerEncoding(Instruction, 1, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source) + return std::unexpected(Source.error()); + std::uint32_t Encoding = + (Assembly.Encoding & ~std::uint32_t{0x000f8f80}) | *Destination << 7U | *Source << 15U; + + const auto Opcode = static_cast<InstructionOpcode>(Instruction.opcode().value()); + const bool Shift64 = Opcode == InstructionOpcode::SLLI || Opcode == InstructionOpcode::SLLI_UW || + Opcode == InstructionOpcode::SRLI || Opcode == InstructionOpcode::SRAI; + const bool Shift32 = Opcode == InstructionOpcode::SLLIW || Opcode == InstructionOpcode::SRLIW || + Opcode == InstructionOpcode::SRAIW; + if (Shift64 || Shift32) { + auto Immediate = immediateOrFixup(Instruction, 2, mc::fixup_kind::None, false, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + auto Range = requireUnsigned(*Immediate, Shift64 ? 6U : 5U, "RISC-V shift"); + if (!Range) + return std::unexpected(Range.error()); + const std::uint32_t Mask = Shift64 ? 0x03f00000U : 0x01f00000U; + return (Encoding & ~Mask) | static_cast<std::uint32_t>(*Immediate) << 20U; + } + + auto Kind = lowFixupKind(Instruction.operands()[2], false); + if (!Kind) + return std::unexpected(Kind.error()); + auto Immediate = immediateOrFixup(Instruction, 2, *Kind, + *Kind == fixupKind(RISCVFixupKind::PCRelativeLow12I), Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[2].expressionValue() == nullptr) { + auto Range = requireSigned(*Immediate, 12, "RISC-V I-type"); + if (!Range) + return std::unexpected(Range.error()); + } + return (Encoding & ~std::uint32_t{0xfff00000}) | (static_cast<std::uint32_t>(*Immediate) & 0xfffU) + << 20U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeSType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto Source2 = registerEncoding(Instruction, 0, Target); + auto Source1 = registerEncoding(Instruction, 1, Target); + if (!Source2) + return std::unexpected(Source2.error()); + if (!Source1) + return std::unexpected(Source1.error()); + auto Kind = lowFixupKind(Instruction.operands()[2], true); + if (!Kind) + return std::unexpected(Kind.error()); + auto Immediate = immediateOrFixup(Instruction, 2, *Kind, + *Kind == fixupKind(RISCVFixupKind::PCRelativeLow12S), Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[2].expressionValue() == nullptr) { + auto Range = requireSigned(*Immediate, 12, "RISC-V S-type"); + if (!Range) + return std::unexpected(Range.error()); + } + const std::uint32_t Bits = static_cast<std::uint32_t>(*Immediate) & 0xfffU; + return (Assembly.Encoding & ~std::uint32_t{0xffff8f80}) | *Source1 << 15U | *Source2 << 20U | + ((Bits >> 5U) & 0x7fU) << 25U | (Bits & 0x1fU) << 7U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeBType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto Source1 = registerEncoding(Instruction, 0, Target); + auto Source2 = registerEncoding(Instruction, 1, Target); + if (!Source1) + return std::unexpected(Source1.error()); + if (!Source2) + return std::unexpected(Source2.error()); + auto Plain = requirePlainExpression(Instruction.operands()[2], "RISC-V branch"); + if (!Plain) + return std::unexpected(Plain.error()); + auto Immediate = + immediateOrFixup(Instruction, 2, fixupKind(RISCVFixupKind::Branch), true, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[2].expressionValue() == nullptr && + (!fitsSigned(*Immediate, 13) || (*Immediate & 1) != 0)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V branch displacement is out of range or unaligned"); + } + return (Assembly.Encoding & ~std::uint32_t{0xffff8f80}) | *Source1 << 15U | *Source2 << 20U | + encodeBranchImmediate(*Immediate); +} + +[[nodiscard]] Expected<std::uint32_t> encodeUType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + if (!Destination) + return std::unexpected(Destination.error()); + auto Kind = uFixupKind(Instruction.operands()[1]); + if (!Kind) + return std::unexpected(Kind.error()); + const bool PCRelative = *Kind == fixupKind(RISCVFixupKind::PCRelativeHigh20) || + *Kind == fixupKind(RISCVFixupKind::GOTPCRelativeHigh20) || + *Kind == fixupKind(RISCVFixupKind::CallPLT); + auto Immediate = immediateOrFixup(Instruction, 1, *Kind, PCRelative, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[1].expressionValue() == nullptr) { + auto Range = requireSigned(*Immediate, 20, "RISC-V U-type"); + if (!Range) + return std::unexpected(Range.error()); + } + return (Assembly.Encoding & 0x7fU) | *Destination << 7U | + (static_cast<std::uint32_t>(*Immediate) & 0xfffffU) << 12U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeJType(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + if (!Destination) + return std::unexpected(Destination.error()); + auto Plain = requirePlainExpression(Instruction.operands()[1], "RISC-V JAL"); + if (!Plain) + return std::unexpected(Plain.error()); + auto Immediate = immediateOrFixup(Instruction, 1, fixupKind(RISCVFixupKind::JAL), true, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (Instruction.operands()[1].expressionValue() == nullptr && + (!fitsSigned(*Immediate, 21) || (*Immediate & 1) != 0)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V JAL displacement is out of range or unaligned"); + } + return (Assembly.Encoding & 0x7fU) | *Destination << 7U | encodeJALImmediate(*Immediate); +} + +[[nodiscard]] Expected<std::uint32_t> encodeSystem(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + if (Format == AssemblyFormat::NONE) { + auto Count = requireOperandCount(Instruction, 0); + if (!Count) + return std::unexpected(Count.error()); + return Assembly.Encoding; + } + if (Format != AssemblyFormat::RD_CSR_RS1 && Format != AssemblyFormat::RD_CSR_IMM) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported RISC-V system assembly format"); + } + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + if (!Destination) + return std::unexpected(Destination.error()); + auto CSR = immediateOrFixup(Instruction, 1, mc::fixup_kind::None, false, Result); + if (!CSR) + return std::unexpected(CSR.error()); + auto CSRRange = requireUnsigned(*CSR, 12, "RISC-V CSR"); + if (!CSRRange) + return std::unexpected(CSRRange.error()); + std::uint32_t Source = 0; + if (Format == AssemblyFormat::RD_CSR_RS1) { + auto Register = registerEncoding(Instruction, 2, Target); + if (!Register) + return std::unexpected(Register.error()); + Source = *Register; + } else { + auto Immediate = immediateOrFixup(Instruction, 2, mc::fixup_kind::None, false, Result); + if (!Immediate) + return std::unexpected(Immediate.error()); + auto Range = requireUnsigned(*Immediate, 5, "RISC-V CSR immediate"); + if (!Range) + return std::unexpected(Range.error()); + Source = static_cast<std::uint32_t>(*Immediate); + } + return (Assembly.Encoding & ~std::uint32_t{0xffff8f80}) | *Destination << 7U | Source << 15U | + static_cast<std::uint32_t>(*CSR) << 20U; +} + +[[nodiscard]] Expected<std::uint32_t> encodeAMO(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly) { + const auto Format = static_cast<AssemblyFormat>(Assembly.Format.value()); + const bool IsLoadReserved = Format == AssemblyFormat::RD_PAREN_RS1; + if (!IsLoadReserved && Format != AssemblyFormat::RD_RS2_PAREN_RS1) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported RISC-V atomic assembly format"); + } + auto Count = requireOperandCount(Instruction, IsLoadReserved ? 2U : 3U); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + auto Source1 = registerEncoding(Instruction, IsLoadReserved ? 1U : 2U, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source1) + return std::unexpected(Source1.error()); + std::uint32_t Encoding = + (Assembly.Encoding & ~std::uint32_t{0x000f8f80}) | *Destination << 7U | *Source1 << 15U; + if (!IsLoadReserved) { + auto Source2 = registerEncoding(Instruction, 1, Target); + if (!Source2) + return std::unexpected(Source2.error()); + Encoding = (Encoding & ~std::uint32_t{0x01f00000}) | *Source2 << 20U; + } + return Encoding; +} + +[[nodiscard]] Expected<std::uint32_t> encodeR4(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + auto Count = requireOperandCount(Instruction, 5); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + auto Source1 = registerEncoding(Instruction, 1, Target); + auto Source2 = registerEncoding(Instruction, 2, Target); + auto Source3 = registerEncoding(Instruction, 3, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source1) + return std::unexpected(Source1.error()); + if (!Source2) + return std::unexpected(Source2.error()); + if (!Source3) + return std::unexpected(Source3.error()); + auto Mode = immediateOrFixup(Instruction, 4, mc::fixup_kind::None, false, Result); + if (!Mode) + return std::unexpected(Mode.error()); + auto Range = requireRoundingMode(*Mode); + if (!Range) + return std::unexpected(Range.error()); + return (Assembly.Encoding & ~std::uint32_t{0xf9ffff80}) | *Destination << 7U | + static_cast<std::uint32_t>(*Mode) << 12U | *Source1 << 15U | *Source2 << 20U | + *Source3 << 27U; +} + +[[nodiscard]] Expected<std::uint16_t> encodeCompressed(const mc::MCInst &Instruction, + const TargetDescription &Target, + const AssemblyInstructionInfo &Assembly, + mc::MCEncodedInstruction &Result) { + const auto Opcode = static_cast<InstructionOpcode>(Instruction.opcode().value()); + std::uint16_t Encoding = static_cast<std::uint16_t>(Assembly.Encoding); + const auto Immediate = [&](std::size_t Index, mc::MCFixupKind Kind = mc::fixup_kind::None, + bool PCRelative = false) { + return immediateOrFixup(Instruction, Index, Kind, PCRelative, Result); + }; + + if (Opcode == InstructionOpcode::C_ADDI4SPN) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = compactRegisterEncoding(Instruction, 0, Target); + auto Value = Immediate(1); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Value) + return std::unexpected(Value.error()); + if (!fitsUnsigned(*Value, 10) || *Value == 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "C.ADDI4SPN immediate is out of range or zero"); + } + auto Alignment = requireAligned(*Value, 4, "C.ADDI4SPN"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Value); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | + ((Bits >> 4U) & 3U) << 11U | ((Bits >> 6U) & 0xfU) << 7U | + ((Bits >> 2U) & 1U) << 6U | ((Bits >> 3U) & 1U) << 5U | + *Destination << 2U); + } + + const bool CompactDoubleLoad = + Opcode == InstructionOpcode::C_FLD || Opcode == InstructionOpcode::C_LD; + const bool CompactWordLoad = Opcode == InstructionOpcode::C_LW; + const bool CompactDoubleStore = + Opcode == InstructionOpcode::C_FSD || Opcode == InstructionOpcode::C_SD; + const bool CompactWordStore = Opcode == InstructionOpcode::C_SW; + if (CompactDoubleLoad || CompactWordLoad || CompactDoubleStore || CompactWordStore) { + auto Count = requireOperandCount(Instruction, 3); + if (!Count) + return std::unexpected(Count.error()); + auto ValueRegister = compactRegisterEncoding(Instruction, 0, Target); + auto Base = compactRegisterEncoding(Instruction, 1, Target); + auto Offset = Immediate(2); + if (!ValueRegister) + return std::unexpected(ValueRegister.error()); + if (!Base) + return std::unexpected(Base.error()); + if (!Offset) + return std::unexpected(Offset.error()); + const bool IsDouble = CompactDoubleLoad || CompactDoubleStore; + auto Range = requireUnsigned(*Offset, IsDouble ? 8U : 7U, "compressed load/store"); + if (!Range) + return std::unexpected(Range.error()); + auto Alignment = requireAligned(*Offset, IsDouble ? 8U : 4U, "compressed load/store"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Offset); + std::uint16_t ImmediateBits = static_cast<std::uint16_t>(((Bits >> 3U) & 7U) << 10U); + ImmediateBits |= + IsDouble + ? static_cast<std::uint16_t>(((Bits >> 6U) & 3U) << 5U) + : static_cast<std::uint16_t>(((Bits >> 2U) & 1U) << 6U | ((Bits >> 6U) & 1U) << 5U); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | ImmediateBits | + *Base << 7U | *ValueRegister << 2U); + } + + const bool CI = Opcode == InstructionOpcode::C_ADDI || Opcode == InstructionOpcode::C_ADDIW || + Opcode == InstructionOpcode::C_LI || Opcode == InstructionOpcode::C_LUI || + Opcode == InstructionOpcode::C_SLLI; + if (CI) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Register = registerEncoding(Instruction, 0, Target); + auto Value = Immediate(1); + if (!Register) + return std::unexpected(Register.error()); + if (!Value) + return std::unexpected(Value.error()); + const bool IsShift = Opcode == InstructionOpcode::C_SLLI; + auto Range = + IsShift ? requireUnsigned(*Value, 6, "C.SLLI") : requireSigned(*Value, 6, "compressed CI"); + if (!Range) + return std::unexpected(Range.error()); + if (Opcode == InstructionOpcode::C_LUI && *Value == 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "C.LUI immediate must be nonzero"); + } + const auto Bits = static_cast<std::uint16_t>(*Value); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | + ((Bits >> 5U) & 1U) << 12U | *Register << 7U | + (Bits & 0x1fU) << 2U); + } + + if (Opcode == InstructionOpcode::C_ADDI16SP) { + auto Count = requireOperandCount(Instruction, 1); + if (!Count) + return std::unexpected(Count.error()); + auto Value = Immediate(0); + if (!Value) + return std::unexpected(Value.error()); + if (!fitsSigned(*Value, 10) || *Value == 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "C.ADDI16SP immediate is out of range or zero"); + } + auto Alignment = requireAligned(*Value, 16, "C.ADDI16SP"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Value); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x107c}) | + ((Bits >> 9U) & 1U) << 12U | ((Bits >> 4U) & 1U) << 6U | + ((Bits >> 6U) & 1U) << 5U | ((Bits >> 7U) & 3U) << 3U | + ((Bits >> 5U) & 1U) << 2U); + } + + const bool CompactShift = + Opcode == InstructionOpcode::C_SRLI || Opcode == InstructionOpcode::C_SRAI; + if (CompactShift || Opcode == InstructionOpcode::C_ANDI) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Register = compactRegisterEncoding(Instruction, 0, Target); + auto Value = Immediate(1); + if (!Register) + return std::unexpected(Register.error()); + if (!Value) + return std::unexpected(Value.error()); + auto Range = CompactShift ? requireUnsigned(*Value, 6, "compressed shift") + : requireSigned(*Value, 6, "C.ANDI"); + if (!Range) + return std::unexpected(Range.error()); + const auto Bits = static_cast<std::uint16_t>(*Value); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x13fc}) | + ((Bits >> 5U) & 1U) << 12U | *Register << 7U | + (Bits & 0x1fU) << 2U); + } + + const bool CompactALU = + Opcode == InstructionOpcode::C_SUB || Opcode == InstructionOpcode::C_XOR || + Opcode == InstructionOpcode::C_OR || Opcode == InstructionOpcode::C_AND || + Opcode == InstructionOpcode::C_SUBW || Opcode == InstructionOpcode::C_ADDW; + if (CompactALU) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = compactRegisterEncoding(Instruction, 0, Target); + auto Source = compactRegisterEncoding(Instruction, 1, Target); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Source) + return std::unexpected(Source.error()); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x039c}) | *Destination << 7U | + *Source << 2U); + } + + if (Opcode == InstructionOpcode::C_J) { + auto Count = requireOperandCount(Instruction, 1); + if (!Count) + return std::unexpected(Count.error()); + auto Plain = requirePlainExpression(Instruction.operands()[0], "C.J"); + if (!Plain) + return std::unexpected(Plain.error()); + auto Value = Immediate(0, fixupKind(RISCVFixupKind::RVCJump), true); + if (!Value) + return std::unexpected(Value.error()); + if (Instruction.operands()[0].expressionValue() == nullptr && + (!fitsSigned(*Value, 12) || (*Value & 1) != 0)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "C.J displacement is out of range or unaligned"); + } + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | + encodeRVCJumpImmediate(*Value)); + } + + if (Opcode == InstructionOpcode::C_BEQZ || Opcode == InstructionOpcode::C_BNEZ) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Source = compactRegisterEncoding(Instruction, 0, Target); + if (!Source) + return std::unexpected(Source.error()); + auto Plain = requirePlainExpression(Instruction.operands()[1], "compressed branch"); + if (!Plain) + return std::unexpected(Plain.error()); + auto Value = Immediate(1, fixupKind(RISCVFixupKind::RVCBranch), true); + if (!Value) + return std::unexpected(Value.error()); + if (Instruction.operands()[1].expressionValue() == nullptr && + (!fitsSigned(*Value, 9) || (*Value & 1) != 0)) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "compressed branch displacement is out of range or unaligned"); + } + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | *Source << 7U | + encodeRVCBranchImmediate(*Value)); + } + + const bool StackDoubleLoad = + Opcode == InstructionOpcode::C_FLDSP || Opcode == InstructionOpcode::C_LDSP; + const bool StackWordLoad = Opcode == InstructionOpcode::C_LWSP; + if (StackDoubleLoad || StackWordLoad) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Destination = registerEncoding(Instruction, 0, Target); + auto Offset = Immediate(1); + if (!Destination) + return std::unexpected(Destination.error()); + if (!Offset) + return std::unexpected(Offset.error()); + auto Range = requireUnsigned(*Offset, StackDoubleLoad ? 9U : 8U, "stack load"); + if (!Range) + return std::unexpected(Range.error()); + auto Alignment = requireAligned(*Offset, StackDoubleLoad ? 8U : 4U, "stack load"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Offset); + const std::uint16_t ImmediateBits = + StackDoubleLoad + ? static_cast<std::uint16_t>(((Bits >> 5U) & 1U) << 12U | ((Bits >> 3U) & 3U) << 5U | + ((Bits >> 6U) & 7U) << 2U) + : static_cast<std::uint16_t>(((Bits >> 5U) & 1U) << 12U | ((Bits >> 2U) & 7U) << 4U | + ((Bits >> 6U) & 3U) << 2U); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | ImmediateBits | + *Destination << 7U); + } + + const bool CRSingle = Opcode == InstructionOpcode::C_JR || Opcode == InstructionOpcode::C_JALR; + const bool CRDouble = Opcode == InstructionOpcode::C_MV || Opcode == InstructionOpcode::C_ADD; + if (CRSingle || CRDouble) { + auto Count = requireOperandCount(Instruction, CRSingle ? 1U : 2U); + if (!Count) + return std::unexpected(Count.error()); + auto First = registerEncoding(Instruction, 0, Target); + if (!First) + return std::unexpected(First.error()); + Encoding = static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x0f80}) | *First << 7U); + if (CRDouble) { + auto Second = registerEncoding(Instruction, 1, Target); + if (!Second) + return std::unexpected(Second.error()); + Encoding = static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x007c}) | *Second << 2U); + } + return Encoding; + } + + if (Opcode == InstructionOpcode::C_EBREAK) { + auto Count = requireOperandCount(Instruction, 0); + if (!Count) + return std::unexpected(Count.error()); + return Encoding; + } + + const bool StackDoubleStore = + Opcode == InstructionOpcode::C_FSDSP || Opcode == InstructionOpcode::C_SDSP; + const bool StackWordStore = Opcode == InstructionOpcode::C_SWSP; + if (StackDoubleStore || StackWordStore) { + auto Count = requireOperandCount(Instruction, 2); + if (!Count) + return std::unexpected(Count.error()); + auto Source = registerEncoding(Instruction, 0, Target); + auto Offset = Immediate(1); + if (!Source) + return std::unexpected(Source.error()); + if (!Offset) + return std::unexpected(Offset.error()); + auto Range = requireUnsigned(*Offset, StackDoubleStore ? 9U : 8U, "stack store"); + if (!Range) + return std::unexpected(Range.error()); + auto Alignment = requireAligned(*Offset, StackDoubleStore ? 8U : 4U, "stack store"); + if (!Alignment) + return std::unexpected(Alignment.error()); + const auto Bits = static_cast<std::uint16_t>(*Offset); + const std::uint16_t ImmediateBits = + StackDoubleStore + ? static_cast<std::uint16_t>(((Bits >> 3U) & 7U) << 10U | ((Bits >> 6U) & 7U) << 7U) + : static_cast<std::uint16_t>(((Bits >> 2U) & 0xfU) << 9U | ((Bits >> 6U) & 3U) << 7U); + return static_cast<std::uint16_t>((Encoding & ~std::uint16_t{0x1ffc}) | ImmediateBits | + *Source << 2U); + } + + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "unsupported compressed RISC-V instruction"); +} + +void appendLittleEndian(mc::MCEncodedInstruction &Result, std::uint32_t Encoding, + std::uint8_t Size) { + Result.Bytes.reserve(Size); + for (std::uint8_t Index = 0; Index < Size; ++Index) + Result.Bytes.push_back(static_cast<std::uint8_t>(Encoding >> (Index * 8U))); +} + +} // namespace + +Expected<mc::MCEncodedInstruction> +RISCVMCCodeEmitter::encodeInstruction(const mc::MCInst &Instruction) const { + if (!Instruction.opcode().isValid()) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "cannot encode an invalid RISC-V MC opcode"); + } + const Opcode OpcodeValue = Opcode::fromValue(Instruction.opcode().value()); + const InstructionDescriptor *Descriptor = Target->instructionDescriptor(OpcodeValue); + const AssemblyInstructionInfo *Assembly = assemblyInstruction(OpcodeValue); + if (Descriptor == nullptr || Assembly == nullptr || Assembly->Encoding == 0) { + return makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction has no binary encoding description"); + } + auto Registers = validateRegisterOperands(Instruction, *Descriptor, *Target); + if (!Registers) + return std::unexpected(Registers.error()); + + mc::MCEncodedInstruction Result; + const auto Category = static_cast<EncodingCategory>(Descriptor->EncodingCategory.value()); + Expected<std::uint32_t> Encoding = makeUnexpected(ErrorCode::InstructionEncodingFailed, + "RISC-V instruction has no encoding category"); + switch (Category) { + case EncodingCategory::R: + Encoding = encodeRTypeWithResult(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::I: + Encoding = encodeIType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::S: + Encoding = encodeSType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::B: + Encoding = encodeBType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::U: + Encoding = encodeUType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::J: + Encoding = encodeJType(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::SYSTEM: + Encoding = encodeSystem(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::AMO: + Encoding = encodeAMO(Instruction, *Target, *Assembly); + break; + case EncodingCategory::R4: + Encoding = encodeR4(Instruction, *Target, *Assembly, Result); + break; + case EncodingCategory::C: { + auto Compressed = encodeCompressed(Instruction, *Target, *Assembly, Result); + if (!Compressed) + return std::unexpected(Compressed.error()); + appendLittleEndian(Result, *Compressed, 2); + return Result; + } + case EncodingCategory::ZCMP_MOVE: { + auto ZcmpMove = encodeZcmpMove(Instruction, *Target, *Assembly); + if (!ZcmpMove) + return std::unexpected(ZcmpMove.error()); + appendLittleEndian(Result, *ZcmpMove, 2); + return Result; + } + case EncodingCategory::ZCMP_STACK: { + auto ZcmpStack = encodeZcmpStack(Instruction, *Assembly, Result); + if (!ZcmpStack) + return std::unexpected(ZcmpStack.error()); + appendLittleEndian(Result, *ZcmpStack, 2); + return Result; + } + case EncodingCategory::None: + break; + } + if (!Encoding) + return std::unexpected(Encoding.error()); + appendLittleEndian(Result, *Encoding, 4); + return Result; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVMCPseudoLowering.rccdesc b/backend/lib/Target/RISCV/RISCVMCPseudoLowering.rccdesc new file mode 100644 index 0000000..a505281 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMCPseudoLowering.rccdesc @@ -0,0 +1,35 @@ +mc_pseudo_expansions: + - + pseudo: PseudoBR + opcode: JAL + operands: + - { register: X0 } + - { operand: 0 } + - + pseudo: PseudoBRIND + opcode: JALR + operands: + - { register: X0 } + - { operand: 0 } + - { operand: 1 } + - + pseudo: PseudoRET + opcode: JALR + operands: + - { register: X0 } + - { register: X1 } + - { immediate: 0 } + - + pseudo: ReadFFLAGS + opcode: CSRRS + operands: + - { operand: 0 } + - { immediate: 1 } + - { register: X0 } + - + pseudo: WriteFFLAGS + opcode: CSRRW + operands: + - { register: X0 } + - { immediate: 1 } + - { operand: 0 } diff --git a/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp b/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp new file mode 100644 index 0000000..065bd36 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp @@ -0,0 +1,334 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVMCTargetLowering.h" + +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMCContext.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/Register.h" +#include "rcc/IR/Constants.h" +#include "rcc/MC/MCExpr.h" +#include "rcc/MC/MCInst.h" +#include "rcc/MC/MCStreamer.h" +#include "rcc/MC/MCSymbol.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <bit> +#include <cstddef> +#include <cstdint> +#include <optional> +#include <ranges> +#include <string_view> +#include <utility> + +namespace rcc::riscv { +namespace { + +constexpr std::uint32_t CallTargetFlag = 1; +constexpr std::uint32_t PCRelativeHighTargetFlag = 2; +constexpr std::uint32_t PCRelativeLowTargetFlag = 4; +constexpr std::uint32_t CopyF32TargetFlag = 8; +constexpr std::uint32_t CopyF64TargetFlag = 16; +constexpr std::uint32_t AbsoluteHighTargetFlag = 32; +constexpr std::uint32_t AbsoluteLowTargetFlag = 64; +constexpr std::uint32_t GOTPCRelativeHighTargetFlag = 128; +constexpr std::uint32_t KnownTargetFlags = CallTargetFlag | PCRelativeHighTargetFlag | + PCRelativeLowTargetFlag | CopyF32TargetFlag | + CopyF64TargetFlag | AbsoluteHighTargetFlag | + AbsoluteLowTargetFlag | GOTPCRelativeHighTargetFlag; + +[[nodiscard]] Expected<mc::MCExprRef> addOffset(mc::MCExprRef Expression, std::int64_t Offset) { + if (Offset == 0) + return Expression; + auto Result = mc::MCExpr::binary(mc::MCBinaryOperator::Add, std::move(Expression), + mc::MCExpr::constant(Offset)); + if (!Result) + return std::unexpected(Result.error()); + return *Result; +} + +[[nodiscard]] Expected<mc::MCExprRef> applyTargetFlags(mc::MCExprRef Expression, + std::uint32_t Flags) { + if ((Flags & ~KnownTargetFlags) != 0 || (Flags & (CopyF32TargetFlag | CopyF64TargetFlag)) != 0) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V operand has target flags that cannot survive MC lowering"); + } + if (Flags == 0) + return Expression; + + RISCVMCExprKind Kind; + switch (Flags) { + case CallTargetFlag: + Kind = RISCVMCExprKind::CallPLT; + break; + case PCRelativeHighTargetFlag: + Kind = RISCVMCExprKind::PCRelativeHigh; + break; + case PCRelativeLowTargetFlag: + Kind = RISCVMCExprKind::PCRelativeLow; + break; + case AbsoluteHighTargetFlag: + Kind = RISCVMCExprKind::AbsoluteHigh; + break; + case AbsoluteLowTargetFlag: + Kind = RISCVMCExprKind::AbsoluteLow; + break; + case GOTPCRelativeHighTargetFlag: + Kind = RISCVMCExprKind::GOTPCRelativeHigh; + break; + default: + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V operand combines incompatible direct target flags"); + } + auto TargetExpression = + mc::MCExpr::target(static_cast<std::uint32_t>(Kind), std::move(Expression)); + if (!TargetExpression) + return std::unexpected(TargetExpression.error()); + return *TargetExpression; +} + +[[nodiscard]] Expected<mc::MCOperand> symbolOperand(mc::MCSymbolRef Symbol, std::int64_t Offset, + std::uint32_t TargetFlags) { + auto Expression = mc::MCExpr::symbol(std::move(Symbol)); + if (!Expression) + return std::unexpected(Expression.error()); + auto WithOffset = addOffset(std::move(*Expression), Offset); + if (!WithOffset) + return std::unexpected(WithOffset.error()); + auto TargetExpression = applyTargetFlags(std::move(*WithOffset), TargetFlags); + if (!TargetExpression) + return std::unexpected(TargetExpression.error()); + return mc::MCOperand::expression(std::move(*TargetExpression)); +} + +[[nodiscard]] Expected<std::int64_t> lowerIntegerImmediate(const APInt &Value) { + if (Value.bitWidth() > 64 || Value.words().size() != 1) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V instruction has an integer immediate wider than 64 bits"); + } + const std::uint64_t Raw = Value.words().front(); + if (Value.bitWidth() == 64) + return std::bit_cast<std::int64_t>(Raw); + if (!Value.isNegative()) + return static_cast<std::int64_t>(Raw); + const std::uint64_t Mask = (std::uint64_t{1} << Value.bitWidth()) - 1U; + return std::bit_cast<std::int64_t>(Raw | ~Mask); +} + +[[nodiscard]] Expected<mc::MCOperand> lowerOperand(const MachineOperand &Operand, + const MachineFunction &Function, + MachineMCContext &Context) { + if (const auto *RegisterValue = Operand.getIf<Register>()) { + const auto Physical = RegisterValue->physicalRegister(); + if (!Physical || Operand.subReg().value() != 0) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V MC lowering requires a resolved physical register"); + } + if (Operand.targetFlags() != 0) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V register operand retains target flags at MC lowering"); + } + return mc::MCOperand::registerOperand(mc::MCRegister::fromValue(Physical->number())); + } + if (const auto *Immediate = Operand.getIf<std::int64_t>()) { + if (Operand.targetFlags() != 0) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V immediate operand has symbol-only target flags"); + } + return mc::MCOperand::immediate(*Immediate); + } + if (const auto *Immediate = Operand.getIf<IntegerImmediateOperand>()) { + if (Operand.targetFlags() != 0) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V integer immediate has symbol-only target flags"); + } + auto Lowered = lowerIntegerImmediate(Immediate->Value); + if (!Lowered) + return std::unexpected(Lowered.error()); + return mc::MCOperand::immediate(*Lowered); + } + if (const auto *Immediate = Operand.getIf<FloatingImmediateOperand>()) { + if (Operand.targetFlags() != 0 || Immediate->Bits.words().size() != 1) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V floating immediate cannot be represented in MCInst"); + } + if (Immediate->Bits.bitWidth() == 32) + return mc::MCOperand::float32Bits(static_cast<std::uint32_t>(Immediate->Bits.words()[0])); + if (Immediate->Bits.bitWidth() == 64) + return mc::MCOperand::float64Bits(Immediate->Bits.words()[0]); + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V MC lowering supports only 32-bit and 64-bit FP immediates"); + } + if (const auto *Block = Operand.getIf<BasicBlockOperand>()) { + if (Block->Block == nullptr) + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, "RISC-V block operand is null"); + const mc::MCSymbolRef Symbol = Context.blockSymbol(*Block->Block); + if (!Symbol) + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V block operand has no MC symbol"); + return symbolOperand(Symbol, 0, Operand.targetFlags()); + } + if (const auto *Pool = Operand.getIf<ConstantPoolOperand>()) { + const mc::MCSymbolRef Symbol = Context.constantPoolSymbol(Function, Pool->Index); + if (!Symbol) + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V constant-pool operand has no MC symbol"); + return symbolOperand(Symbol, Pool->Offset, Operand.targetFlags()); + } + if (const auto *Table = Operand.getIf<JumpTableOperand>()) { + const mc::MCSymbolRef Symbol = Context.jumpTableSymbol(Function, Table->Index); + if (!Symbol) + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V jump-table operand has no MC symbol"); + return symbolOperand(Symbol, 0, Operand.targetFlags()); + } + if (const auto *Global = Operand.getIf<GlobalSymbolOperand>()) { + auto Symbol = Context.symbol(Global->Name, mc::MCSymbolKind::Object); + if (!Symbol) + return std::unexpected(Symbol.error()); + return symbolOperand(std::move(*Symbol), Global->Offset, Operand.targetFlags()); + } + if (const auto *External = Operand.getIf<ExternalSymbolOperand>()) { + auto Symbol = Context.symbol(External->Name); + if (!Symbol) + return std::unexpected(Symbol.error()); + return symbolOperand(std::move(*Symbol), External->Offset, Operand.targetFlags()); + } + if (const auto *Address = Operand.getIf<BlockAddressOperand>()) { + auto Symbol = Context.blockAddressSymbol(Address->FunctionName, Address->BlockName); + if (!Symbol) + return std::unexpected(Symbol.error()); + return symbolOperand(std::move(*Symbol), Address->Offset, Operand.targetFlags()); + } + if (const auto *SymbolValue = Operand.getIf<MCSymbolOperand>()) { + auto Symbol = Context.symbol(SymbolValue->Name); + if (!Symbol) + return std::unexpected(Symbol.error()); + return symbolOperand(std::move(*Symbol), SymbolValue->Offset, Operand.targetFlags()); + } + if (Operand.kind() == MachineOperand::Kind::FrameIndex) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V MC lowering encountered an unresolved frame index"); + } + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V MC lowering encountered a non-encoding operand"); +} + +#define GET_MC_PSEUDO_LOWERING +#include "RISCVGenMCPseudoLowering.inc" +#undef GET_MC_PSEUDO_LOWERING + +[[nodiscard]] const AssemblyFormatInfo *assemblyFormat(AssemblyFormatID ID) noexcept { + const auto Formats = assemblyFormats(); + const auto Position = std::ranges::find(Formats, ID, &AssemblyFormatInfo::ID); + return Position == Formats.end() ? nullptr : &*Position; +} + +[[nodiscard]] std::optional<std::size_t> formatOperandCount(std::string_view Name) noexcept { + if (Name == "none") + return 0; + if (Name == "rs1" || Name == "target" || Name == "imm-sp") + return 1; + if (Name == "rd-imm" || Name == "rd-target" || Name == "rd-frs1" || Name == "frd-rs1" || + Name == "rs1-target" || Name == "rd-rs2" || Name == "rd-paren-rs1" || Name == "rd-imm-sp" || + Name == "rd-offset-sp" || Name == "rs2-offset-sp" || Name == "frd-offset-sp" || + Name == "frs2-offset-sp") { + return 2; + } + if (Name == "rd-rs1-imm" || Name == "rd-rs1-rs2" || Name == "rd-offset-rs1" || + Name == "rs2-offset-rs1" || Name == "rs1-rs2-target" || Name == "imm-imm" || + Name == "rd-csr-rs1" || Name == "rd-csr-imm" || Name == "rd-rs2-paren-rs1" || + Name == "frd-offset-rs1" || Name == "frs2-offset-rs1" || Name == "frd-frs1-rm" || + Name == "frd-frs1-frs2" || Name == "rd-frs1-rm" || Name == "rd-frs1-frs2" || + Name == "frd-rs1-rm") { + return 3; + } + if (Name == "frd-frs1-frs2-rm") + return 4; + if (Name == "frd-frs1-frs2-frs3-rm") + return 5; + return std::nullopt; +} + +} // namespace + +Expected<mc::MCInst> RISCVMCTargetLowering::lowerInstruction(const MachineInstr &Instruction, + MachineMCContext &Context) const { + const InstructionDescriptor *Descriptor = Target->instructionDescriptor(Instruction.opcode()); + const AssemblyInstructionInfo *Assembly = assemblyInstruction(Instruction.opcode()); + if (Descriptor == nullptr || Assembly == nullptr || Assembly->Mnemonic.empty()) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V instruction has no MC lowering description"); + } + const AssemblyFormatInfo *Format = assemblyFormat(Assembly->Format); + if (Format == nullptr) + return makeUnexpected(ErrorCode::InternalError, "RISC-V assembly format is missing"); + const auto RequiredOperands = formatOperandCount(Format->Name); + if (!RequiredOperands) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V instruction uses an unsupported assembly format"); + } + + mc::MCInst Result(mc::MCOpcode::fromValue(Instruction.opcode().value())); + Result.setFlags(Instruction.flags().rawValue()); + Result.setSourceIdentity(Instruction.stableID().value()); + Result.reserveOperands(*RequiredOperands); + for (const MachineOperand &Operand : Instruction.operands()) { + if (Result.operands().size() == *RequiredOperands) + break; + if (Operand.isRegister() && Operand.isImplicit()) + continue; + if (Operand.kind() == MachineOperand::Kind::RegisterMask || + Operand.kind() == MachineOperand::Kind::LiveOutRegisterMask) { + continue; + } + auto Lowered = lowerOperand(Operand, Instruction.function(), Context); + if (!Lowered) + return std::unexpected(Lowered.error()); + Result.addOperand(std::move(*Lowered)); + } + if (Result.operands().size() != *RequiredOperands) { + return makeUnexpected(ErrorCode::AssemblyLoweringFailed, + "RISC-V instruction has too few encoding operands"); + } + return Result; +} + +Expected<mc::MCExprRef> +RISCVMCTargetLowering::lowerSymbolExpression(mc::MCExprRef Expression, + std::uint32_t TargetFlags) const { + return applyTargetFlags(std::move(Expression), TargetFlags); +} + +Expected<void> RISCVMCTargetLowering::emitTerminalInstruction(const MachineInstr &Instruction, + MachineMCContext &Context, + mc::MCStreamer &Streamer) const { + auto Pseudo = lowerGeneratedMCPseudo(Instruction, Context); + if (!Pseudo) + return std::unexpected(Pseudo.error()); + if (*Pseudo) + return Streamer.emitInstruction(**Pseudo); + if (Instruction.opcode() != opcode(GenericOpcode::INLINEASM) && + Instruction.opcode() != opcode(GenericOpcode::INLINEASM_BR)) { + return TargetMCInstLowering::emitTerminalInstruction(Instruction, Context, Streamer); + } + for (const MachineOperand &Operand : Instruction.operands()) { + if (Operand.kind() == MachineOperand::Kind::RegisterMask || + Operand.kind() == MachineOperand::Kind::LiveOutRegisterMask || + (Operand.isRegister() && Operand.isImplicit())) { + continue; + } + const auto *Text = Operand.getIf<MCSymbolOperand>(); + if (Text == nullptr || Text->Offset != 0 || Operand.targetFlags() != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V inline assembly requires a literal MC-symbol text operand"); + } + return Streamer.emitInlineAssembly(Text->Name); + } + return {}; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVMachineCombiner.cpp b/backend/lib/Target/RISCV/RISCVMachineCombiner.cpp new file mode 100644 index 0000000..dbb637e --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMachineCombiner.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <cstdlib> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] MachineCombinerPattern +fusedMultiplyAdd(const char *Name, InstructionOpcode RootOpcode, InstructionOpcode ProducerOpcode, + InstructionOpcode ReplacementOpcode, std::size_t ProducerOperand, + std::size_t AddendOperand) { + return { + .Name = Name, + .RootOpcode = instructionOpcode(RootOpcode), + .ProducerOpcode = instructionOpcode(ProducerOpcode), + .ReplacementOpcode = instructionOpcode(ReplacementOpcode), + .ProducerOperand = ProducerOperand, + .AddendOperand = AddendOperand, + }; +} + +[[nodiscard]] std::vector<MachineCombinerPattern> createPatterns() { + return { + fusedMultiplyAdd("fmadd-s-ax", InstructionOpcode::FADD_S, InstructionOpcode::FMUL_S, + InstructionOpcode::FMADD_S, 1, 2), + fusedMultiplyAdd("fmadd-s-xa", InstructionOpcode::FADD_S, InstructionOpcode::FMUL_S, + InstructionOpcode::FMADD_S, 2, 1), + fusedMultiplyAdd("fmsub-s", InstructionOpcode::FSUB_S, InstructionOpcode::FMUL_S, + InstructionOpcode::FMSUB_S, 1, 2), + fusedMultiplyAdd("fnmsub-s", InstructionOpcode::FSUB_S, InstructionOpcode::FMUL_S, + InstructionOpcode::FNMSUB_S, 2, 1), + fusedMultiplyAdd("fmadd-d-ax", InstructionOpcode::FADD_D, InstructionOpcode::FMUL_D, + InstructionOpcode::FMADD_D, 1, 2), + fusedMultiplyAdd("fmadd-d-xa", InstructionOpcode::FADD_D, InstructionOpcode::FMUL_D, + InstructionOpcode::FMADD_D, 2, 1), + fusedMultiplyAdd("fmsub-d", InstructionOpcode::FSUB_D, InstructionOpcode::FMUL_D, + InstructionOpcode::FMSUB_D, 1, 2), + fusedMultiplyAdd("fnmsub-d", InstructionOpcode::FSUB_D, InstructionOpcode::FMUL_D, + InstructionOpcode::FNMSUB_D, 2, 1), + }; +} + +} // namespace + +const TargetMachineCombinerInfo &machineCombinerInfo() noexcept { + static const Expected<TargetMachineCombinerInfo> Info = + TargetMachineCombinerInfo::create(createPatterns()); + if (!Info) + std::abort(); + return *Info; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVMachineMemoryFolding.cpp b/backend/lib/Target/RISCV/RISCVMachineMemoryFolding.cpp new file mode 100644 index 0000000..ef72fe1 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMachineMemoryFolding.cpp @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/TargetDescription.h" + +#include <cstddef> +#include <optional> + +namespace rcc::riscv { +namespace { + +class RISCVMachineMemoryFoldingInfo final : public TargetMemoryFoldingInfo { +public: + [[nodiscard]] Expected<bool> canFoldMemoryOperand(const MachineInstr &, std::size_t, + const MachineInstr &, const MachineFunction &, + const TargetDescription &) const override { + return false; + } + + [[nodiscard]] Expected<MachineInstr *> + foldMemoryOperand(MachineInstr &, std::size_t, MachineInstr &, MachineFunction &, + const TargetDescription &) const override { + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC has no register instruction form with a folded memory operand"); + } + + [[nodiscard]] Expected<bool> unfoldMemoryOperand(MachineInstr &, MachineFunction &, + const TargetDescription &) const override { + return false; + } + + [[nodiscard]] Expected<std::optional<MemorySize>> + getFoldedMemorySize(const MachineInstr &, const MachineFunction &, + const TargetDescription &) const override { + return std::optional<MemorySize>{}; + } +}; + +} // namespace + +const TargetMemoryFoldingInfo &memoryFoldingInfo() noexcept { + static const RISCVMachineMemoryFoldingInfo Value; + return Value; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVMacroFusion.rccdesc b/backend/lib/Target/RISCV/RISCVMacroFusion.rccdesc new file mode 100644 index 0000000..2c432a2 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMacroFusion.rccdesc @@ -0,0 +1,139 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +macro_fusion_rules: + - + id: TuneLUIADDIFusion + feature: fusion-lui-addi + first_opcodes: [LUI] + second_opcodes: [ADDI, ADDIW] + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneAUIPCADDIFusion + feature: fusion-auipc-addi + first_opcodes: [AUIPC] + second_opcodes: [ADDI] + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneZExtHFusion + feature: fusion-zexth + first_opcodes: [SLLI] + second_opcodes: [SRLI] + immediates: + - { instruction: first, operand: 2, min: 48, max: 48 } + - { instruction: second, operand: 2, min: 48, max: 48 } + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneZExtWFusion + feature: fusion-zextw + first_opcodes: [SLLI] + second_opcodes: [SRLI] + immediates: + - { instruction: first, operand: 2, min: 32, max: 32 } + - { instruction: second, operand: 2, min: 32, max: 32 } + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneShiftedZExtWFusion + feature: fusion-shifted-zextw + first_opcodes: [SLLI] + second_opcodes: [SRLI] + immediates: + - { instruction: first, operand: 2, min: 32, max: 32 } + - { instruction: second, operand: 2, min: 0, max: 31 } + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneLDADDFusion + feature: fusion-ld-add + first_opcodes: [ADD] + second_opcodes: [LD] + immediates: + - { instruction: second, operand: 2, min: 0, max: 0 } + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneADDLoadFusion + feature: fusion-add-load + first_opcodes: [ADD, ADD_UW] + second_opcodes: [LB, LH, LW, LD, LBU, LHU, LWU] + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneAUIPCLoadFusion + feature: fusion-auipc-load + first_opcodes: [AUIPC] + second_opcodes: [LB, LH, LW, LD, LBU, LHU, LWU] + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneLUILoadFusion + feature: fusion-lui-load + first_opcodes: [LUI] + second_opcodes: [LB, LH, LW, LD, LBU, LHU, LWU] + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneBFExtFusion + feature: fusion-bfext + first_opcodes: [SLLI] + second_opcodes: [SRLI] + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneADDILoadFusion + feature: fusion-addi-load + first_opcodes: [ADDI] + second_opcodes: [LB, LH, LW, LD, LBU, LHU, LWU] + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneSHXADDLoadFusion + feature: fusion-shxadd-load + first_opcodes: [SH1ADD, SH2ADD, SH3ADD, SH1ADD_UW, SH2ADD_UW, SH3ADD_UW] + second_opcodes: [LB, LH, LW, LD, LBU, LHU, LWU] + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneFusionLogicRegReg + feature: fusion-logic-reg-reg + first_opcodes: [AND, OR, XOR] + second_opcodes: [AND, OR, XOR] + link: { first_definition: 0, second_use: 1, second_definition: 0, commutable_second_use: 2, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneFusionLogicRegImm + feature: fusion-logic-reg-imm + first_opcodes: [AND, OR, XOR] + second_opcodes: [ANDI, ORI, XORI] + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneFusionLogicImmReg + feature: fusion-logic-imm-reg + first_opcodes: [ANDI, ORI, XORI] + second_opcodes: [AND, OR, XOR] + link: { first_definition: 0, second_use: 1, second_definition: 0, commutable_second_use: 2, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneFusionMulAdd + feature: fusion-mul-add + first_opcodes: [MUL, MULW] + second_opcodes: [ADD, ADDW] + opcode_pairs: + - { first: MUL, second: ADD } + - { first: MULW, second: ADDW } + register_not_equal: + - { instruction: second, left: 0, right: 2 } + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } + - + id: TuneFusionAddMem + feature: fusion-add-mem + first_opcodes: [ADD] + second_opcodes: [LB, LBU, LH, LHU, LW, LWU, LD, FLW, FLD, SB, SH, SW, SD, FSW, FSD] + immediates: + - { instruction: second, operand: 2, min: 0, max: 0 } + register_not_equal: + - { instruction: first, left: 0, right: 1 } + - { instruction: first, left: 0, right: 2 } + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: false, second_destination_tied: false } + - + id: TuneFusionShiftBitExtract + feature: fusion-shift-bit-extract + first_opcodes: [SLLI, SLLIW] + second_opcodes: [SRLI, SRLIW, SRAI, SRAIW] + opcode_pairs: + - { first: SLLI, second: SRLI } + - { first: SLLI, second: SRAI } + - { first: SLLIW, second: SRLIW } + - { first: SLLIW, second: SRAIW } + immediate_relations: + - { kind: less_equal, first_operand: 2, second_operand: 2 } + link: { first_definition: 0, second_use: 1, second_definition: 0, one_non_debug_use: true, second_destination_tied: true } diff --git a/backend/lib/Target/RISCV/RISCVMakeCompressible.cpp b/backend/lib/Target/RISCV/RISCVMakeCompressible.cpp new file mode 100644 index 0000000..e7fef9c --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMakeCompressible.cpp @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineFunctionAttributes.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachinePassManager.h" +#include "rcc/CodeGen/RegisterMask.h" +#include "rcc/CodeGen/RegisterScavenger.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <span> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +struct RegImmPair final { + Register Reg; + std::int64_t Imm = 0; +}; + +[[nodiscard]] constexpr RegisterClassID registerClassID(RegisterClass Value) noexcept { + return RegisterClassID::fromValue(static_cast<std::uint32_t>(Value)); +} + +[[nodiscard]] PhysicalRegister physicalRegister(PhysicalRegisterID Value) noexcept { + const auto Created = PhysicalRegister::create(static_cast<std::uint32_t>(Value)); + return *Created; +} + +[[nodiscard]] bool isOpcode(const MachineInstr &Instruction, InstructionOpcode Value) noexcept { + return Instruction.opcode() == instructionOpcode(Value); +} + +[[nodiscard]] std::optional<PhysicalRegister> +physicalRegisterValue(const MachineOperand *Operand) noexcept { + const auto Reg = Operand == nullptr ? std::nullopt : Operand->registerValue(); + return Reg ? Reg->physicalRegister() : std::nullopt; +} + +[[nodiscard]] bool classContains(RegisterClass Class, Register Reg, + const TargetDescription &Target) noexcept { + const auto Physical = Reg.physicalRegister(); + return Physical && Target.registers().registerClassContains(registerClassID(Class), *Physical); +} + +[[nodiscard]] bool isCompressedRegister(Register Reg, const TargetDescription &Target) noexcept { + return classContains(RegisterClass::GPRC, Reg, Target) || + classContains(RegisterClass::FPR32C, Reg, Target) || + classContains(RegisterClass::FPR64C, Reg, Target); +} + +[[nodiscard]] bool isCompressibleLoad(const MachineInstr &Instruction, + const RISCVSubtarget &Subtarget) noexcept { + if (!Subtarget.features().has(RISCVExtension::C)) + return false; + if (isOpcode(Instruction, InstructionOpcode::LW) || + isOpcode(Instruction, InstructionOpcode::LD)) { + return true; + } + return isOpcode(Instruction, InstructionOpcode::FLD) && + Subtarget.features().has(RISCVExtension::D); +} + +[[nodiscard]] bool isCompressibleStore(const MachineInstr &Instruction, + const RISCVSubtarget &Subtarget) noexcept { + if (!Subtarget.features().has(RISCVExtension::C)) + return false; + if (isOpcode(Instruction, InstructionOpcode::SW) || + isOpcode(Instruction, InstructionOpcode::SD)) { + return true; + } + return isOpcode(Instruction, InstructionOpcode::FSD) && + Subtarget.features().has(RISCVExtension::D); +} + +[[nodiscard]] std::optional<unsigned> loadStoreWidth(const MachineInstr &Instruction) noexcept { + if (isOpcode(Instruction, InstructionOpcode::LW) || + isOpcode(Instruction, InstructionOpcode::SW)) { + return 2U; + } + if (isOpcode(Instruction, InstructionOpcode::LD) || + isOpcode(Instruction, InstructionOpcode::SD) || + isOpcode(Instruction, InstructionOpcode::FLD) || + isOpcode(Instruction, InstructionOpcode::FSD)) { + return 3U; + } + return std::nullopt; +} + +[[nodiscard]] std::int64_t compressedOffsetMask(const MachineInstr &Instruction) noexcept { + const std::optional<unsigned> Width = loadStoreWidth(Instruction); + if (!Width) + return 0; + return static_cast<std::int64_t>(std::uint64_t{0x1f} << *Width); +} + +[[nodiscard]] bool isCompressibleStackOffset(std::int64_t Offset, + const MachineInstr &Instruction) noexcept { + const std::optional<unsigned> Width = loadStoreWidth(Instruction); + if (!Width || Offset < 0) + return false; + const std::uint64_t Value = static_cast<std::uint64_t>(Offset); + const std::uint64_t AlignmentMask = (std::uint64_t{1} << *Width) - 1U; + return (Value & AlignmentMask) == 0 && (Value >> *Width) < 64U; +} + +[[nodiscard]] std::optional<RegImmPair> +regImmPreventingCompression(MachineInstr &Instruction, const RISCVSubtarget &Subtarget, + const TargetDescription &Target) noexcept { + const bool IsLoad = isCompressibleLoad(Instruction, Subtarget); + const bool IsStore = isCompressibleStore(Instruction, Subtarget); + if (!IsLoad && !IsStore) + return std::nullopt; + + MachineOperand *ValueOperand = Instruction.operandAt(0); + MachineOperand *BaseOperand = Instruction.operandAt(1); + MachineOperand *OffsetOperand = Instruction.operandAt(2); + const auto Value = physicalRegisterValue(ValueOperand); + const auto Base = physicalRegisterValue(BaseOperand); + const auto *Offset = OffsetOperand == nullptr ? nullptr : OffsetOperand->getIf<std::int64_t>(); + if (!Value || !Base || Offset == nullptr) + return std::nullopt; + + const std::int64_t Mask = compressedOffsetMask(Instruction); + const std::int64_t BaseAdjustment = *Offset & ~Mask; + const Register BaseRegister = Register::fromPhysical(*Base); + if (*Base == physicalRegister(PhysicalRegisterID::X2)) { + if (!isCompressibleStackOffset(*Offset, Instruction) && BaseAdjustment != 0) + return RegImmPair{BaseRegister, BaseAdjustment}; + return std::nullopt; + } + + const Register ValueRegister = Register::fromPhysical(*Value); + const bool ValueCompressed = isCompressedRegister(ValueRegister, Target); + const bool BaseCompressed = isCompressedRegister(BaseRegister, Target); + if ((!BaseCompressed || BaseAdjustment != 0) && ValueCompressed) + return RegImmPair{BaseRegister, BaseAdjustment}; + if (IsStore && !ValueCompressed && (BaseCompressed || *Value == *Base) && BaseAdjustment == 0) { + return RegImmPair{ValueRegister, 0}; + } + return std::nullopt; +} + +[[nodiscard]] bool maskClobbers(const RegisterMask &Mask, PhysicalRegister Reg, + const TargetDescription &Target) noexcept { + for (const PhysicalRegisterDesc &Description : Target.registers().physicalRegisters()) { + if (Target.registers().regsOverlap(Description.Reg, Reg) && Mask.clobbers(Description.Reg)) + return true; + } + return false; +} + +[[nodiscard]] bool instructionModifies(const MachineInstr &Instruction, Register Reg, + const TargetDescription &Target) noexcept { + const auto Physical = Reg.physicalRegister(); + if (!Physical) + return false; + for (const MachineOperand &Operand : Instruction.operands()) { + if (const auto OperandPhysical = physicalRegisterValue(&Operand); + OperandPhysical && Operand.isDef() && + Target.registers().regsOverlap(*OperandPhysical, *Physical)) { + return true; + } + const auto *Mask = Operand.getIf<RegisterMaskOperand>(); + if (Mask != nullptr && Mask->Mask && maskClobbers(*Mask->Mask, *Physical, Target)) + return true; + } + return false; +} + +[[nodiscard]] bool instructionTouches(const MachineInstr &Instruction, PhysicalRegister Reg, + const TargetDescription &Target) noexcept { + for (const MachineOperand &Operand : Instruction.operands()) { + if (const auto OperandPhysical = physicalRegisterValue(&Operand); + OperandPhysical && Target.registers().regsOverlap(*OperandPhysical, Reg)) { + return true; + } + const auto *Mask = Operand.getIf<RegisterMaskOperand>(); + if (Mask != nullptr && Mask->Mask && maskClobbers(*Mask->Mask, Reg, Target)) + return true; + } + return false; +} + +[[nodiscard]] std::optional<RegisterClass> +compressedClassFor(Register Reg, const TargetDescription &Target) noexcept { + if (classContains(RegisterClass::GPR, Reg, Target)) + return RegisterClass::GPRC; + if (classContains(RegisterClass::FPR32, Reg, Target)) + return RegisterClass::FPR32C; + if (classContains(RegisterClass::FPR64, Reg, Target)) + return RegisterClass::FPR64C; + return std::nullopt; +} + +[[nodiscard]] Expected<std::optional<PhysicalRegister>> +scavengeCompressedRegister(MachineFunction &Function, MachineInstr &First, MachineInstr &Last, + Register OldReg, const TargetDescription &Target) { + const auto Class = compressedClassFor(OldReg, Target); + if (!Class) + return std::optional<PhysicalRegister>{}; + const RegisterClassDesc *Description = Target.registers().registerClass(registerClassID(*Class)); + if (Description == nullptr) + return makeUnexpected(ErrorCode::InternalError, + "RISC-V compressed register class is unavailable"); + + std::vector<PhysicalRegister> Candidates; + for (PhysicalRegister Candidate : Description->AllocationOrder) { + bool Touched = false; + for (MachineInstr *Instruction = &First; Instruction != nullptr; + Instruction = Instruction->next()) { + Touched |= instructionTouches(*Instruction, Candidate, Target); + if (Instruction == &Last) + break; + } + if (!Touched) + Candidates.push_back(Candidate); + } + if (Candidates.empty()) + return std::optional<PhysicalRegister>{}; + + RegisterScavenger Scavenger(Function, Target); + auto Scavenged = Scavenger.scavenge(First, Candidates); + if (!Scavenged) { + if (Scavenged.error().code() == ErrorCode::RegisterAllocationFailed) + return std::optional<PhysicalRegister>{}; + return std::unexpected(Scavenged.error()); + } + if (Scavenged->RequiresSpill) + return std::optional<PhysicalRegister>{}; + return std::optional<PhysicalRegister>(Scavenged->Reg); +} + +[[nodiscard]] Expected<std::optional<PhysicalRegister>> +analyzeCompressibleUses(MachineFunction &Function, MachineInstr &First, RegImmPair RegImm, + std::vector<MachineInstr *> &Instructions, const RISCVSubtarget &Subtarget, + const TargetDescription &Target) { + for (MachineInstr *Instruction = &First; Instruction != nullptr; + Instruction = Instruction->next()) { + const auto Candidate = regImmPreventingCompression(*Instruction, Subtarget, Target); + if (Candidate && Candidate->Reg == RegImm.Reg && Candidate->Imm == RegImm.Imm) + Instructions.push_back(Instruction); + if (instructionModifies(*Instruction, RegImm.Reg, Target)) + break; + } + + if (Instructions.size() <= 1U || (RegImm.Imm != 0 && Instructions.size() < 3U)) + return std::optional<PhysicalRegister>{}; + return scavengeCompressedRegister(Function, First, *Instructions.back(), RegImm.Reg, Target); +} + +[[nodiscard]] Expected<MachineInstr *> +insertRegisterCopy(MachineFunction &Function, MachineInstr &Before, PhysicalRegister Destination, + RegImmPair Source, const TargetDescription &Target) { + MachineIRBuilder Builder(Function, Target); + auto InsertPoint = Builder.setInsertPointBefore(Before); + if (!InsertPoint) + return std::unexpected(InsertPoint.error()); + + InstructionOpcode Opcode = InstructionOpcode::ADDI; + const bool IsGPR = classContains(RegisterClass::GPR, Source.Reg, Target); + if (!IsGPR) { + if (Source.Imm != 0) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V floating-point compressed copy has an offset"); + if (classContains(RegisterClass::FPR32, Source.Reg, Target)) + Opcode = InstructionOpcode::FSGNJ_S; + else if (classContains(RegisterClass::FPR64, Source.Reg, Target)) + Opcode = InstructionOpcode::FSGNJ_D; + else + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V compressed copy has an unsupported register class"); + } else if (Source.Imm < -2048 || Source.Imm > 2047) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V compressed base adjustment is outside ADDI range"); + } + + auto Pending = Builder.buildInstr(instructionOpcode(Opcode)); + if (!Pending) + return std::unexpected(Pending.error()); + auto Added = Pending->addDef(Register::fromPhysical(Destination)); + if (!Added) + return std::unexpected(Added.error()); + Added = Pending->addUse(Source.Reg); + if (!Added) + return std::unexpected(Added.error()); + if (IsGPR) + Added = Pending->addImmediate(Source.Imm); + else + Added = Pending->addUse(Source.Reg); + if (!Added) + return std::unexpected(Added.error()); + if (Before.debugLocation()) { + Added = Pending->setDebugLocation(*Before.debugLocation()); + if (!Added) + return std::unexpected(Added.error()); + } + return Pending->build(); +} + +[[nodiscard]] Expected<void> updateOperands(MachineInstr &Instruction, RegImmPair OldRegImm, + PhysicalRegister NewReg, + const RISCVSubtarget &Subtarget) { + const std::size_t FirstOperand = + isCompressibleStore(Instruction, Subtarget) && OldRegImm.Imm != 0 ? 1U : 0U; + for (std::size_t Index = FirstOperand; Index < Instruction.operandCount(); ++Index) { + MachineOperand *Operand = Instruction.operandAt(Index); + if (Operand == nullptr || Operand->isDef() || Operand->registerValue() != OldRegImm.Reg) + continue; + auto Updated = Operand->setRegister(Register::fromPhysical(NewReg)); + if (!Updated) + return std::unexpected(Updated.error()); + } + + MachineOperand *Offset = Instruction.operandAt(2); + const auto *OldOffset = Offset == nullptr ? nullptr : Offset->getIf<std::int64_t>(); + if (OldOffset == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V compressible load/store has no immediate offset"); + return Offset->setImmediate(*OldOffset & compressedOffsetMask(Instruction)); +} + +[[nodiscard]] Expected<bool> makeBlockCompressible(MachineFunction &Function, + MachineBasicBlock &Block, + const RISCVSubtarget &Subtarget, + const TargetDescription &Target) { + bool Changed = false; + for (MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const auto RegImm = regImmPreventingCompression(*Instruction, Subtarget, Target); + if (!RegImm) + continue; + std::vector<MachineInstr *> Uses; + auto NewReg = analyzeCompressibleUses(Function, *Instruction, *RegImm, Uses, Subtarget, Target); + if (!NewReg) + return std::unexpected(NewReg.error()); + const PhysicalRegister Scavenged = NewReg->value_or(PhysicalRegister{}); + if (!Scavenged.isValid()) + continue; + auto Copy = insertRegisterCopy(Function, *Instruction, Scavenged, *RegImm, Target); + if (!Copy) + return std::unexpected(Copy.error()); + for (MachineInstr *Use : Uses) { + auto Updated = updateOperands(*Use, *RegImm, Scavenged, Subtarget); + if (!Updated) + return std::unexpected(Updated.error()); + } + Changed = true; + } + return Changed; +} + +class RISCVMakeCompressiblePass final : public MachineFunctionPass { +public: + explicit RISCVMakeCompressiblePass(const RISCVSubtarget &Subtarget) noexcept + : Subtarget(&Subtarget) {} + + [[nodiscard]] const MachinePassContract &contract() const noexcept override { + static const MachinePassContract Contract = [] { + MachinePassContract Value{ + .Name = "riscv-make-compressible", + .Requires = {}, + .Sets = {}, + .Clears = {}, + .RequiredAnalyses = {}, + .VerificationStage = std::nullopt, + }; + Value.ThreadSafety = PassThreadSafety::ThreadSafe; + Value.DeclaredChanges = {MachinePassChange::Instructions, MachinePassChange::Operands, + MachinePassChange::Registers}; + return Value; + }(); + return Contract; + } + + [[nodiscard]] Expected<MachinePassResult> run(MachineFunction &Function, + const TargetDescription &Target, + MachineFunctionAnalysisManager &) override { + MachinePassResult Result; + Result.Preserved = PreservedAnalyses::all(); + if (Subtarget == nullptr || !Subtarget->features().has(RISCVExtension::C) || + !Function.attributes().has(MachineFunctionAttribute::MinSize)) { + return Result; + } + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + auto BlockResult = makeBlockCompressible(Function, *Block, *Subtarget, Target); + if (!BlockResult) + return std::unexpected(BlockResult.error()); + Result.Changed |= *BlockResult; + } + if (Result.Changed) { + Result.Changes = {MachinePassChange::Instructions, MachinePassChange::Operands, + MachinePassChange::Registers}; + Result.Preserved = PreservedAnalyses::none(); + } + return Result; + } + +private: + const RISCVSubtarget *Subtarget = nullptr; +}; + +} // namespace + +std::unique_ptr<MachineFunctionPass> createMakeCompressiblePass(const RISCVSubtarget &Subtarget) { + return std::make_unique<RISCVMakeCompressiblePass>(Subtarget); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVMergeBaseOffset.cpp b/backend/lib/Target/RISCV/RISCVMergeBaseOffset.cpp new file mode 100644 index 0000000..16d8929 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMergeBaseOffset.cpp @@ -0,0 +1,495 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachinePassManager.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" + +#include <algorithm> +#include <cassert> +#include <cstdint> +#include <limits> +#include <memory> +#include <optional> +#include <ranges> +#include <vector> + +namespace rcc::riscv { +namespace { + +constexpr std::uint32_t PCRelativeHighFlag = 2; +constexpr std::uint32_t PCRelativeLowFlag = 4; +constexpr std::uint32_t AbsoluteHighFlag = 32; +constexpr std::uint32_t AbsoluteLowFlag = 64; + +struct AddressPair final { + MachineInstr *High = nullptr; + MachineInstr *Low = nullptr; +}; + +[[nodiscard]] bool isInt32(std::int64_t Value) noexcept { + return Value >= std::numeric_limits<std::int32_t>::min() && + Value <= std::numeric_limits<std::int32_t>::max(); +} + +[[nodiscard]] std::int64_t signExtend32(std::uint64_t Value) noexcept { + return static_cast<std::int32_t>(static_cast<std::uint32_t>(Value)); +} + +[[nodiscard]] bool isZeroRegister(Register Reg) noexcept { + const auto Physical = Reg.physicalRegister(); + return Physical && Physical->number() == static_cast<std::uint32_t>(PhysicalRegisterID::X0); +} + +[[nodiscard]] std::optional<std::int64_t> addressOffset(const MachineOperand &Operand) noexcept { + if (const auto *Constant = Operand.getIf<ConstantPoolOperand>()) + return Constant->Offset; + if (const auto *Global = Operand.getIf<GlobalSymbolOperand>()) + return Global->Offset; + if (const auto *Block = Operand.getIf<BlockAddressOperand>()) + return Block->Offset; + if (const auto *Symbol = Operand.getIf<MCSymbolOperand>()) + return Symbol->Offset; + return std::nullopt; +} + +[[nodiscard]] bool isFoldableAddress(const MachineOperand &Operand) noexcept { + return Operand.getIf<ConstantPoolOperand>() != nullptr || + Operand.getIf<GlobalSymbolOperand>() != nullptr || + Operand.getIf<BlockAddressOperand>() != nullptr; +} + +[[nodiscard]] Expected<MachineOperand> addressWithOffset(const MachineOperand &Operand, + std::int64_t Offset) { + if (const auto *Constant = Operand.getIf<ConstantPoolOperand>()) + return MachineOperand::constantPool(Constant->Index, Offset, Operand.targetFlags()); + if (const auto *Global = Operand.getIf<GlobalSymbolOperand>()) { + return MachineOperand::globalSymbol(Global->Name, Offset, Operand.targetFlags(), + Global->Properties); + } + if (const auto *Block = Operand.getIf<BlockAddressOperand>()) { + return MachineOperand::blockAddress(Block->FunctionName, Block->BlockName, Offset, + Operand.targetFlags()); + } + if (const auto *Symbol = Operand.getIf<MCSymbolOperand>()) + return MachineOperand::mcSymbol(Symbol->Name, Offset, Operand.targetFlags()); + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V base-offset folding requires a symbolic address operand"); +} + +[[nodiscard]] Expected<void> replaceOperand(MachineInstr &Instruction, std::size_t Index, + MachineOperand Operand) { + auto Erased = Instruction.eraseOperand(Index); + if (!Erased) + return std::unexpected(Erased.error()); + return Instruction.insertOperand(Index, std::move(Operand)); +} + +[[nodiscard]] MachineInstr *uniqueUseInstruction(MachineRegisterInfo &Registers, + Register Reg) noexcept { + if (!Registers.hasOneUse(Reg, false)) + return nullptr; + for (MachineOperand &Use : Registers.useOperands(Reg, false)) + return Use.parent(); + return nullptr; +} + +[[nodiscard]] std::optional<AddressPair> detectFoldable(MachineInstr &High, + MachineRegisterInfo &Registers) { + const bool IsLUI = High.opcode() == instructionOpcode(InstructionOpcode::LUI); + const bool IsAUIPC = High.opcode() == instructionOpcode(InstructionOpcode::AUIPC); + if (!IsLUI && !IsAUIPC) + return std::nullopt; + MachineOperand *HighAddress = High.operandAt(1); + if (HighAddress == nullptr || !isFoldableAddress(*HighAddress) || + addressOffset(*HighAddress) != 0 || + HighAddress->targetFlags() != (IsAUIPC ? PCRelativeHighFlag : AbsoluteHighFlag)) { + return std::nullopt; + } + MachineOperand *HighDefinition = High.operandAt(0); + const auto HighRegister = + HighDefinition == nullptr ? std::nullopt : HighDefinition->registerValue(); + if (!HighRegister) + return std::nullopt; + MachineInstr *Low = uniqueUseInstruction(Registers, *HighRegister); + if (Low == nullptr || Low->opcode() != instructionOpcode(InstructionOpcode::ADDI)) + return std::nullopt; + const auto LowBase = + Low->operandAt(1) == nullptr ? std::nullopt : Low->operandAt(1)->registerValue(); + MachineOperand *LowAddress = Low->operandAt(2); + if (!LowBase || *LowBase != *HighRegister || LowAddress == nullptr) + return std::nullopt; + if (IsLUI) { + if (!isFoldableAddress(*LowAddress) || addressOffset(*LowAddress) != 0 || + LowAddress->targetFlags() != AbsoluteLowFlag) { + return std::nullopt; + } + } else if (LowAddress->getIf<MCSymbolOperand>() == nullptr || + LowAddress->targetFlags() != PCRelativeLowFlag) { + return std::nullopt; + } + return AddressPair{.High = &High, .Low = Low}; +} + +[[nodiscard]] Expected<bool> foldOffset(AddressPair Pair, MachineInstr &Tail, std::int64_t Offset, + MachineRegisterInfo &Registers) { + if (!isInt32(Offset)) + return false; + MachineOperand *HighAddress = Pair.High->operandAt(1); + MachineOperand *LowAddress = Pair.Low->operandAt(2); + MachineOperand *LowDefinition = Pair.Low->operandAt(0); + MachineOperand *TailDefinition = Tail.operandAt(0); + if (HighAddress == nullptr || LowAddress == nullptr || LowDefinition == nullptr || + TailDefinition == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V base-offset sequence has incomplete operands"); + } + auto LowRegister = LowDefinition->registerValue(); + auto TailRegister = TailDefinition->registerValue(); + if (!LowRegister || !TailRegister) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V base-offset sequence has non-register definitions"); + auto NewHighAddress = addressWithOffset(*HighAddress, Offset); + if (!NewHighAddress) + return std::unexpected(NewHighAddress.error()); + std::optional<MachineOperand> NewLowAddress; + if (Pair.High->opcode() != instructionOpcode(InstructionOpcode::AUIPC)) { + auto Address = addressWithOffset(*LowAddress, Offset); + if (!Address) + return std::unexpected(Address.error()); + NewLowAddress.emplace(std::move(*Address)); + } + auto ReplacedHigh = replaceOperand(*Pair.High, 1, std::move(*NewHighAddress)); + if (!ReplacedHigh) + return std::unexpected(ReplacedHigh.error()); + if (NewLowAddress) { + auto ReplacedLow = replaceOperand(*Pair.Low, 2, std::move(*NewLowAddress)); + if (!ReplacedLow) + return std::unexpected(ReplacedLow.error()); + } + auto ReplacedRegisters = Registers.replaceRegWith(*TailRegister, *LowRegister); + if (!ReplacedRegisters) + return std::unexpected(ReplacedRegisters.error()); + auto Removed = Tail.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; +} + +[[nodiscard]] Expected<bool> foldLargeOffset(AddressPair Pair, MachineInstr &Tail, + Register GlobalAddress, + MachineRegisterInfo &Registers) { + MachineOperand *LeftOperand = Tail.operandAt(1); + MachineOperand *RightOperand = Tail.operandAt(2); + const auto Left = LeftOperand == nullptr ? std::nullopt : LeftOperand->registerValue(); + const auto Right = RightOperand == nullptr ? std::nullopt : RightOperand->registerValue(); + if (!Left || !Right) + return false; + const Register OffsetRegister = *Left == GlobalAddress ? *Right : *Left; + if (!OffsetRegister.isVirtual() || !Registers.hasOneUse(OffsetRegister, false)) + return false; + MachineInstr *OffsetTail = Registers.getVRegDef(OffsetRegister); + if (OffsetTail == nullptr) + return false; + + const bool IsADDI = OffsetTail->opcode() == instructionOpcode(InstructionOpcode::ADDI); + const bool IsADDIW = OffsetTail->opcode() == instructionOpcode(InstructionOpcode::ADDIW); + if (IsADDI || IsADDIW) { + MachineOperand *BaseOperand = OffsetTail->operandAt(1); + MachineOperand *ImmediateOperand = OffsetTail->operandAt(2); + const auto Base = BaseOperand == nullptr ? std::nullopt : BaseOperand->registerValue(); + const auto *Low = + ImmediateOperand == nullptr ? nullptr : ImmediateOperand->getIf<std::int64_t>(); + if (!Base || Low == nullptr || ImmediateOperand->targetFlags() != 0) + return false; + if (isZeroRegister(*Base)) { + auto Folded = foldOffset(Pair, Tail, *Low, Registers); + if (!Folded || !*Folded) + return Folded; + auto Removed = OffsetTail->eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; + } + MachineInstr *OffsetHigh = Registers.getVRegDef(*Base); + if (OffsetHigh == nullptr || + OffsetHigh->opcode() != instructionOpcode(InstructionOpcode::LUI) || + !Registers.hasOneUse(*Base, false)) { + return false; + } + MachineOperand *HighImmediate = OffsetHigh->operandAt(1); + const auto *High = HighImmediate == nullptr ? nullptr : HighImmediate->getIf<std::int64_t>(); + if (High == nullptr || HighImmediate->targetFlags() != 0) + return false; + std::int64_t Offset = signExtend32(static_cast<std::uint64_t>(*High) << 12U) + *Low; + if (IsADDIW) + Offset = signExtend32(static_cast<std::uint64_t>(Offset)); + auto Folded = foldOffset(Pair, Tail, Offset, Registers); + if (!Folded || !*Folded) + return Folded; + auto Removed = OffsetTail->eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + Removed = OffsetHigh->eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; + } + + if (OffsetTail->opcode() != instructionOpcode(InstructionOpcode::LUI)) + return false; + MachineOperand *ImmediateOperand = OffsetTail->operandAt(1); + const auto *High = + ImmediateOperand == nullptr ? nullptr : ImmediateOperand->getIf<std::int64_t>(); + if (High == nullptr || ImmediateOperand->targetFlags() != 0) + return false; + const std::int64_t Offset = signExtend32(static_cast<std::uint64_t>(*High) << 12U); + auto Folded = foldOffset(Pair, Tail, Offset, Registers); + if (!Folded || !*Folded) + return Folded; + auto Removed = OffsetTail->eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; +} + +[[nodiscard]] Expected<bool> foldShiftedOffset(AddressPair Pair, MachineInstr &Tail, + Register GlobalAddress, + MachineRegisterInfo &Registers) { + if (Tail.operandAt(2) == nullptr || Tail.operandAt(2)->registerValue() != GlobalAddress) + return false; + unsigned Shift = 0; + if (Tail.opcode() == instructionOpcode(InstructionOpcode::SH1ADD)) + Shift = 1; + else if (Tail.opcode() == instructionOpcode(InstructionOpcode::SH2ADD)) + Shift = 2; + else if (Tail.opcode() == instructionOpcode(InstructionOpcode::SH3ADD)) + Shift = 3; + else + return false; + const auto Shifted = + Tail.operandAt(1) == nullptr ? std::nullopt : Tail.operandAt(1)->registerValue(); + if (!Shifted || !Shifted->isVirtual() || !Registers.hasOneUse(*Shifted, false)) + return false; + MachineInstr *OffsetTail = Registers.getVRegDef(*Shifted); + if (OffsetTail == nullptr || OffsetTail->opcode() != instructionOpcode(InstructionOpcode::ADDI)) { + return false; + } + const auto Base = OffsetTail->operandAt(1) == nullptr ? std::nullopt + : OffsetTail->operandAt(1)->registerValue(); + const auto *Immediate = OffsetTail->operandAt(2) == nullptr + ? nullptr + : OffsetTail->operandAt(2)->getIf<std::int64_t>(); + if (!Base || !isZeroRegister(*Base) || Immediate == nullptr) + return false; + const std::int64_t Offset = + static_cast<std::int64_t>(static_cast<std::uint64_t>(*Immediate) << Shift); + auto Folded = foldOffset(Pair, Tail, Offset, Registers); + if (!Folded || !*Folded) + return Folded; + auto Removed = OffsetTail->eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; +} + +[[nodiscard]] Expected<bool> foldArithmeticOffset(AddressPair Pair, + MachineRegisterInfo &Registers) { + MachineOperand *LowDefinition = Pair.Low->operandAt(0); + const auto LowRegister = LowDefinition == nullptr ? std::nullopt : LowDefinition->registerValue(); + if (!LowRegister) + return false; + MachineInstr *Tail = uniqueUseInstruction(Registers, *LowRegister); + if (Tail == nullptr) + return false; + if (Tail->opcode() == instructionOpcode(InstructionOpcode::ADDI)) { + const auto *Immediate = + Tail->operandAt(2) == nullptr ? nullptr : Tail->operandAt(2)->getIf<std::int64_t>(); + if (Immediate == nullptr) + return false; + MachineOperand *TailDefinition = Tail->operandAt(0); + const auto TailRegister = + TailDefinition == nullptr ? std::nullopt : TailDefinition->registerValue(); + MachineInstr *SecondTail = + TailRegister ? uniqueUseInstruction(Registers, *TailRegister) : nullptr; + if (SecondTail != nullptr && + SecondTail->opcode() == instructionOpcode(InstructionOpcode::ADDI)) { + const auto *SecondImmediate = SecondTail->operandAt(2) == nullptr + ? nullptr + : SecondTail->operandAt(2)->getIf<std::int64_t>(); + if (SecondImmediate != nullptr) { + auto Folded = foldOffset(Pair, *SecondTail, *Immediate + *SecondImmediate, Registers); + if (!Folded || !*Folded) + return Folded; + auto Removed = Tail->eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; + } + } + return foldOffset(Pair, *Tail, *Immediate, Registers); + } + if (Tail->opcode() == instructionOpcode(InstructionOpcode::ADD)) + return foldLargeOffset(Pair, *Tail, *LowRegister, Registers); + if (Tail->opcode() == instructionOpcode(InstructionOpcode::SH1ADD) || + Tail->opcode() == instructionOpcode(InstructionOpcode::SH2ADD) || + Tail->opcode() == instructionOpcode(InstructionOpcode::SH3ADD)) { + return foldShiftedOffset(Pair, *Tail, *LowRegister, Registers); + } + return false; +} + +[[nodiscard]] bool isOffsetMemoryInstruction(const MachineInstr &Instruction, + const TargetDescription &Target) noexcept { + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction.opcode()); + if (Descriptor == nullptr || (!Descriptor->Flags.has(InstructionFlag::MayLoad) && + !Descriptor->Flags.has(InstructionFlag::MayStore))) { + return false; + } + return Instruction.operandAt(0) != nullptr && Instruction.operandAt(1) != nullptr && + Instruction.operandAt(2) != nullptr; +} + +[[nodiscard]] Expected<bool> foldIntoMemoryOperations(AddressPair Pair, + MachineRegisterInfo &Registers, + const TargetDescription &Target) { + MachineOperand *LowDefinition = Pair.Low->operandAt(0); + const auto LowRegister = LowDefinition == nullptr ? std::nullopt : LowDefinition->registerValue(); + if (!LowRegister) + return false; + std::vector<MachineInstr *> Uses; + for (MachineOperand &Use : Registers.useOperands(*LowRegister, false)) { + MachineInstr *Instruction = Use.parent(); + if (Instruction != nullptr && std::ranges::find(Uses, Instruction) == Uses.end()) + Uses.push_back(Instruction); + } + if (Uses.empty()) + return false; + + std::optional<std::int64_t> CommonOffset; + for (MachineInstr *Use : Uses) { + if (!isOffsetMemoryInstruction(*Use, Target)) + return false; + const auto Value = Use->operandAt(0)->registerValue(); + const auto Base = Use->operandAt(1)->registerValue(); + const auto *Offset = Use->operandAt(2)->getIf<std::int64_t>(); + if (!Base || *Base != *LowRegister || (Value && *Value == *LowRegister) || Offset == nullptr) + return false; + if (CommonOffset && *CommonOffset != *Offset) + return false; + CommonOffset = *Offset; + } + assert(CommonOffset && "nonempty validated use list must define a common offset"); + + MachineOperand *HighAddress = Pair.High->operandAt(1); + MachineOperand *LowAddress = Pair.Low->operandAt(2); + MachineOperand *HighDefinition = Pair.High->operandAt(0); + if (HighAddress == nullptr || LowAddress == nullptr || HighDefinition == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V base-offset sequence has incomplete address operands"); + const auto HighRegister = HighDefinition->registerValue(); + const auto OldOffset = addressOffset(*HighAddress); + if (!HighRegister || !OldOffset) + return false; + const std::int64_t NewOffset = *OldOffset + *CommonOffset; + if (!isInt32(NewOffset)) + return false; + + auto NewHighAddress = addressWithOffset(*HighAddress, NewOffset); + if (!NewHighAddress) + return std::unexpected(NewHighAddress.error()); + auto NewLowAddress = Pair.High->opcode() == instructionOpcode(InstructionOpcode::AUIPC) + ? Expected<MachineOperand>(LowAddress->cloneDetached()) + : addressWithOffset(*LowAddress, NewOffset); + if (!NewLowAddress) + return std::unexpected(NewLowAddress.error()); + auto ReplacedHigh = replaceOperand(*Pair.High, 1, std::move(*NewHighAddress)); + if (!ReplacedHigh) + return std::unexpected(ReplacedHigh.error()); + if (Pair.High->opcode() != instructionOpcode(InstructionOpcode::AUIPC)) { + auto ReplacedLow = replaceOperand(*Pair.Low, 2, NewLowAddress->cloneDetached()); + if (!ReplacedLow) + return std::unexpected(ReplacedLow.error()); + } + for (MachineInstr *Use : Uses) { + auto SetBase = Use->operandAt(1)->setRegister(*HighRegister); + if (!SetBase) + return std::unexpected(SetBase.error()); + auto ReplacedOffset = replaceOperand(*Use, 2, NewLowAddress->cloneDetached()); + if (!ReplacedOffset) + return std::unexpected(ReplacedOffset.error()); + } + auto Removed = Pair.Low->eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; +} + +class RISCVMergeBaseOffsetPass final : public MachineFunctionPass { +public: + [[nodiscard]] const MachinePassContract &contract() const noexcept override { + static const MachinePassContract Contract = [] { + MachinePassContract Value{ + .Name = "riscv-merge-base-offset", + .Requires = {MachineFunctionProperty::IsSSA}, + .Sets = {}, + .Clears = {}, + .RequiredAnalyses = {}, + .VerificationStage = std::nullopt, + }; + Value.ThreadSafety = PassThreadSafety::ThreadSafe; + Value.DeclaredChanges = {MachinePassChange::Instructions, MachinePassChange::Operands, + MachinePassChange::Registers}; + return Value; + }(); + return Contract; + } + + [[nodiscard]] Expected<MachinePassResult> run(MachineFunction &Function, + const TargetDescription &Target, + MachineFunctionAnalysisManager &) override { + MachinePassResult Result; + Result.Preserved = PreservedAnalyses::all(); + MachineRegisterInfo &Registers = Function.registerInfo(); + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + for (MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;) { + auto Pair = detectFoldable(*Instruction, Registers); + if (!Pair) { + Instruction = Instruction->next(); + continue; + } + auto Arithmetic = foldArithmeticOffset(*Pair, Registers); + if (!Arithmetic) + return std::unexpected(Arithmetic.error()); + Result.Changed |= *Arithmetic; + auto Memory = foldIntoMemoryOperations(*Pair, Registers, Target); + if (!Memory) + return std::unexpected(Memory.error()); + Result.Changed |= *Memory; + Instruction = Pair->High->next(); + } + } + if (Result.Changed) { + Result.Changes = {MachinePassChange::Instructions, MachinePassChange::Operands, + MachinePassChange::Registers}; + Result.Preserved = PreservedAnalyses::none(); + } + return Result; + } +}; + +} // namespace + +std::unique_ptr<MachineFunctionPass> createMergeBaseOffsetPass() { + return std::make_unique<RISCVMergeBaseOffsetPass>(); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVMoveMerger.cpp b/backend/lib/Target/RISCV/RISCVMoveMerger.cpp new file mode 100644 index 0000000..1eb4b43 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVMoveMerger.cpp @@ -0,0 +1,309 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "RISCVInstrInfo.h" +#include "rcc/CodeGen/LiveRegUnits.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachinePassManager.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" + +#include <memory> +#include <optional> +#include <utility> + +namespace rcc::riscv { +namespace { + +struct CopyOperands final { + MachineOperand *Destination = nullptr; + MachineOperand *Source = nullptr; + Register DestinationRegister; + Register SourceRegister; + PhysicalRegister DestinationPhysical; + PhysicalRegister SourcePhysical; +}; + +[[nodiscard]] std::optional<CopyOperands> copyOperands(MachineInstr &Instruction) { + auto Operands = instrInfo().getCopyOperands(Instruction); + if (!Operands || Operands->Destination == nullptr || Operands->Source == nullptr) + return std::nullopt; + auto Destination = Operands->Destination->registerValue(); + auto Source = Operands->Source->registerValue(); + if (!Destination || !Source) + return std::nullopt; + auto DestinationPhysical = Destination->physicalRegister(); + auto SourcePhysical = Source->physicalRegister(); + if (!DestinationPhysical || !SourcePhysical) + return std::nullopt; + return CopyOperands{ + .Destination = Operands->Destination, + .Source = Operands->Source, + .DestinationRegister = *Destination, + .SourceRegister = *Source, + .DestinationPhysical = *DestinationPhysical, + .SourcePhysical = *SourcePhysical, + }; +} + +[[nodiscard]] bool isRegister(PhysicalRegister RegisterValue, + PhysicalRegisterID Expected) noexcept { + return RegisterValue.number() == static_cast<std::uint32_t>(Expected); +} + +[[nodiscard]] bool isArgumentRegister(PhysicalRegister RegisterValue) noexcept { + return isRegister(RegisterValue, PhysicalRegisterID::X10) || + isRegister(RegisterValue, PhysicalRegisterID::X11); +} + +[[nodiscard]] bool isSavedRegister(PhysicalRegister RegisterValue, + const TargetDescription &Target) noexcept { + const RegisterClassID Class = + RegisterClassID::fromValue(static_cast<std::uint32_t>(RegisterClass::SR07)); + return Target.registers().registerClassContains(Class, RegisterValue); +} + +[[nodiscard]] bool isMoveFromSavedToArgument(const CopyOperands &Copy, + const TargetDescription &Target) noexcept { + return isArgumentRegister(Copy.DestinationPhysical) && + isSavedRegister(Copy.SourcePhysical, Target); +} + +[[nodiscard]] bool isMoveFromArgumentToSaved(const CopyOperands &Copy, + const TargetDescription &Target) noexcept { + return isArgumentRegister(Copy.SourcePhysical) && + isSavedRegister(Copy.DestinationPhysical, Target); +} + +[[nodiscard]] Expected<bool> isReadBetween(MachineInstr &First, MachineInstr &Second, + PhysicalRegister RegisterValue, + const TargetDescription &Target) { + LiveRegUnits Modified; + LiveRegUnits Used; + for (MachineInstr *Instruction = First.next(); Instruction != &Second; + Instruction = Instruction->next()) { + if (Instruction == nullptr) + return makeUnexpected(ErrorCode::InternalError, + "RISC-V move pair is not in one machine basic block"); + if (Instruction->isDebugInstr()) + continue; + if (auto Accumulated = LiveRegUnits::accumulateUsedDefed(*Instruction, Modified, Used, Target); + !Accumulated) { + return std::unexpected(Accumulated.error()); + } + } + auto Available = Used.available(RegisterValue, Target); + if (!Available) + return std::unexpected(Available.error()); + return !*Available; +} + +[[nodiscard]] Expected<MachineInstr *> findMatchingCopy(MachineInstr &First, + const CopyOperands &FirstCopy, + bool MoveFromSavedToArgument, + const TargetDescription &Target) { + LiveRegUnits Modified; + LiveRegUnits Used; + for (MachineInstr *Instruction = First.next(); Instruction != nullptr; + Instruction = Instruction->next()) { + if (Instruction->isDebugInstr()) + continue; + auto SecondCopy = copyOperands(*Instruction); + if (SecondCopy) { + const bool Candidate = MoveFromSavedToArgument + ? isMoveFromSavedToArgument(*SecondCopy, Target) + : isMoveFromArgumentToSaved(*SecondCopy, Target); + if (Candidate) { + if (FirstCopy.DestinationPhysical == SecondCopy->DestinationPhysical) + return nullptr; + if (!MoveFromSavedToArgument && FirstCopy.SourcePhysical == SecondCopy->SourcePhysical) { + return nullptr; + } + auto DestinationNotModified = Modified.available(SecondCopy->DestinationPhysical, Target); + if (!DestinationNotModified) + return std::unexpected(DestinationNotModified.error()); + auto DestinationNotUsed = Used.available(SecondCopy->DestinationPhysical, Target); + if (!DestinationNotUsed) + return std::unexpected(DestinationNotUsed.error()); + auto SourceNotModified = Modified.available(SecondCopy->SourcePhysical, Target); + if (!SourceNotModified) + return std::unexpected(SourceNotModified.error()); + if (!*DestinationNotModified || !*DestinationNotUsed || !*SourceNotModified) + return nullptr; + return Instruction; + } + } + if (auto Accumulated = LiveRegUnits::accumulateUsedDefed(*Instruction, Modified, Used, Target); + !Accumulated) { + return std::unexpected(Accumulated.error()); + } + } + return nullptr; +} + +[[nodiscard]] Expected<MachineInstr *> mergeCopies(MachineFunction &Function, MachineInstr &First, + MachineInstr &Second, + bool MoveFromSavedToArgument, + const TargetDescription &Target) { + auto FirstCopy = copyOperands(First); + auto SecondCopy = copyOperands(Second); + if (!FirstCopy || !SecondCopy) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V move merger lost a recognized copy instruction"); + } + + MachineOperand *FirstOperand = + MoveFromSavedToArgument ? FirstCopy->Source : FirstCopy->Destination; + MachineOperand *SecondOperand = + MoveFromSavedToArgument ? SecondCopy->Source : SecondCopy->Destination; + Register FirstRegister = + MoveFromSavedToArgument ? FirstCopy->SourceRegister : FirstCopy->DestinationRegister; + Register SecondRegister = + MoveFromSavedToArgument ? SecondCopy->SourceRegister : SecondCopy->DestinationRegister; + RegisterOperandFlags PairedSourceFlags = SecondCopy->Source->registerFlags(); + if (MoveFromSavedToArgument && PairedSourceFlags.has(RegisterOperandFlag::Kill)) { + auto ReadBetween = isReadBetween(First, Second, SecondCopy->SourcePhysical, Target); + if (!ReadBetween) + return std::unexpected(ReadBetween.error()); + if (*ReadBetween) + PairedSourceFlags.clear(RegisterOperandFlag::Kill); + } + if (MoveFromSavedToArgument ? !isRegister(FirstCopy->DestinationPhysical, PhysicalRegisterID::X10) + : !isRegister(FirstCopy->SourcePhysical, PhysicalRegisterID::X10)) { + std::swap(FirstOperand, SecondOperand); + std::swap(FirstRegister, SecondRegister); + } + + const RegisterOperandFlags FirstFlags = + FirstOperand == SecondCopy->Source ? PairedSourceFlags : FirstOperand->registerFlags(); + const RegisterOperandFlags SecondFlags = + SecondOperand == SecondCopy->Source ? PairedSourceFlags : SecondOperand->registerFlags(); + + MachineIRBuilder Builder(Function, Target); + auto InsertPoint = Builder.setInsertPointBefore(First); + if (!InsertPoint) + return std::unexpected(InsertPoint.error()); + const InstructionOpcode Opcode = + MoveFromSavedToArgument ? InstructionOpcode::CM_MVA01S : InstructionOpcode::CM_MVSA01; + auto Pending = Builder.buildInstr(instructionOpcode(Opcode)); + if (!Pending) + return std::unexpected(Pending.error()); + if (First.debugLocation()) { + if (auto Location = Pending->setDebugLocation(*First.debugLocation()); !Location) + return std::unexpected(Location.error()); + } + Expected<void> Added = MoveFromSavedToArgument ? Pending->addUse(FirstRegister, FirstFlags) + : Pending->addDef(FirstRegister, FirstFlags); + if (!Added) + return std::unexpected(Added.error()); + Added = MoveFromSavedToArgument ? Pending->addUse(SecondRegister, SecondFlags) + : Pending->addDef(SecondRegister, SecondFlags); + if (!Added) + return std::unexpected(Added.error()); + return Pending->build(); +} + +[[nodiscard]] Expected<bool> mergeBlock(MachineFunction &Function, MachineBasicBlock &Block, + const TargetDescription &Target) { + bool Changed = false; + for (MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr;) { + MachineInstr *Next = Instruction->next(); + if (Instruction->isDebugInstr()) { + Instruction = Next; + continue; + } + auto FirstCopy = copyOperands(*Instruction); + if (!FirstCopy) { + Instruction = Next; + continue; + } + const bool MoveFromSavedToArgument = isMoveFromSavedToArgument(*FirstCopy, Target); + const bool MoveFromArgumentToSaved = isMoveFromArgumentToSaved(*FirstCopy, Target); + if (!MoveFromSavedToArgument && !MoveFromArgumentToSaved) { + Instruction = Next; + continue; + } + auto Paired = findMatchingCopy(*Instruction, *FirstCopy, MoveFromSavedToArgument, Target); + if (!Paired) + return std::unexpected(Paired.error()); + if (*Paired == nullptr) { + Instruction = Next; + continue; + } + const bool Adjacent = Next == *Paired; + MachineInstr *AfterPair = (*Paired)->next(); + auto Merged = mergeCopies(Function, *Instruction, **Paired, MoveFromSavedToArgument, Target); + if (!Merged) + return std::unexpected(Merged.error()); + auto RemovedFirst = Instruction->eraseFromParent(); + if (!RemovedFirst) + return std::unexpected(RemovedFirst.error()); + auto RemovedPaired = (*Paired)->eraseFromParent(); + if (!RemovedPaired) + return std::unexpected(RemovedPaired.error()); + Changed = true; + Instruction = Adjacent ? AfterPair : Next; + } + return Changed; +} + +class RISCVMoveMergePass final : public MachineFunctionPass { +public: + explicit RISCVMoveMergePass(const RISCVSubtarget &Subtarget) noexcept : Subtarget(&Subtarget) {} + + [[nodiscard]] const MachinePassContract &contract() const noexcept override { + static const MachinePassContract Contract = [] { + MachinePassContract Value{ + .Name = "riscv-move-merge", + .Requires = {}, + .Sets = {}, + .Clears = {}, + .RequiredAnalyses = {}, + .VerificationStage = std::nullopt, + }; + Value.ThreadSafety = PassThreadSafety::ThreadSafe; + Value.DeclaredChanges = {MachinePassChange::Instructions, MachinePassChange::Operands, + MachinePassChange::Registers}; + return Value; + }(); + return Contract; + } + + [[nodiscard]] Expected<MachinePassResult> run(MachineFunction &Function, + const TargetDescription &Target, + MachineFunctionAnalysisManager &) override { + MachinePassResult Result; + Result.Preserved = PreservedAnalyses::all(); + if (Subtarget == nullptr || !Subtarget->features().has(RISCVExtension::Zcmp)) + return Result; + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + auto BlockResult = mergeBlock(Function, *Block, Target); + if (!BlockResult) + return std::unexpected(BlockResult.error()); + Result.Changed |= *BlockResult; + } + if (Result.Changed) { + Result.Changes = {MachinePassChange::Instructions, MachinePassChange::Operands, + MachinePassChange::Registers}; + Result.Preserved = PreservedAnalyses::none(); + } + return Result; + } + +private: + const RISCVSubtarget *Subtarget = nullptr; +}; + +} // namespace + +std::unique_ptr<MachineFunctionPass> createMoveMergePass(const RISCVSubtarget &Subtarget) { + return std::make_unique<RISCVMoveMergePass>(Subtarget); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVObjectFile.cpp b/backend/lib/Target/RISCV/RISCVObjectFile.cpp new file mode 100644 index 0000000..706c1f8 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVObjectFile.cpp @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVObjectFile.h" + +#include "RISCVAsmPrinter.h" +#include "RISCVTargetObjectFile.h" +#include "rcc/CodeGen/AsmPrinter.h" +#include "rcc/CodeGen/CompilationUnit.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineMCContext.h" +#include "rcc/IR/DataLayout.h" +#include "rcc/MC/ELFObjectWriter.h" +#include "rcc/MC/MCContext.h" +#include "rcc/MC/MCObjectStreamer.h" +#include "rcc/Target/RISCV/RISCVAsmBackend.h" +#include "rcc/Target/RISCV/RISCVMCCodeEmitter.h" +#include "rcc/Target/RISCV/RISCVMCTargetLowering.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <cstdint> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +constexpr std::uint16_t EMRISCV = 243; +constexpr std::uint32_t SHTRISCVAttributes = 0x70000003; + +[[nodiscard]] AsmPrinterOptions printerOptions(ObjectEmissionOptions Options) noexcept { + return AsmPrinterOptions{ + .EmitFunctionDirectives = Options.EmitFunctionDirectives, + .EmitCFI = Options.EmitCFI, + .EmitBlockLabels = Options.EmitBlockLabels, + .EmitInstructionSymbols = true, + .VerifyInput = Options.VerifyInput, + }; +} + +[[nodiscard]] std::vector<std::uint8_t> attributesSection() { + constexpr char Architecture[] = "rv64i2p1_m2p0_a2p1_f2p2_d2p2_c2p0_zicsr2p0_zifencei2p0_" + "zmmul1p0_zaamo1p0_zalrsc1p0"; + const std::uint32_t FilePayloadSize = 1U + sizeof(Architecture) + 2U; + const std::uint32_t VendorSize = 4U + 6U + 1U + 4U + FilePayloadSize; + std::vector<std::uint8_t> Bytes; + Bytes.reserve(1U + VendorSize); + const auto AppendU32 = [&Bytes](std::uint32_t Value) { + for (std::uint8_t Shift = 0; Shift < 32; Shift += 8) + Bytes.push_back(static_cast<std::uint8_t>(Value >> Shift)); + }; + Bytes.push_back('A'); + AppendU32(VendorSize); + Bytes.insert(Bytes.end(), {'r', 'i', 's', 'c', 'v', 0}); + Bytes.push_back(1); + AppendU32(5U + FilePayloadSize); + Bytes.push_back(5); + Bytes.insert(Bytes.end(), Architecture, Architecture + sizeof(Architecture)); + Bytes.push_back(4); + Bytes.push_back(16); + return Bytes; +} + +[[nodiscard]] mc::ELFObjectTargetInfo objectTargetInfo() { + mc::ELFObjectTargetInfo Info; + Info.Machine = EMRISCV; + Info.ExtraSections.push_back(mc::ELFExtraSection{ + .Name = ".riscv.attributes", + .Type = SHTRISCVAttributes, + .Flags = 0, + .Alignment = 1, + .EntrySize = 0, + .Contents = attributesSection(), + }); + return Info; +} + +struct ObjectEmissionContext final { + explicit ObjectEmissionContext(const TargetDescription &Target) + : MachineContext(Context), Lowering(Target), Emitter(Target), Writer(objectTargetInfo()), + Streamer(Emitter, Backend, Writer) { + mc::MCSectionDesc EHFrameDescription{ + .Name = ".eh_frame", + .Kind = mc::MCSectionKind::EHFrame, + .Flags = {mc::MCSectionFlag::Alloc}, + .Alignment = 8, + .EntrySize = 0, + .Group = {}, + .UniqueID = std::nullopt, + }; + auto EHFrame = Context.getOrCreateSection(std::move(EHFrameDescription)); + if (!EHFrame) { + Initialization = std::unexpected(EHFrame.error()); + return; + } + Initialization = Streamer.configureDwarfFrame(std::move(*EHFrame), + mc::MCDwarfFrameInfo{.CodeAlignment = 1, + .DataAlignment = -8, + .ReturnAddressRegister = 1, + .InitialCFARegister = 2, + .InitialCFAOffset = 0}); + } + + mc::MCContext Context; + MachineMCContext MachineContext; + RISCVMCTargetLowering Lowering; + RISCVELFTargetObjectFile ObjectFile; + RISCVMCCodeEmitter Emitter; + RISCVAsmBackend Backend; + mc::ELF64LEObjectWriter Writer; + mc::ELFObjectStreamer Streamer; + Expected<void> Initialization; +}; + +} // namespace + +Expected<std::vector<std::uint8_t>> emitObject(const CompilationUnit &Unit, + const TargetDescription &Target, + ObjectEmissionOptions Options) { + ObjectEmissionContext Context(Target); + if (!Context.Initialization) + return std::unexpected(Context.Initialization.error()); + auto Layout = DataLayout::parse(TargetDataLayout); + if (!Layout) + return std::unexpected(Layout.error()); + RISCVAsmPrinter Printer(Target, Context.Lowering, Context.ObjectFile, std::move(*Layout), + Context.MachineContext, Context.Streamer, printerOptions(Options)); + auto Emitted = Printer.emitCompilationUnit(Unit); + if (!Emitted) + return std::unexpected(Emitted.error()); + return Context.Streamer.takeOutput(); +} + +Expected<std::vector<std::uint8_t>> emitObject(const MachineFunction &Function, + const TargetDescription &Target, + ObjectEmissionOptions Options) { + ObjectEmissionContext Context(Target); + if (!Context.Initialization) + return std::unexpected(Context.Initialization.error()); + auto Layout = DataLayout::parse(TargetDataLayout); + if (!Layout) + return std::unexpected(Layout.error()); + RISCVAsmPrinter Printer(Target, Context.Lowering, Context.ObjectFile, std::move(*Layout), + Context.MachineContext, Context.Streamer, printerOptions(Options)); + auto Emitted = Printer.emitFunction(Function); + if (!Emitted) + return std::unexpected(Emitted.error()); + return Context.Streamer.takeOutput(); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVOptWInstrs.cpp b/backend/lib/Target/RISCV/RISCVOptWInstrs.cpp new file mode 100644 index 0000000..5448ae1 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVOptWInstrs.cpp @@ -0,0 +1,621 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TargetRegisterInfo.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" + +#include <algorithm> +#include <bit> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <ranges> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] bool isOpcode(const MachineInstr &Instruction, InstructionOpcode Opcode) noexcept { + return Instruction.opcode() == instructionOpcode(Opcode); +} + +[[nodiscard]] const Register *registerOperand(const MachineInstr &Instruction, + std::size_t Index) noexcept { + const MachineOperand *Operand = Instruction.operandAt(Index); + return Operand == nullptr ? nullptr : Operand->getIf<Register>(); +} + +[[nodiscard]] const std::int64_t *immediateOperand(const MachineInstr &Instruction, + std::size_t Index) noexcept { + const MachineOperand *Operand = Instruction.operandAt(Index); + return Operand == nullptr ? nullptr : Operand->getIf<std::int64_t>(); +} + +[[nodiscard]] bool isZeroRegister(Register Reg) noexcept { + const auto Physical = Reg.physicalRegister(); + return Physical && Physical->number() == static_cast<std::uint32_t>(PhysicalRegisterID::X0); +} + +[[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]] bool hasOneExplicitDef(const MachineInstr &Instruction) noexcept { + return std::ranges::count_if(Instruction.operands(), [](const MachineOperand &Operand) { + return Operand.isDef() && !Operand.isImplicit(); + }) == 1; +} + +[[nodiscard]] bool isWordConsumer(Opcode Opcode) noexcept { + constexpr InstructionOpcode Opcodes[]{ + InstructionOpcode::ADDIW, InstructionOpcode::ADDW, InstructionOpcode::DIVUW, + InstructionOpcode::DIVW, InstructionOpcode::MULW, InstructionOpcode::REMUW, + InstructionOpcode::REMW, InstructionOpcode::SLLW, InstructionOpcode::SRAIW, + InstructionOpcode::SRAW, InstructionOpcode::SRLIW, InstructionOpcode::SRLW, + InstructionOpcode::SUBW, InstructionOpcode::FMV_W_X, InstructionOpcode::FCVT_S_W, + InstructionOpcode::FCVT_S_WU, InstructionOpcode::FCVT_D_W, InstructionOpcode::FCVT_D_WU, + }; + return std::ranges::any_of( + Opcodes, [Opcode](InstructionOpcode Value) { return Opcode == instructionOpcode(Value); }); +} + +struct UserDemand final { + const MachineInstr *Instruction = nullptr; + unsigned Bits = 0; + + friend bool operator==(const UserDemand &, const UserDemand &) = default; +}; + +[[nodiscard]] bool hasAllNBitUsers(const MachineInstr &Origin, const MachineFunction &Function, + unsigned OriginBits) { + std::vector<UserDemand> Visited; + std::vector<UserDemand> Worklist{{&Origin, OriginBits}}; + const MachineRegisterInfo &Registers = Function.registerInfo(); + + while (!Worklist.empty()) { + const UserDemand Demand = Worklist.back(); + Worklist.pop_back(); + if (std::ranges::find(Visited, Demand) != Visited.end()) + continue; + Visited.push_back(Demand); + + const MachineInstr &Instruction = *Demand.Instruction; + if (!hasOneExplicitDef(Instruction)) + return false; + const Register *Destination = registerOperand(Instruction, 0); + if (Destination == nullptr || !Destination->isVirtual()) + return false; + + for (const MachineOperand &Use : + const_cast<MachineRegisterInfo &>(Registers).useOperands(*Destination, false)) { + const MachineInstr *User = Use.parent(); + if (User == nullptr) + return false; + const auto UseIndex = operandIndex(*User, Use); + if (!UseIndex) + return false; + const Opcode UserOpcode = User->opcode(); + + if (isWordConsumer(UserOpcode)) { + if (Demand.Bits < 32) + return false; + continue; + } + if (UserOpcode == instructionOpcode(InstructionOpcode::SEXT_B)) { + if (Demand.Bits < 8) + return false; + continue; + } + if (UserOpcode == instructionOpcode(InstructionOpcode::SEXT_H) || + UserOpcode == instructionOpcode(InstructionOpcode::ZEXT_H_RV64)) { + if (Demand.Bits < 16) + return false; + continue; + } + if (UserOpcode == instructionOpcode(InstructionOpcode::SRLI)) { + const std::int64_t *Amount = immediateOperand(*User, 2); + if (Amount == nullptr || *Amount < 0 || *Amount >= 64 || + Demand.Bits <= static_cast<unsigned>(*Amount)) { + return false; + } + Worklist.push_back({User, Demand.Bits - static_cast<unsigned>(*Amount)}); + continue; + } + if (UserOpcode == instructionOpcode(InstructionOpcode::SLLI) || + UserOpcode == instructionOpcode(InstructionOpcode::SLLIW)) { + const std::int64_t *Amount = immediateOperand(*User, 2); + const unsigned Width = + UserOpcode == instructionOpcode(InstructionOpcode::SLLIW) ? 32U : 64U; + if (Amount == nullptr || *Amount < 0 || static_cast<unsigned>(*Amount) >= Width) + return false; + const unsigned Shift = static_cast<unsigned>(*Amount); + if (Demand.Bits < Width - Shift) + Worklist.push_back({User, Demand.Bits + Shift}); + continue; + } + if (UserOpcode == instructionOpcode(InstructionOpcode::ANDI) || + UserOpcode == instructionOpcode(InstructionOpcode::ORI)) { + const std::int64_t *Immediate = immediateOperand(*User, 2); + if (Immediate == nullptr) + return false; + const std::uint64_t Mask = static_cast<std::uint64_t>(*Immediate); + const unsigned Required = UserOpcode == instructionOpcode(InstructionOpcode::ANDI) + ? static_cast<unsigned>(std::bit_width(Mask)) + : static_cast<unsigned>(std::bit_width(~Mask)); + if (Demand.Bits < Required) + Worklist.push_back({User, Demand.Bits}); + continue; + } + if (UserOpcode == instructionOpcode(InstructionOpcode::SLL) || + UserOpcode == instructionOpcode(InstructionOpcode::SH1ADD) || + UserOpcode == instructionOpcode(InstructionOpcode::SH2ADD) || + UserOpcode == instructionOpcode(InstructionOpcode::SH3ADD)) { + if (*UseIndex == 2) { + if (Demand.Bits < 6) + return false; + } else { + Worklist.push_back({User, Demand.Bits}); + } + continue; + } + if (UserOpcode == instructionOpcode(InstructionOpcode::SRA) || + UserOpcode == instructionOpcode(InstructionOpcode::SRL)) { + if (*UseIndex != 2 || Demand.Bits < 6) + return false; + continue; + } + if (UserOpcode == instructionOpcode(InstructionOpcode::ADD_UW) || + UserOpcode == instructionOpcode(InstructionOpcode::SH1ADD_UW) || + UserOpcode == instructionOpcode(InstructionOpcode::SH2ADD_UW) || + UserOpcode == instructionOpcode(InstructionOpcode::SH3ADD_UW)) { + if (*UseIndex == 1 && Demand.Bits >= 32) + continue; + Worklist.push_back({User, Demand.Bits}); + continue; + } + if (UserOpcode == instructionOpcode(InstructionOpcode::SB) || + UserOpcode == instructionOpcode(InstructionOpcode::SH) || + UserOpcode == instructionOpcode(InstructionOpcode::SW)) { + const unsigned StoredBits = UserOpcode == instructionOpcode(InstructionOpcode::SB) ? 8U + : UserOpcode == instructionOpcode(InstructionOpcode::SH) ? 16U + : 32U; + if (*UseIndex != 0 || Demand.Bits < StoredBits) + return false; + continue; + } + + const bool PropagatesLowBits = UserOpcode == opcode(GenericOpcode::COPY) || + UserOpcode == opcode(GenericOpcode::PHI) || + UserOpcode == instructionOpcode(InstructionOpcode::ADD) || + UserOpcode == instructionOpcode(InstructionOpcode::ADDI) || + UserOpcode == instructionOpcode(InstructionOpcode::AND) || + UserOpcode == instructionOpcode(InstructionOpcode::MUL) || + UserOpcode == instructionOpcode(InstructionOpcode::OR) || + UserOpcode == instructionOpcode(InstructionOpcode::SUB) || + UserOpcode == instructionOpcode(InstructionOpcode::XOR) || + UserOpcode == instructionOpcode(InstructionOpcode::XORI); + if (!PropagatesLowBits) + return false; + Worklist.push_back({User, Demand.Bits}); + } + } + return true; +} + +[[nodiscard]] bool hasAllWordUsers(const MachineInstr &Instruction, + const MachineFunction &Function) { + return hasAllNBitUsers(Instruction, Function, 32); +} + +[[nodiscard]] bool isInherentlySignExtended(const MachineInstr &Instruction) noexcept { + constexpr InstructionOpcode Opcodes[]{ + InstructionOpcode::LB, InstructionOpcode::LBU, InstructionOpcode::LH, + InstructionOpcode::LHU, InstructionOpcode::LW, InstructionOpcode::ADDIW, + InstructionOpcode::ADDW, InstructionOpcode::SUBW, InstructionOpcode::SLLIW, + InstructionOpcode::SRLIW, InstructionOpcode::SRAIW, InstructionOpcode::SLLW, + InstructionOpcode::SRLW, InstructionOpcode::SRAW, InstructionOpcode::MULW, + InstructionOpcode::DIVW, InstructionOpcode::DIVUW, InstructionOpcode::REMW, + InstructionOpcode::REMUW, InstructionOpcode::LR_W, InstructionOpcode::SC_W, + InstructionOpcode::AMOSWAP_W, InstructionOpcode::AMOADD_W, InstructionOpcode::AMOXOR_W, + InstructionOpcode::AMOAND_W, InstructionOpcode::AMOOR_W, InstructionOpcode::AMOMIN_W, + InstructionOpcode::AMOMAX_W, InstructionOpcode::AMOMINU_W, InstructionOpcode::AMOMAXU_W, + InstructionOpcode::FCVT_W_S, InstructionOpcode::FCVT_WU_S, InstructionOpcode::FCVT_W_D, + InstructionOpcode::FCVT_WU_D, InstructionOpcode::FMV_X_W, + }; + return std::ranges::any_of(Opcodes, + [&](InstructionOpcode Value) { return isOpcode(Instruction, Value); }); +} + +[[nodiscard]] bool addVirtualRegister(std::vector<Register> &Worklist, Register Reg) { + if (!Reg.isVirtual()) + return false; + Worklist.push_back(Reg); + return true; +} + +[[nodiscard]] bool isSignExtendedWord(Register Source, MachineFunction &Function, + std::vector<MachineInstr *> &FixableDefinitions) { + if (isZeroRegister(Source)) + return true; + if (!Source.isVirtual()) + return false; + + std::vector<Register> Visited; + std::vector<Register> Worklist{Source}; + MachineRegisterInfo &Registers = Function.registerInfo(); + while (!Worklist.empty()) { + const Register Reg = Worklist.back(); + Worklist.pop_back(); + if (std::ranges::find(Visited, Reg) != Visited.end()) + continue; + Visited.push_back(Reg); + + MachineInstr *Definition = Registers.getVRegDef(Reg); + if (Definition == nullptr) + return false; + if (isInherentlySignExtended(*Definition)) + continue; + + const Register *FirstSource = registerOperand(*Definition, 1); + if (isOpcode(*Definition, InstructionOpcode::SRAI) || + isOpcode(*Definition, InstructionOpcode::SRLI)) { + const std::int64_t *Amount = immediateOperand(*Definition, 2); + if (Amount != nullptr && ((isOpcode(*Definition, InstructionOpcode::SRAI) && *Amount >= 32) || + (isOpcode(*Definition, InstructionOpcode::SRLI) && *Amount > 32))) { + continue; + } + } + if (isOpcode(*Definition, InstructionOpcode::ADDI) && FirstSource != nullptr && + isZeroRegister(*FirstSource)) { + continue; + } + if (isOpcode(*Definition, InstructionOpcode::ANDI) || + isOpcode(*Definition, InstructionOpcode::ORI)) { + const std::int64_t *Immediate = immediateOperand(*Definition, 2); + if (Immediate != nullptr) { + const bool UnsignedElevenBit = *Immediate >= 0 && *Immediate < 2048; + if ((isOpcode(*Definition, InstructionOpcode::ANDI) && UnsignedElevenBit) || + (isOpcode(*Definition, InstructionOpcode::ORI) && !UnsignedElevenBit)) { + continue; + } + } + } + if (Definition->opcode() == opcode(GenericOpcode::COPY)) { + if (FirstSource == nullptr || + (!isZeroRegister(*FirstSource) && !addVirtualRegister(Worklist, *FirstSource))) { + return false; + } + continue; + } + + const bool PropagatesFirst = isOpcode(*Definition, InstructionOpcode::REM) || + isOpcode(*Definition, InstructionOpcode::ANDI) || + isOpcode(*Definition, InstructionOpcode::ORI) || + isOpcode(*Definition, InstructionOpcode::XORI) || + isOpcode(*Definition, InstructionOpcode::SRAI); + if (PropagatesFirst) { + if (FirstSource == nullptr || !addVirtualRegister(Worklist, *FirstSource)) + return false; + continue; + } + + const bool PropagatesAll = isOpcode(*Definition, InstructionOpcode::REMU) || + isOpcode(*Definition, InstructionOpcode::AND) || + isOpcode(*Definition, InstructionOpcode::OR) || + isOpcode(*Definition, InstructionOpcode::XOR) || + isOpcode(*Definition, InstructionOpcode::MIN) || + isOpcode(*Definition, InstructionOpcode::MINU) || + isOpcode(*Definition, InstructionOpcode::MAX) || + isOpcode(*Definition, InstructionOpcode::MAXU) || + Definition->opcode() == opcode(GenericOpcode::PHI); + if (PropagatesAll) { + const std::size_t Step = Definition->opcode() == opcode(GenericOpcode::PHI) ? 2U : 1U; + const std::size_t End = + Definition->opcode() == opcode(GenericOpcode::PHI) ? Definition->operandCount() : 3U; + for (std::size_t Index = 1; Index < End; Index += Step) { + const Register *Input = registerOperand(*Definition, Index); + if (Input == nullptr || + (!isZeroRegister(*Input) && !addVirtualRegister(Worklist, *Input))) { + return false; + } + } + continue; + } + + const bool IsFixable = isOpcode(*Definition, InstructionOpcode::ADDI) || + isOpcode(*Definition, InstructionOpcode::ADD) || + isOpcode(*Definition, InstructionOpcode::LD) || + isOpcode(*Definition, InstructionOpcode::LWU) || + isOpcode(*Definition, InstructionOpcode::MUL) || + isOpcode(*Definition, InstructionOpcode::SLLI) || + isOpcode(*Definition, InstructionOpcode::SUB); + if (!IsFixable || !hasAllWordUsers(*Definition, Function)) + return false; + if (isOpcode(*Definition, InstructionOpcode::SLLI)) { + const std::int64_t *Amount = immediateOperand(*Definition, 2); + if (Amount == nullptr || *Amount < 0 || *Amount >= 32) + return false; + } + if (isOpcode(*Definition, InstructionOpcode::LD)) { + if (Definition->memOperands().size() != 1 || Definition->memOperands().front() == nullptr || + Definition->memOperands().front()->isVolatile()) { + return false; + } + } + if (std::ranges::find(FixableDefinitions, Definition) == FixableDefinitions.end()) + FixableDefinitions.push_back(Definition); + } + return true; +} + +[[nodiscard]] std::optional<InstructionOpcode> wordOpcode(Opcode Opcode) noexcept { + constexpr std::pair<InstructionOpcode, InstructionOpcode> Mappings[]{ + {InstructionOpcode::ADDI, InstructionOpcode::ADDIW}, + {InstructionOpcode::ADD, InstructionOpcode::ADDW}, + {InstructionOpcode::LD, InstructionOpcode::LW}, + {InstructionOpcode::LWU, InstructionOpcode::LW}, + {InstructionOpcode::MUL, InstructionOpcode::MULW}, + {InstructionOpcode::SLLI, InstructionOpcode::SLLIW}, + {InstructionOpcode::SUB, InstructionOpcode::SUBW}, + }; + const auto *const Mapping = std::ranges::find_if( + Mappings, [Opcode](const auto &Entry) { return instructionOpcode(Entry.first) == Opcode; }); + return Mapping == std::end(Mappings) ? std::nullopt + : std::optional<InstructionOpcode>{Mapping->second}; +} + +void clearWordInvalidFlags(MachineInstr &Instruction) { + MachineInstrFlags Flags = Instruction.flags(); + Flags.clear(MachineInstrFlag::NoSignedWrap); + Flags.clear(MachineInstrFlag::NoUnsignedWrap); + Flags.clear(MachineInstrFlag::Exact); + Instruction.setFlags(Flags); +} + +[[nodiscard]] Expected<bool> constrainReplacementRegister(MachineFunction &Function, + const TargetDescription &Target, + Register Source, Register Destination) { + auto SourceInfo = Function.registerInfo().virtualRegisterInfo(Source); + if (!SourceInfo) + return std::unexpected(SourceInfo.error()); + auto DestinationInfo = Function.registerInfo().virtualRegisterInfo(Destination); + if (!DestinationInfo) + return std::unexpected(DestinationInfo.error()); + const std::optional<RegisterClassID> SourceClassValue = SourceInfo->get().RegClass; + const std::optional<RegisterClassID> DestinationClassValue = DestinationInfo->get().RegClass; + if (!SourceClassValue || !DestinationClassValue) + return true; + const RegisterClassID SourceClass = *SourceClassValue; + const RegisterClassID DestinationClass = *DestinationClassValue; + if (Target.registers().isSubclass(SourceClass, DestinationClass)) + return true; + if (!Target.registers().isSubclass(DestinationClass, SourceClass)) + return false; + auto Constrained = Function.registerInfo().setRegisterClass(Source, DestinationClass); + if (!Constrained) + return std::unexpected(Constrained.error()); + return true; +} + +[[nodiscard]] Expected<void> clearKillFlags(MachineFunction &Function, Register Reg) { + for (MachineOperand &Use : Function.registerInfo().useOperands(Reg, true)) { + if (!Use.registerFlags().has(RegisterOperandFlag::Kill)) + continue; + RegisterOperandFlags Flags = Use.registerFlags(); + Flags.clear(RegisterOperandFlag::Kill); + auto Updated = Use.setRegisterFlags(Flags); + if (!Updated) + return std::unexpected(Updated.error()); + } + return {}; +} + +[[nodiscard]] Expected<bool> removeRedundantSignExtensions(MachineFunction &Function, + const TargetDescription &Target) { + bool Changed = false; + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) { + for (MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr;) { + MachineInstr *Next = Instruction->next(); + const std::int64_t *Immediate = immediateOperand(*Instruction, 2); + if (!isOpcode(*Instruction, InstructionOpcode::ADDIW) || Immediate == nullptr || + *Immediate != 0) { + Instruction = Next; + continue; + } + const Register *Destination = registerOperand(*Instruction, 0); + const Register *Source = registerOperand(*Instruction, 1); + if (Destination == nullptr || Source == nullptr || !Destination->isVirtual() || + !Source->isVirtual()) { + Instruction = Next; + continue; + } + + std::vector<MachineInstr *> FixableDefinitions; + if (!hasAllWordUsers(*Instruction, Function) && + !isSignExtendedWord(*Source, Function, FixableDefinitions)) { + Instruction = Next; + continue; + } + auto Constrained = constrainReplacementRegister(Function, Target, *Source, *Destination); + if (!Constrained) + return std::unexpected(Constrained.error()); + if (!*Constrained) { + Instruction = Next; + continue; + } + for (MachineInstr *Definition : FixableDefinitions) { + const auto Replacement = wordOpcode(Definition->opcode()); + if (!Replacement) + return makeUnexpected(ErrorCode::InternalError, + "W-instruction optimization lost a fixable opcode mapping"); + auto Updated = Definition->setOpcode(instructionOpcode(*Replacement)); + if (!Updated) + return std::unexpected(Updated.error()); + clearWordInvalidFlags(*Definition); + } + auto Replaced = Function.registerInfo().replaceRegWith(*Destination, *Source); + if (!Replaced) + return std::unexpected(Replaced.error()); + auto Cleared = clearKillFlags(Function, *Source); + if (!Cleared) + return std::unexpected(Cleared.error()); + auto Removed = Instruction->eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + Changed = true; + Instruction = Next; + } + } + return Changed; +} + +[[nodiscard]] Expected<bool> canonicalizeWordSuffixes(MachineFunction &Function, + bool PreferWordInstructions) { + bool Changed = false; + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; Block = Block->next()) { + for (MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + std::optional<InstructionOpcode> WordOpcode; + std::optional<InstructionOpcode> NonWordOpcode; + const Opcode Original = Instruction->opcode(); + if (Original == instructionOpcode(InstructionOpcode::ADDW)) + NonWordOpcode = InstructionOpcode::ADD; + else if (Original == instructionOpcode(InstructionOpcode::ADDIW)) + NonWordOpcode = InstructionOpcode::ADDI; + else if (Original == instructionOpcode(InstructionOpcode::MULW)) + NonWordOpcode = InstructionOpcode::MUL; + else if (Original == instructionOpcode(InstructionOpcode::SLLIW)) + NonWordOpcode = InstructionOpcode::SLLI; + else if (Original == instructionOpcode(InstructionOpcode::SUBW)) + NonWordOpcode = InstructionOpcode::SUB; + else if (Original == instructionOpcode(InstructionOpcode::ADD)) + WordOpcode = InstructionOpcode::ADDW; + else if (Original == instructionOpcode(InstructionOpcode::ADDI)) + WordOpcode = InstructionOpcode::ADDIW; + else if (Original == instructionOpcode(InstructionOpcode::SUB)) + WordOpcode = InstructionOpcode::SUBW; + else if (Original == instructionOpcode(InstructionOpcode::MUL)) + WordOpcode = InstructionOpcode::MULW; + else if (Original == instructionOpcode(InstructionOpcode::SLLI)) { + const std::int64_t *Amount = immediateOperand(*Instruction, 2); + if (Amount == nullptr || *Amount < 0 || *Amount >= 32) + continue; + WordOpcode = InstructionOpcode::SLLIW; + } else if (Original == instructionOpcode(InstructionOpcode::LD)) { + if (Instruction->memOperands().size() != 1 || + Instruction->memOperands().front() == nullptr || + Instruction->memOperands().front()->isVolatile()) { + continue; + } + WordOpcode = InstructionOpcode::LW; + } else if (Original == instructionOpcode(InstructionOpcode::LWU)) { + WordOpcode = InstructionOpcode::LW; + } else { + continue; + } + + if (!PreferWordInstructions && NonWordOpcode && hasAllWordUsers(*Instruction, Function)) { + auto Updated = Instruction->setOpcode(instructionOpcode(*NonWordOpcode)); + if (!Updated) + return std::unexpected(Updated.error()); + Changed = true; + continue; + } + if ((PreferWordInstructions || Original == instructionOpcode(InstructionOpcode::LWU)) && + WordOpcode && hasAllWordUsers(*Instruction, Function)) { + auto Updated = Instruction->setOpcode(instructionOpcode(*WordOpcode)); + if (!Updated) + return std::unexpected(Updated.error()); + clearWordInvalidFlags(*Instruction); + Changed = true; + } + } + } + return Changed; +} + +[[nodiscard]] Expected<bool> runImplementation(MachineFunction &Function, + const TargetDescription &Target, + bool PreferWordInstructions) { + auto Removed = removeRedundantSignExtensions(Function, Target); + if (!Removed) + return std::unexpected(Removed.error()); + auto Canonicalized = canonicalizeWordSuffixes(Function, PreferWordInstructions); + if (!Canonicalized) + return std::unexpected(Canonicalized.error()); + return *Removed || *Canonicalized; +} + +class RISCVOptWInstrsPass final : public MachineFunctionPass { +public: + explicit RISCVOptWInstrsPass(const RISCVSubtarget &Subtarget) noexcept : Subtarget(&Subtarget) {} + + [[nodiscard]] const MachinePassContract &contract() const noexcept override { + static const MachinePassContract Contract = [] { + MachinePassContract Value{ + .Name = "riscv-opt-w-instrs", + .Requires = {MachineFunctionProperty::IsSSA}, + .Sets = {}, + .Clears = {}, + .RequiredAnalyses = {}, + .VerificationStage = std::nullopt, + }; + Value.ThreadSafety = PassThreadSafety::ThreadSafe; + Value.DeclaredChanges = {MachinePassChange::Instructions, MachinePassChange::Operands, + MachinePassChange::Registers}; + return Value; + }(); + return Contract; + } + + [[nodiscard]] Expected<MachinePassResult> run(MachineFunction &Function, + const TargetDescription &Target, + MachineFunctionAnalysisManager &) override { + if (Subtarget == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, + "RISC-V W-instruction optimization requires a subtarget"); + const bool PreferWordInstructions = + Subtarget->features().has(RISCVSubtargetOption::PreferWordInstructions); + auto Changed = runImplementation(Function, Target, PreferWordInstructions); + if (!Changed) + return std::unexpected(Changed.error()); + + MachinePassResult Result; + Result.Changed = *Changed; + if (Result.Changed) { + Result.Changes = {MachinePassChange::Instructions, MachinePassChange::Operands, + MachinePassChange::Registers}; + Result.Preserved = PreservedAnalyses::none(); + } else { + Result.Preserved = PreservedAnalyses::all(); + } + return Result; + } + +private: + const RISCVSubtarget *Subtarget = nullptr; +}; + +} // namespace + +std::unique_ptr<MachineFunctionPass> createOptWInstrsPass(const RISCVSubtarget &Subtarget) { + return std::make_unique<RISCVOptWInstrsPass>(Subtarget); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVOptimizationPipeline.cpp b/backend/lib/Target/RISCV/RISCVOptimizationPipeline.cpp new file mode 100644 index 0000000..e81ee54 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVOptimizationPipeline.cpp @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "RISCVInstrInfo.h" + +namespace rcc::riscv { + +MachineOptimizationPipelineTargetInfo optimizationPipelineInfo(const RISCVSubtarget &Subtarget, + bool EmitSchedulerDebugOutput, + bool EmitFrameDebugOutput) noexcept { + return { + .InstrInfo = instrInfo(), + .Combiner = machineCombinerInfo(), + .MemoryFolding = memoryFoldingInfo(), + .FrameLowering = Subtarget.frameLowering(), + .PseudoExpansion = pseudoExpansionInfo(), + .SchedulingModel = Subtarget.schedulingModel(), + .SchedulerOptions = {.EmitDebugOutput = EmitSchedulerDebugOutput}, + .FrameOptions = {.EmitDebugOutput = EmitFrameDebugOutput}, + }; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVOutliner.cpp b/backend/lib/Target/RISCV/RISCVOutliner.cpp new file mode 100644 index 0000000..a3b7641 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVOutliner.cpp @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "RISCVInstrInfo.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineOutliner.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVPipeline.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <algorithm> +#include <array> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <ranges> +#include <string> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +constexpr std::uint32_t CallTargetFlag = 1; +constexpr std::uint32_t PCRelativeLowTargetFlag = 4; + +enum class OutlinerConstruction : std::uint32_t { + TailCall, + Default, + RegisterSave, +}; + +[[nodiscard]] bool isUnsafeOperand(const MachineOperand &Operand) noexcept { + switch (Operand.kind()) { + case MachineOperand::Kind::BasicBlock: + case MachineOperand::Kind::FrameIndex: + case MachineOperand::Kind::ConstantPoolIndex: + case MachineOperand::Kind::JumpTableIndex: + case MachineOperand::Kind::BlockAddress: + case MachineOperand::Kind::TargetIndex: + return true; + case MachineOperand::Kind::Register: { + const auto Reg = Operand.registerValue(); + return Reg && Reg->isVirtual(); + } + case MachineOperand::Kind::Immediate: + case MachineOperand::Kind::IntegerImmediate: + case MachineOperand::Kind::FloatingImmediate: + case MachineOperand::Kind::GlobalSymbol: + case MachineOperand::Kind::ExternalSymbol: + case MachineOperand::Kind::RegisterMask: + case MachineOperand::Kind::LiveOutRegisterMask: + case MachineOperand::Kind::Metadata: + case MachineOperand::Kind::MCSymbol: + case MachineOperand::Kind::CFIIndex: + case MachineOperand::Kind::Intrinsic: + case MachineOperand::Kind::Predicate: + case MachineOperand::Kind::ShuffleMask: + case MachineOperand::Kind::DebugInstrRef: + case MachineOperand::Kind::LaneMask: + return false; + case MachineOperand::Kind::Count: + return true; + } + return true; +} + +[[nodiscard]] std::vector<PhysicalRegister> +sortedRegisters(std::span<const PhysicalRegister> Registers) { + std::vector<PhysicalRegister> Result(Registers.begin(), Registers.end()); + std::ranges::sort(Result, {}, &PhysicalRegister::number); + Result.erase(std::unique(Result.begin(), Result.end()), Result.end()); + return Result; +} + +class RISCVOutlinerInfo final : public TargetMachineOutlinerInfo { +public: + RISCVOutlinerInfo(bool FunctionSections, bool EnableRegisterSave, + std::array<PhysicalRegister, 9> Registers) + : FunctionSections(FunctionSections), EnableRegisterSave(EnableRegisterSave), + X0(Registers[0]), X2(Registers[1]), X5(Registers[2]), X6(Registers[3]), + SaveRegisters{Registers[3], Registers[4], Registers[5], + Registers[6], Registers[7], Registers[8]} {} + + [[nodiscard]] bool isFunctionSafeToOutlineFrom( + const MachineFunction &, + const MachineOutlinerFunctionProperties &Properties) const noexcept override { + return !Properties.LinkOnceODR && !Properties.HasSection; + } + + [[nodiscard]] MachineOutlinerInstrType + getOutliningType(const MachineInstr &Instruction, + const MachineOutlinerFunctionProperties &Properties, + const TargetDescription &Target) const noexcept override { + if (Instruction.isDebugInstr() || Instruction.opcode() == opcode(GenericOpcode::EH_LABEL) || + Instruction.opcode() == opcode(GenericOpcode::GC_LABEL) || + Instruction.opcode() == opcode(GenericOpcode::ANNOTATION_LABEL)) { + return MachineOutlinerInstrType::Invisible; + } + if (Instruction.opcode() == opcode(GenericOpcode::CFI_INSTRUCTION)) + return MachineOutlinerInstrType::Legal; + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction.opcode()); + if (Descriptor == nullptr) + return MachineOutlinerInstrType::Illegal; + for (const MachineOperand &Operand : Instruction.operands()) { + if (isUnsafeOperand(Operand)) + return MachineOutlinerInstrType::Illegal; + if ((Operand.targetFlags() & PCRelativeLowTargetFlag) != 0 && + (FunctionSections || Properties.HasComdat || Properties.HasSection || + Properties.HasSectionPrefix)) { + return MachineOutlinerInstrType::Illegal; + } + } + if (Descriptor->Flags.has(InstructionFlag::Return)) + return MachineOutlinerInstrType::LegalTerminator; + if (Descriptor->Flags.has(InstructionFlag::Terminator) || + Descriptor->Flags.has(InstructionFlag::Branch) || + Descriptor->Flags.has(InstructionFlag::Call) || + Descriptor->Flags.has(InstructionFlag::NotDuplicable) || + Descriptor->Flags.has(InstructionFlag::Convergent) || + Descriptor->Flags.has(InstructionFlag::UnmodeledSideEffects)) { + return MachineOutlinerInstrType::Illegal; + } + return MachineOutlinerInstrType::Legal; + } + + [[nodiscard]] Expected<bool> analyzeCandidate(MachineOutlinerCandidate &Candidate, + const TargetDescription &Target) const override { + std::size_t CandidateCFICount = 0; + for (const MachineInstr *Instruction : Candidate.Instructions) { + if (Instruction->opcode() == opcode(GenericOpcode::CFI_INSTRUCTION)) + ++CandidateCFICount; + } + + const InstructionDescriptor *LastDescriptor = + Target.instructionDescriptor(Candidate.back().opcode()); + const bool IsTail = + LastDescriptor != nullptr && LastDescriptor->Flags.has(InstructionFlag::Return); + if (CandidateCFICount != 0 && + (!IsTail || CandidateCFICount != Candidate.Function->cfiInfo().instructions().size())) { + return false; + } + if (IsTail) { + if (!Candidate.isRegisterAvailable(X6, Target)) + return false; + Candidate.CallConstructionID = static_cast<std::uint32_t>(OutlinerConstruction::TailCall); + Candidate.CallOverhead = 8; + return true; + } + + if (!Candidate.isRegisterAvailable(X5, Target)) { + if (Candidate.containsRegister(X5, Target) || !EnableRegisterSave) + return false; + for (const PhysicalRegister Reg : SaveRegisters) { + if (Candidate.isRegisterAvailable(Reg, Target)) { + Candidate.SaveRegister = Reg; + Candidate.CallConstructionID = + static_cast<std::uint32_t>(OutlinerConstruction::RegisterSave); + Candidate.CallOverhead = 16; + return true; + } + } + return false; + } + Candidate.CallConstructionID = static_cast<std::uint32_t>(OutlinerConstruction::Default); + Candidate.CallOverhead = 8; + return true; + } + + [[nodiscard]] Expected<std::uint32_t> + instructionSize(const MachineInstr &Instruction, const MachineFunction &Function, + const TargetDescription &Target) const override { + return instrInfo().instructionSize(Instruction, Function, Target); + } + + [[nodiscard]] std::uint32_t + frameOverhead(const MachineOutlinerCandidate &Candidate) const noexcept override { + return Candidate.CallConstructionID == + static_cast<std::uint32_t>(OutlinerConstruction::TailCall) + ? 0 + : 4; + } + + [[nodiscard]] Expected<void> insertOutlinedCall(MachineOutlinerCandidate &Candidate, + std::string OutlinedFunctionName, + const TargetDescription &Target) const override { + MachineIRBuilder Builder(*Candidate.Function, Target); + auto InsertPoint = Builder.setInsertPointBefore(Candidate.front()); + if (!InsertPoint) + return std::unexpected(InsertPoint.error()); + const auto Construction = static_cast<OutlinerConstruction>(Candidate.CallConstructionID); + const Register StackPointer = Register::fromPhysical(X2); + const Register Link = Register::fromPhysical(X5); + + if (Construction == OutlinerConstruction::TailCall) { + auto Pending = Builder.buildInstr(instructionOpcode(InstructionOpcode::PseudoTAIL)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Added = Pending->addGlobalSymbol(std::move(OutlinedFunctionName), 0, CallTargetFlag); + !Added) { + return std::unexpected(Added.error()); + } + if (auto Added = Pending->addImplicitUse(StackPointer); !Added) + return std::unexpected(Added.error()); + for (const PhysicalRegister Reg : Candidate.DefinedRegisters) { + if (auto Added = Pending->addImplicitDef(Register::fromPhysical(Reg)); !Added) + return std::unexpected(Added.error()); + } + if (auto Added = Pending->addImplicitUse(StackPointer); !Added) + return std::unexpected(Added.error()); + for (const PhysicalRegister Reg : sortedRegisters(Candidate.LiveIns)) { + if (auto Added = Pending->addImplicitUse(Register::fromPhysical(Reg)); !Added) + return std::unexpected(Added.error()); + } + auto Built = Pending->build(); + return Built ? Expected<void>{} : std::unexpected(Built.error()); + } + + if (Construction == OutlinerConstruction::RegisterSave) { + auto Save = Builder.buildInstr(instructionOpcode(InstructionOpcode::ADDI)); + if (!Save) + return std::unexpected(Save.error()); + if (auto Added = Save->addDef(Register::fromPhysical(Candidate.SaveRegister)); !Added) + return std::unexpected(Added.error()); + if (auto Added = Save->addUse(Link); !Added) + return std::unexpected(Added.error()); + if (auto Added = Save->addImmediate(0); !Added) + return std::unexpected(Added.error()); + auto Built = Save->build(); + if (!Built) + return std::unexpected(Built.error()); + } + + auto Call = Builder.buildInstr(instructionOpcode(InstructionOpcode::PseudoCALLReg)); + if (!Call) + return std::unexpected(Call.error()); + if (auto Added = Call->addDef(Link); !Added) + return std::unexpected(Added.error()); + if (auto Added = Call->addGlobalSymbol(std::move(OutlinedFunctionName), 0, CallTargetFlag); + !Added) { + return std::unexpected(Added.error()); + } + if (auto Added = Call->addImplicitDef(Link); !Added) + return std::unexpected(Added.error()); + for (const PhysicalRegister Reg : Candidate.DefinedRegisters) { + if (Reg == X5) + continue; + if (auto Added = Call->addImplicitDef(Register::fromPhysical(Reg)); !Added) + return std::unexpected(Added.error()); + } + std::vector<PhysicalRegister> Uses = Candidate.LiveIns; + if (Construction == OutlinerConstruction::RegisterSave) + Uses.push_back(Candidate.SaveRegister); + for (const PhysicalRegister Reg : sortedRegisters(Uses)) { + if (auto Added = Call->addImplicitUse(Register::fromPhysical(Reg)); !Added) + return std::unexpected(Added.error()); + } + auto BuiltCall = Call->build(); + if (!BuiltCall) + return std::unexpected(BuiltCall.error()); + + if (Construction == OutlinerConstruction::RegisterSave) { + auto Restore = Builder.buildInstr(instructionOpcode(InstructionOpcode::ADDI)); + if (!Restore) + return std::unexpected(Restore.error()); + if (auto Added = Restore->addDef(Link); !Added) + return std::unexpected(Added.error()); + if (auto Added = Restore->addUse(Register::fromPhysical(Candidate.SaveRegister)); !Added) + return std::unexpected(Added.error()); + if (auto Added = Restore->addImmediate(0); !Added) + return std::unexpected(Added.error()); + auto Built = Restore->build(); + if (!Built) + return std::unexpected(Built.error()); + } + return {}; + } + + [[nodiscard]] Expected<void> buildOutlinedFrame(MachineFunction &Function, + MachineBasicBlock &Block, + const MachineOutlinerCandidate &Candidate, + const TargetDescription &Target) const override { + if (Candidate.CallConstructionID == + static_cast<std::uint32_t>(OutlinerConstruction::TailCall)) { + return {}; + } + auto AddedLiveIn = Block.addLiveIn(X5); + if (!AddedLiveIn) + return std::unexpected(AddedLiveIn.error()); + MachineIRBuilder Builder(Function, Target); + auto InsertPoint = Builder.setInsertPoint(Block); + if (!InsertPoint) + return std::unexpected(InsertPoint.error()); + auto Return = Builder.buildInstr(instructionOpcode(InstructionOpcode::JALR)); + if (!Return) + return std::unexpected(Return.error()); + if (auto Added = Return->addDef(Register::fromPhysical(X0)); !Added) + return std::unexpected(Added.error()); + if (auto Added = Return->addUse(Register::fromPhysical(X5)); !Added) + return std::unexpected(Added.error()); + if (auto Added = Return->addImmediate(0); !Added) + return std::unexpected(Added.error()); + auto Built = Return->build(); + return Built ? Expected<void>{} : std::unexpected(Built.error()); + } + +private: + bool FunctionSections = false; + bool EnableRegisterSave = false; + PhysicalRegister X0; + PhysicalRegister X2; + PhysicalRegister X5; + PhysicalRegister X6; + std::array<PhysicalRegister, 6> SaveRegisters; +}; + +} // namespace + +Expected<std::unique_ptr<TargetMachineOutlinerInfo>> +createMachineOutlinerInfo(bool FunctionSections, bool EnableRegisterSave) { + constexpr std::array IDs{ + PhysicalRegisterID::X0, PhysicalRegisterID::X2, PhysicalRegisterID::X5, + PhysicalRegisterID::X6, PhysicalRegisterID::X7, PhysicalRegisterID::X28, + PhysicalRegisterID::X29, PhysicalRegisterID::X30, PhysicalRegisterID::X31, + }; + std::array<PhysicalRegister, IDs.size()> Registers; + for (std::size_t Index = 0; Index < IDs.size(); ++Index) { + auto Reg = physicalRegister(IDs[Index]); + if (!Reg) + return std::unexpected(Reg.error()); + Registers[Index] = *Reg; + } + return std::make_unique<RISCVOutlinerInfo>(FunctionSections, EnableRegisterSave, Registers); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVPipeline.cpp b/backend/lib/Target/RISCV/RISCVPipeline.cpp new file mode 100644 index 0000000..3e7599d --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVPipeline.cpp @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVPipeline.h" + +#include "GISel/RISCVCallLowering.h" +#include "GISel/RISCVRegisterBankInfo.h" +#include "RISCV.h" +#include "RISCVCallingConv.h" +#include "RISCVInstrInfo.h" +#include "rcc/CodeGen/CFIInstrInserter.h" +#include "rcc/CodeGen/FinalizeISel.h" +#include "rcc/CodeGen/GlobalISel/Combiner.h" +#include "rcc/CodeGen/GlobalISel/InstructionSelect.h" +#include "rcc/CodeGen/GlobalISel/Legalizer.h" +#include "rcc/CodeGen/GlobalISel/RegBankSelect.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineOptimizationPipeline.h" +#include "rcc/CodeGen/MachinePseudoExpansion.h" +#include "rcc/CodeGen/PHIElimination.h" +#include "rcc/CodeGen/RegAllocCommon.h" +#include "rcc/CodeGen/RegAllocPBQP.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/CodeGen/TwoAddressInstructionPass.h" +#include "rcc/CodeGen/VirtRegMap.h" + +#include <memory> +#include <ranges> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] LibcallLoweringInfo libcallLoweringInfo(const RISCVSubtarget &Subtarget) { + const CallingConvention Convention = + Subtarget.targetABI() == RISCVABI::LP64D ? CallingConvention::LP64D : CallingConvention::LP64; + return {Subtarget.getCallLowering(), callingConventionID(Convention)}; +} + +template <typename PassT, typename... ArgumentT> +[[nodiscard]] Expected<void> addPass(MachineFunctionPassManager &Pipeline, + ArgumentT &&...Arguments) { + return Pipeline.addPass(std::make_unique<PassT>(std::forward<ArgumentT>(Arguments)...)); +} + +[[nodiscard]] Expected<void> addInitialPipeline(MachineFunctionPassManager &Pipeline, + const RISCVSubtarget &Subtarget, + TargetPipelineOptions Options) { + auto Added = addPass<GlobalISelCombinerPass>( + Pipeline, giselCombinerTable(), giselCombinerExecutor(), "riscv-prelegalizer-combiner"); + if (!Added) + return std::unexpected(Added.error()); + Added = addPass<Legalizer>(Pipeline, Subtarget.getLegalizerInfo(), Options.Optimization, + libcallLoweringInfo(Subtarget), Options.GlobalISelAbort); + if (!Added) + return std::unexpected(Added.error()); + Added = addPass<RegBankSelect>(Pipeline, Subtarget.getRegBankInfo()); + if (!Added) + return std::unexpected(Added.error()); + Added = addPass<InstructionSelect>(Pipeline, Subtarget.getInstructionSelector()); + if (!Added) + return std::unexpected(Added.error()); + return addPass<FinalizeISelPass>(Pipeline, finalizeISelInfo()); +} + +[[nodiscard]] Expected<void> addSSAExitPipeline(MachineFunctionPassManager &Pipeline, + const RISCVSubtarget &Subtarget, + TargetPipelineOptions Options) { + auto Added = addSelectedSSAOptimizationPipeline( + Pipeline, optimizationPipelineInfo(Subtarget, Options.DebugMachineScheduler, + Options.DebugPrologueEpilogue)); + if (!Added) + return std::unexpected(Added.error()); + Added = Pipeline.addPass(createOptWInstrsPass(Subtarget)); + if (!Added) + return std::unexpected(Added.error()); + Added = addPass<PHIEliminationPass>(Pipeline, instrInfo()); + if (!Added) + return std::unexpected(Added.error()); + return addPass<TwoAddressInstructionPass>(Pipeline, instrInfo()); +} + +[[nodiscard]] Expected<void> addRegisterAllocationPipeline(MachineFunctionPassManager &Pipeline, + const RISCVSubtarget &Subtarget, + TargetPipelineOptions Options) { + auto Added = addPass<PreRATargetPseudoExpansionPass>(Pipeline, pseudoExpansionInfo()); + if (!Added) + return std::unexpected(Added.error()); + Added = Pipeline.addPass(createMergeBaseOffsetPass()); + if (!Added) + return std::unexpected(Added.error()); + Added = addPreRegisterAllocationOptimizationPipeline( + Pipeline, optimizationPipelineInfo(Subtarget, Options.DebugMachineScheduler, + Options.DebugPrologueEpilogue)); + if (!Added) + return std::unexpected(Added.error()); + Added = addPass<PBQPAllocatorPass>(Pipeline, registerAllocationInfo()); + if (!Added) + return std::unexpected(Added.error()); + return addPass<VirtualRegisterRewritePass>(Pipeline); +} + +[[nodiscard]] MachineFunctionPassManager::PassList +postRAPseudoExpansionPasses(const RISCVSubtarget &Subtarget) { + MachineFunctionPassManager::PassList Passes; + Passes.push_back(createMakeCompressiblePass(Subtarget)); + Passes.push_back(createMoveMergePass(Subtarget)); + Passes.push_back(createPushPopOptimizationPass(Subtarget)); + return Passes; +} + +[[nodiscard]] Expected<void> addCompleteRegistry(MachineFunctionPassManager &Pipeline, + const RISCVSubtarget &Subtarget, + TargetPipelineOptions Options) { + auto Added = addInitialPipeline(Pipeline, Subtarget, Options); + if (!Added) + return std::unexpected(Added.error()); + Added = addSSAExitPipeline(Pipeline, Subtarget, Options); + if (!Added) + return std::unexpected(Added.error()); + Added = Pipeline.addPass(createGreedyAllocatorPass(registerAllocationInfo())); + if (!Added) + return std::unexpected(Added.error()); + Added = addRegisterAllocationPipeline(Pipeline, Subtarget, Options); + if (!Added) + return std::unexpected(Added.error()); + Added = addPostRegisterAllocationPipeline( + Pipeline, + optimizationPipelineInfo(Subtarget, Options.DebugMachineScheduler, + Options.DebugPrologueEpilogue), + MachineOptimizationPipelineOptions{.UseTargetCopyInstructions = + Options.UseTargetCopyInstructions}, + postRAPseudoExpansionPasses(Subtarget)); + if (!Added) + return std::unexpected(Added.error()); + return addPass<CFIInstrInserterPass>(Pipeline, Subtarget.frameLowering()); +} + +} // namespace + +Expected<void> addCanonicalPipeline(MachineFunctionPassManager &Pipeline, + const MachineFunction &Function, + const RISCVSubtarget &Subtarget, + TargetPipelineOptions Options) { + if (!Function.properties().has(MachineFunctionProperty::Legalized)) { + auto Added = addPass<GlobalISelCombinerPass>( + Pipeline, giselCombinerTable(), giselCombinerExecutor(), "riscv-prelegalizer-combiner"); + if (!Added) + return std::unexpected(Added.error()); + Added = addPass<Legalizer>(Pipeline, Subtarget.getLegalizerInfo(), Options.Optimization, + libcallLoweringInfo(Subtarget), Options.GlobalISelAbort); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::RegBankSelected)) { + auto Added = addPass<RegBankSelect>(Pipeline, Subtarget.getRegBankInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::Selected)) { + auto Added = addPass<InstructionSelect>(Pipeline, Subtarget.getInstructionSelector()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::TiedOpsRewritten)) { + auto Added = addPass<FinalizeISelPass>(Pipeline, finalizeISelInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::TiedOpsRewritten)) { + auto Added = addSelectedSSAOptimizationPipeline( + Pipeline, optimizationPipelineInfo(Subtarget, Options.DebugMachineScheduler, + Options.DebugPrologueEpilogue)); + if (!Added) + return std::unexpected(Added.error()); + Added = Pipeline.addPass(createOptWInstrsPass(Subtarget)); + if (!Added) + return std::unexpected(Added.error()); + if (!Function.properties().has(MachineFunctionProperty::NoPHIs)) { + Added = addPass<PHIEliminationPass>(Pipeline, instrInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + } + if (!Function.properties().has(MachineFunctionProperty::TiedOpsRewritten)) { + auto Added = addPass<TwoAddressInstructionPass>(Pipeline, instrInfo()); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::NoVRegs)) { + auto Added = addRegisterAllocationPipeline(Pipeline, Subtarget, Options); + if (!Added) + return std::unexpected(Added.error()); + } + if (!Function.properties().has(MachineFunctionProperty::NoExecutablePseudos)) { + auto Added = addPostRegisterAllocationPipeline( + Pipeline, + optimizationPipelineInfo(Subtarget, Options.DebugMachineScheduler, + Options.DebugPrologueEpilogue), + MachineOptimizationPipelineOptions{.UseTargetCopyInstructions = + Options.UseTargetCopyInstructions}, + postRAPseudoExpansionPasses(Subtarget)); + if (!Added) + return std::unexpected(Added.error()); + } + return {}; +} + +Expected<std::unique_ptr<MachineFunctionPass>> +createCanonicalPipelinePass(std::string_view Name, const RISCVSubtarget &Subtarget, + TargetPipelineOptions Options) { + if (Name.empty()) + return makeUnexpected(ErrorCode::InvalidArgument, "pipeline pass name cannot be empty"); + MachineFunctionPassManager Registry; + auto Added = addCompleteRegistry(Registry, Subtarget, Options); + if (!Added) + return std::unexpected(Added.error()); + MachineFunctionPassManager::PassList Passes = std::move(Registry).takePasses(); + auto Position = std::ranges::find_if(Passes, [Name](const auto &Pass) { + return Pass != nullptr && Pass->contract().Name == Name; + }); + if (Position == Passes.end()) { + return makeUnexpected(ErrorCode::InvalidArgument, + "unknown RV64GC machine pass '" + std::string(Name) + "'"); + } + return std::move(*Position); +} + +Expected<std::vector<std::string>> canonicalPipelinePassNames(const MachineFunction &Function, + const RISCVSubtarget &Subtarget) { + MachineFunctionPassManager Pipeline; + auto Added = addCanonicalPipeline(Pipeline, Function, Subtarget); + if (!Added) + return std::unexpected(Added.error()); + MachineFunctionPassManager::PassList Passes = std::move(Pipeline).takePasses(); + std::vector<std::string> Result; + Result.reserve(Passes.size()); + for (const std::unique_ptr<MachineFunctionPass> &Pass : Passes) { + if (Pass == nullptr) + return makeUnexpected(ErrorCode::InternalError, "canonical pipeline contains a null pass"); + Result.push_back(Pass->contract().Name); + } + return Result; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVPseudoExpansion.cpp b/backend/lib/Target/RISCV/RISCVPseudoExpansion.cpp new file mode 100644 index 0000000..3b3a205 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVPseudoExpansion.cpp @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineFunctionRecords.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <cstddef> +#include <cstdint> +#include <optional> +#include <string> +#include <string_view> +#include <utility> + +namespace rcc::riscv { +namespace { + +constexpr std::uint32_t PCRelativeHighFlag = 2; +constexpr std::uint32_t PCRelativeLowFlag = 4; +constexpr std::uint32_t CopyF32Flag = 8; +constexpr std::uint32_t CopyF64Flag = 16; +constexpr std::uint32_t GOTPCRelativeHighFlag = 128; + +[[nodiscard]] std::optional<RegisterBankID> +registerBank(Register Reg, const MachineFunction &Function, const TargetDescription &Target) { + if (Reg.isVirtual()) { + auto Info = Function.registerInfo().virtualRegisterInfo(Reg); + return Info ? Info->get().Bank : std::nullopt; + } + const std::optional<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]] std::optional<LowLevelType> registerType(Register Reg, + const MachineFunction &Function) { + if (!Reg.isVirtual()) + return std::nullopt; + auto Info = Function.registerInfo().virtualRegisterInfo(Reg); + return Info ? Info->get().Type : std::nullopt; +} + +[[nodiscard]] Expected<void> copyLocation(const MachineInstr &Source, MachineInstr &Destination) { + if (Source.sourceLocation()) + Destination.setSourceLocation(*Source.sourceLocation()); + if (Source.debugLocation()) { + auto Debug = Destination.setDebugLocation(*Source.debugLocation()); + if (!Debug) + return std::unexpected(Debug.error()); + } + if (Source.origin()) + Destination.setOrigin(*Source.origin()); + return {}; +} + +[[nodiscard]] Expected<void> copyCommonDecoration(const MachineInstr &Source, + MachineInstr &Destination, bool CopyPreSymbol, + bool CopyPostSymbol, bool CopyDebugNumber) { + auto Location = copyLocation(Source, Destination); + if (!Location) + return std::unexpected(Location.error()); + Destination.setFlags(Source.flags()); + if (CopyDebugNumber && Source.debugInstrNumber() != 0) { + auto Number = Destination.setDebugInstrNumber(Source.debugInstrNumber()); + if (!Number) + return std::unexpected(Number.error()); + } + if (CopyPreSymbol) { + if (const auto Symbol = Source.preInstrSymbol()) { + auto Set = Destination.setPreInstrSymbol(std::string(*Symbol)); + if (!Set) + return std::unexpected(Set.error()); + } + } + if (CopyPostSymbol) { + if (const auto Symbol = Source.postInstrSymbol()) { + auto Set = Destination.setPostInstrSymbol(std::string(*Symbol)); + if (!Set) + return std::unexpected(Set.error()); + } + } + if (Source.heapAllocMarker()) { + auto Set = Destination.setHeapAllocMarker(*Source.heapAllocMarker()); + if (!Set) + return std::unexpected(Set.error()); + } + if (Source.pcSections()) { + auto Set = Destination.setPCSections(*Source.pcSections()); + if (!Set) + return std::unexpected(Set.error()); + } + if (Source.mmra()) { + auto Set = Destination.setMMRA(*Source.mmra()); + if (!Set) + return std::unexpected(Set.error()); + } + if (Source.cfiType()) { + auto Set = Destination.setCFIType(*Source.cfiType()); + if (!Set) + return std::unexpected(Set.error()); + } + if (const auto Symbol = Source.deactivationSymbol()) { + auto Set = Destination.setDeactivationSymbol(std::string(*Symbol)); + if (!Set) + return std::unexpected(Set.error()); + } + return {}; +} + +[[nodiscard]] Expected<std::pair<Register, Register>> +copyRegisters(const MachineInstr &Instruction) { + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *SourceOperand = Instruction.operandAt(1); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Destination == nullptr || Source == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "post-RA copy pseudo requires two register operands"); + return std::pair{*Destination, *Source}; +} + +[[nodiscard]] Expected<bool> prepareCopy(MachineInstr &Instruction, MachineFunction &Function, + const TargetDescription &Target) { + if (Instruction.opcode() != opcode(GenericOpcode::COPY)) + return false; + auto Registers = copyRegisters(Instruction); + if (!Registers) + return std::unexpected(Registers.error()); + const auto DestinationBank = registerBank(Registers->first, Function, Target); + const auto SourceBank = registerBank(Registers->second, Function, Target); + if (!DestinationBank || !SourceBank) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V copy preparation requires constrained register banks"); + if (*DestinationBank == *SourceBank) + return false; + + const auto DestinationType = registerType(Registers->first, Function); + const auto SourceType = registerType(Registers->second, Function); + const std::optional<LowLevelType> Type = DestinationType ? DestinationType : SourceType; + if (!Type || Type->isVector() || (Type->scalarBitWidth() != 32 && Type->scalarBitWidth() != 64)) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V cross-bank COPY requires a scalar 32-bit or 64-bit type"); + } + if (DestinationType && SourceType && + DestinationType->scalarBitWidth() != SourceType->scalarBitWidth()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V cross-bank COPY has mismatched value widths"); + } + const std::uint32_t Flag = Type->scalarBitWidth() == 32 ? CopyF32Flag : CopyF64Flag; + MachineOperand *DestinationOperand = Instruction.operandAt(0); + MachineOperand *SourceOperand = Instruction.operandAt(1); + const bool Changed = + DestinationOperand->targetFlags() != Flag || SourceOperand->targetFlags() != Flag; + DestinationOperand->setTargetFlags(Flag); + SourceOperand->setTargetFlags(Flag); + return Changed; +} + +[[nodiscard]] Expected<bool> expandCopy(MachineInstr &Instruction, MachineFunction &Function, + const TargetDescription &Target) { + auto Registers = copyRegisters(Instruction); + if (!Registers) + return std::unexpected(Registers.error()); + const Register Destination = Registers->first; + const Register Source = Registers->second; + if (!Destination.isPhysical() || !Source.isPhysical()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V post-RA COPY retains a virtual register"); + } + if (Destination == Source) { + auto Removed = Instruction.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; + } + const auto DestinationBank = registerBank(Destination, Function, Target); + const auto SourceBank = registerBank(Source, Function, Target); + if (!DestinationBank || !SourceBank) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V post-RA COPY references an unknown register bank"); + const RegisterBankID GPR = + RegisterBankID::fromValue(static_cast<std::uint32_t>(RegisterBank::GPR)); + const RegisterBankID FPR = + RegisterBankID::fromValue(static_cast<std::uint32_t>(RegisterBank::FPR)); + + InstructionOpcode Selected = InstructionOpcode::Invalid; + bool DuplicateSource = false; + bool AddZeroImmediate = false; + if (*DestinationBank == GPR && *SourceBank == GPR) { + Selected = InstructionOpcode::ADDI; + AddZeroImmediate = true; + } else if (*DestinationBank == FPR && *SourceBank == FPR) { + Selected = InstructionOpcode::FSGNJ_D; + DuplicateSource = true; + } else { + const std::uint32_t DestinationFlags = Instruction.operandAt(0)->targetFlags(); + const std::uint32_t SourceFlags = Instruction.operandAt(1)->targetFlags(); + if (DestinationFlags != SourceFlags || + (DestinationFlags != CopyF32Flag && DestinationFlags != CopyF64Flag)) { + return makeUnexpected(ErrorCode::PseudoExpansionFailed, + "RISC-V cross-bank COPY has no prepared transfer width"); + } + if (*DestinationBank == GPR && *SourceBank == FPR) { + Selected = + DestinationFlags == CopyF32Flag ? InstructionOpcode::FMV_X_W : InstructionOpcode::FMV_X_D; + } else if (*DestinationBank == FPR && *SourceBank == GPR) { + Selected = + DestinationFlags == CopyF32Flag ? InstructionOpcode::FMV_W_X : InstructionOpcode::FMV_D_X; + } + } + if (Selected == InstructionOpcode::Invalid) + return makeUnexpected(ErrorCode::Unsupported, "RISC-V COPY crosses unsupported banks"); + + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + auto Pending = Builder.buildInstr(instructionOpcode(Selected)); + if (!Pending) + return std::unexpected(Pending.error()); + auto Def = Pending->addDef(Destination); + if (!Def) + return std::unexpected(Def.error()); + RegisterOperandFlags SourceFlags; + const RegisterOperandFlags OriginalSourceFlags = Instruction.operandAt(1)->registerFlags(); + if (OriginalSourceFlags.has(RegisterOperandFlag::Kill)) + SourceFlags.set(RegisterOperandFlag::Kill); + if (*DestinationBank == GPR && *SourceBank == GPR && + OriginalSourceFlags.has(RegisterOperandFlag::Renamable)) { + SourceFlags.set(RegisterOperandFlag::Renamable); + } + auto Use = Pending->addUse(Source, SourceFlags); + if (!Use) + return std::unexpected(Use.error()); + if (DuplicateSource) { + auto SecondUse = Pending->addUse(Source); + if (!SecondUse) + return std::unexpected(SecondUse.error()); + } + if (AddZeroImmediate) { + auto Immediate = Pending->addImmediate(0); + if (!Immediate) + return std::unexpected(Immediate.error()); + } + auto Replacement = Pending->build(); + if (!Replacement) + return std::unexpected(Replacement.error()); + auto Decoration = copyCommonDecoration(Instruction, **Replacement, true, true, true); + if (!Decoration) + return std::unexpected(Decoration.error()); + auto Removed = Instruction.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; +} + +[[nodiscard]] Expected<bool> expandSubregisterCopy(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target) { + const Opcode Value = Instruction.opcode(); + if (Value == opcode(GenericOpcode::EXTRACT_SUBREG) || + Value == opcode(GenericOpcode::SUBREG_TO_REG)) { + const MachineOperand *Index = Instruction.operandAt(2); + const auto *Immediate = Index == nullptr ? nullptr : Index->getIf<std::int64_t>(); + if (Immediate == nullptr || *Immediate != 0) + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC does not support nonzero subregister indices"); + } else if (Value == opcode(GenericOpcode::COPY_LANEMASK)) { + const MachineOperand *MaskOperand = Instruction.operandAt(2); + const auto *Mask = MaskOperand == nullptr ? nullptr : MaskOperand->getIf<LaneMaskOperand>(); + if (Mask == nullptr || Mask->Mask.empty()) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V lane copy requires a nonempty lane mask"); + } else { + return false; + } + return expandCopy(Instruction, Function, Target); +} + +[[nodiscard]] Expected<void> addDirectTarget(MachineInstrBuilder &Builder, + const MachineOperand &Callee) { + if (const auto *Global = Callee.getIf<GlobalSymbolOperand>()) + return Builder.addGlobalSymbol(Global->Name, Global->Offset, PCRelativeHighFlag); + if (const auto *External = Callee.getIf<ExternalSymbolOperand>()) + return Builder.addExternalSymbol(External->Name, External->Offset, PCRelativeHighFlag); + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V direct call pseudo has no symbol target"); +} + +[[nodiscard]] Expected<Register> createGPRScratchRegister(MachineFunction &Function, + const TargetDescription &Target) { + const RegisterBankID GPRBank = + RegisterBankID::fromValue(static_cast<std::uint32_t>(RegisterBank::GPR)); + const RegisterClassID GPRClass = + RegisterClassID::fromValue(static_cast<std::uint32_t>(RegisterClass::GPR)); + if (Target.registers().registerBank(GPRBank) == nullptr || + Target.registers().registerClass(GPRClass) == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V pre-RA address expansion requires the GPR register class"); + } + auto Scratch = Function.registerInfo().createVirtualRegister(); + if (!Scratch) + return std::unexpected(Scratch.error()); + auto SetBank = Function.registerInfo().setRegisterBank(*Scratch, GPRBank); + if (!SetBank) + return std::unexpected(SetBank.error()); + auto SetClass = Function.registerInfo().setRegisterClass(*Scratch, GPRClass); + if (!SetClass) + return std::unexpected(SetClass.error()); + return *Scratch; +} + +[[nodiscard]] Expected<bool> expandAddressPseudo(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target, + bool LoadFromGOT) { + const MachineOperand *Destination = Instruction.operandAt(0); + const MachineOperand *Symbol = Instruction.operandAt(1); + const Register *DestinationRegister = + Destination == nullptr ? nullptr : Destination->getIf<Register>(); + if (DestinationRegister == nullptr || Symbol == nullptr || Instruction.operandCount() != 2) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V address pseudo has malformed explicit operands"); + } + if (LoadFromGOT && Instruction.memOperands().size() != 1) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V global-address pseudo requires one GOT load memory operand"); + } + if (!LoadFromGOT && !Instruction.memOperands().empty()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V local-address pseudo cannot carry memory operands"); + } + + auto Scratch = createGPRScratchRegister(Function, Target); + if (!Scratch) + return std::unexpected(Scratch.error()); + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + + auto HighPending = Builder.buildInstr(instructionOpcode(InstructionOpcode::AUIPC)); + if (!HighPending) + return std::unexpected(HighPending.error()); + auto HighDef = HighPending->addDef(*Scratch); + if (!HighDef) + return std::unexpected(HighDef.error()); + MachineOperand HighSymbol = Symbol->cloneDetached(); + HighSymbol.setTargetFlags(LoadFromGOT ? GOTPCRelativeHighFlag : PCRelativeHighFlag); + auto HighTarget = HighPending->addOperand(std::move(HighSymbol)); + if (!HighTarget) + return std::unexpected(HighTarget.error()); + auto High = HighPending->build(); + if (!High) + return std::unexpected(High.error()); + const std::string Anchor = Instruction.preInstrSymbol() + ? std::string(*Instruction.preInstrSymbol()) + : ".Lrcc_pcrel_hi_" + std::to_string(Function.stableID().value()) + + "_" + std::to_string((*High)->stableID().value()); + auto HighAnchor = (*High)->setPreInstrSymbol(Anchor); + if (!HighAnchor) + return std::unexpected(HighAnchor.error()); + auto HighDecoration = copyCommonDecoration(Instruction, **High, false, false, false); + if (!HighDecoration) + return std::unexpected(HighDecoration.error()); + + const InstructionOpcode LowOpcode = LoadFromGOT ? InstructionOpcode::LD : InstructionOpcode::ADDI; + auto LowPending = Builder.buildInstr(instructionOpcode(LowOpcode)); + if (!LowPending) + return std::unexpected(LowPending.error()); + auto LowDef = LowPending->addOperand(Destination->cloneDetached()); + if (!LowDef) + return std::unexpected(LowDef.error()); + auto LowBase = LowPending->addUse(*Scratch); + if (!LowBase) + return std::unexpected(LowBase.error()); + auto LowOffset = LowPending->addMCSymbol(Anchor, 0, PCRelativeLowFlag); + if (!LowOffset) + return std::unexpected(LowOffset.error()); + if (LoadFromGOT) { + const MachineMemOperand *Memory = Instruction.memOperands().front(); + if (Memory == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V global-address pseudo has a null memory operand"); + auto AddedMemory = LowPending->addMemOperand(*Memory); + if (!AddedMemory) + return std::unexpected(AddedMemory.error()); + } + auto Low = LowPending->build(); + if (!Low) + return std::unexpected(Low.error()); + auto LowDecoration = copyCommonDecoration(Instruction, **Low, false, true, true); + if (!LowDecoration) + return std::unexpected(LowDecoration.error()); + auto Records = + Function.records().replaceInstructionReference(Instruction.stableID(), (*Low)->stableID()); + if (!Records) + return std::unexpected(Records.error()); + auto Removed = Instruction.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; +} + +[[nodiscard]] bool isRegister(const MachineOperand &Operand, PhysicalRegister Reg) noexcept { + const Register *Value = Operand.getIf<Register>(); + return Value != nullptr && Value->physicalRegister() == Reg; +} + +[[nodiscard]] Expected<void> copyExtraImplicitOperands( + const MachineInstr &Source, std::size_t FirstImplicit, MachineInstrBuilder &Destination, + std::optional<PhysicalRegister> ExplicitDef, std::optional<PhysicalRegister> ExplicitUse) { + for (std::size_t Index = FirstImplicit; Index < Source.operandCount(); ++Index) { + const MachineOperand *Operand = Source.operandAt(Index); + if (Operand == nullptr || !Operand->isImplicit()) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call pseudo has malformed implicit operands"); + if ((ExplicitDef && Operand->isDef() && isRegister(*Operand, *ExplicitDef)) || + (ExplicitUse && Operand->isUse() && isRegister(*Operand, *ExplicitUse))) { + continue; + } + auto Added = Destination.addImplicitOperand(Operand->cloneDetached()); + if (!Added) + return std::unexpected(Added.error()); + } + return {}; +} + +[[nodiscard]] Expected<bool> expandCall(MachineInstr &Instruction, MachineFunction &Function, + const TargetDescription &Target, bool IsTail) { + MachineBasicBlock *Block = Instruction.parent(); + const bool HasExplicitLink = + Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoCALLReg); + const std::size_t CalleeIndex = HasExplicitLink ? 1U : 0U; + const MachineOperand *Callee = Instruction.operandAt(CalleeIndex); + const MachineOperand *Mask = Instruction.operandAt(CalleeIndex + 1U); + const bool HasMask = + Mask != nullptr && !Mask->isImplicit() && Mask->kind() == MachineOperand::Kind::RegisterMask; + if (Block == nullptr || Callee == nullptr || + (Mask != nullptr && !Mask->isImplicit() && !HasMask)) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call pseudo has malformed explicit operands"); + } + auto Zero = physicalRegister(PhysicalRegisterID::X0); + auto Link = physicalRegister(PhysicalRegisterID::X1); + auto Scratch = physicalRegister(PhysicalRegisterID::X6); + if (!Zero || !Link || !Scratch) + return makeUnexpected(ErrorCode::InternalError, "RISC-V call registers are unavailable"); + PhysicalRegister DestinationRegister = IsTail ? *Zero : *Link; + if (HasExplicitLink) { + const MachineOperand *Destination = Instruction.operandAt(0); + const Register *DestinationValue = + Destination == nullptr ? nullptr : Destination->getIf<Register>(); + const auto PhysicalDestination = + DestinationValue == nullptr ? std::nullopt : DestinationValue->physicalRegister(); + if (!PhysicalDestination || !Destination->isDef()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V call-register pseudo has an invalid link register"); + } + DestinationRegister = *PhysicalDestination; + } + + MachineIRBuilder Builder(Function, Target); + auto Point = Builder.setInsertPointBefore(Instruction); + if (!Point) + return std::unexpected(Point.error()); + Register Base; + std::optional<std::string> Anchor; + const bool Direct = Callee->kind() == MachineOperand::Kind::GlobalSymbol || + Callee->kind() == MachineOperand::Kind::ExternalSymbol; + if (Direct) { + const PhysicalRegister HighRegister = IsTail ? *Scratch : DestinationRegister; + Base = Register::fromPhysical(HighRegister); + Anchor = Instruction.preInstrSymbol() + ? std::optional<std::string>{std::string(*Instruction.preInstrSymbol())} + : std::optional<std::string>{".Lrcc_call_" + + std::to_string(Function.stableID().value()) + "_" + + std::to_string(Instruction.stableID().value())}; + auto HighPending = Builder.buildInstr(instructionOpcode(InstructionOpcode::AUIPC)); + if (!HighPending) + return std::unexpected(HighPending.error()); + auto HighDef = HighPending->addDef(Base); + if (!HighDef) + return std::unexpected(HighDef.error()); + auto HighTarget = addDirectTarget(*HighPending, *Callee); + if (!HighTarget) + return std::unexpected(HighTarget.error()); + auto High = HighPending->build(); + if (!High) + return std::unexpected(High.error()); + auto Symbol = (*High)->setPreInstrSymbol(*Anchor); + if (!Symbol) + return std::unexpected(Symbol.error()); + auto Decoration = copyLocation(Instruction, **High); + if (!Decoration) + return std::unexpected(Decoration.error()); + } else { + const Register *CalleeRegister = Callee->getIf<Register>(); + if (CalleeRegister == nullptr || !CalleeRegister->isPhysical()) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V indirect call retains a virtual callee register"); + Base = *CalleeRegister; + } + + const InstructionOpcode LowOpcode = + IsTail ? InstructionOpcode::JALR_TAIL : InstructionOpcode::JALR_CALL; + auto LowPending = Builder.buildInstr(instructionOpcode(LowOpcode)); + if (!LowPending) + return std::unexpected(LowPending.error()); + auto LowDef = LowPending->addDef(Register::fromPhysical(DestinationRegister)); + if (!LowDef) + return std::unexpected(LowDef.error()); + auto LowBase = LowPending->addUse(Base); + if (!LowBase) + return std::unexpected(LowBase.error()); + Expected<void> LowOffset = + Anchor ? LowPending->addMCSymbol(*Anchor, 0, PCRelativeLowFlag) : LowPending->addImmediate(0); + if (!LowOffset) + return std::unexpected(LowOffset.error()); + if (HasMask) { + auto LowMask = LowPending->addOperand(Mask->cloneDetached()); + if (!LowMask) + return std::unexpected(LowMask.error()); + } + const std::size_t FirstImplicit = CalleeIndex + 1U + static_cast<std::size_t>(HasMask); + auto Implicit = copyExtraImplicitOperands( + Instruction, FirstImplicit, *LowPending, + IsTail ? std::nullopt : std::optional<PhysicalRegister>{DestinationRegister}, std::nullopt); + if (!Implicit) + return std::unexpected(Implicit.error()); + auto Low = LowPending->build(); + if (!Low) + return std::unexpected(Low.error()); + auto Decoration = copyCommonDecoration(Instruction, **Low, !Direct, true, true); + if (!Decoration) + return std::unexpected(Decoration.error()); + auto Records = + Function.records().replaceInstructionReference(Instruction.stableID(), (*Low)->stableID()); + if (!Records) + return std::unexpected(Records.error()); + auto Removed = Instruction.eraseFromParent(); + if (!Removed) + return std::unexpected(Removed.error()); + return true; +} + +class RISCVPseudoExpansionInfo final : public TargetPseudoExpansionInfo { +public: + [[nodiscard]] Expected<bool> preparePostRAPseudo(MachineInstr &Instruction, + MachineFunction &Function, + const TargetDescription &Target) const override { + return prepareCopy(Instruction, Function, Target); + } + + [[nodiscard]] Expected<bool> expandPseudo(MachineInstr &Instruction, + MachinePseudoExpansionPhase Phase, + MachineFunction &Function, + const TargetDescription &Target) const override { + if (Phase == MachinePseudoExpansionPhase::PreRA) { + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoLLA)) + return expandAddressPseudo(Instruction, Function, Target, false); + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoLGA)) + return expandAddressPseudo(Instruction, Function, Target, true); + return false; + } + if (Phase == MachinePseudoExpansionPhase::PostRA) { + if (Instruction.opcode() == opcode(GenericOpcode::COPY)) + return expandCopy(Instruction, Function, Target); + return expandSubregisterCopy(Instruction, Function, Target); + } + if (Phase == MachinePseudoExpansionPhase::TargetLate) { + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoCALL)) + return expandCall(Instruction, Function, Target, false); + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoCALLReg)) + return expandCall(Instruction, Function, Target, false); + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::PseudoTAIL)) + return expandCall(Instruction, Function, Target, true); + } + return false; + } +}; + +} // namespace + +const TargetPseudoExpansionInfo &pseudoExpansionInfo() noexcept { + static const RISCVPseudoExpansionInfo Value; + return Value; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVPushPopOptimizer.cpp b/backend/lib/Target/RISCV/RISCVPushPopOptimizer.cpp new file mode 100644 index 0000000..77b0ee7 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVPushPopOptimizer.cpp @@ -0,0 +1,222 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "RISCVInstrInfo.h" +#include "rcc/CodeGen/InstructionDescriptor.h" +#include "rcc/CodeGen/LiveRegUnits.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineIRBuilder.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/TargetDescription.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <cstddef> +#include <memory> +#include <optional> +#include <utility> + +namespace rcc::riscv { +namespace { + +struct PushPopOpcodes final { + Opcode Pop; + Opcode PopRet; + Opcode PopRetZero; +}; + +[[nodiscard]] const MachinePassContract &pushPopContract() { + static const MachinePassContract Contract = [] { + MachinePassContract Value{ + .Name = "riscv-push-pop-opt", + .Requires = {MachineFunctionProperty::NoPHIs, MachineFunctionProperty::NoVRegs, + MachineFunctionProperty::FrameFinalized, + MachineFunctionProperty::NoFrameIndices}, + .Sets = {}, + .Clears = {}, + .RequiredAnalyses = {}, + .VerificationStage = PipelineStage::PostRAFinalized, + }; + Value.ThreadSafety = PassThreadSafety::ThreadSafe; + Value.DeclaredChanges = {MachinePassChange::Instructions, MachinePassChange::Operands}; + return Value; + }(); + return Contract; +} + +[[nodiscard]] MachineInstr *previousNonDebug(MachineInstr &Instruction) noexcept { + MachineInstr *Previous = Instruction.previous(); + while (Previous != nullptr && Previous->isDebugInstr()) + Previous = Previous->previous(); + return Previous; +} + +[[nodiscard]] MachineInstr *firstTerminator(MachineBasicBlock &Block, + const TargetDescription &Target) noexcept { + for (MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = Target.instructionDescriptor(Instruction->opcode()); + if (Descriptor != nullptr && Descriptor->Flags.has(InstructionFlag::Terminator)) + return Instruction; + } + return nullptr; +} + +[[nodiscard]] Expected<bool> removeZeroReturnAssignment(MachineInstr &Pop, + const TargetDescription &Target, + PhysicalRegister ReturnRegister, + PhysicalRegister ZeroRegister) { + LiveRegUnits Modified; + LiveRegUnits Used; + for (MachineInstr *Instruction = previousNonDebug(Pop); Instruction != nullptr; + Instruction = previousNonDebug(*Instruction)) { + if (const std::optional<MachineCopyOperands> Copy = instrInfo().getCopyOperands(*Instruction)) { + const std::optional<Register> Destination = Copy->Destination->registerValue(); + const std::optional<Register> Source = Copy->Source->registerValue(); + if (Destination && Source && Destination->physicalRegister() == ReturnRegister && + Source->physicalRegister() == ZeroRegister) { + if (auto Removed = Instruction->eraseFromParent(); !Removed) + return std::unexpected(Removed.error()); + return true; + } + } + if (auto Accumulated = LiveRegUnits::accumulateUsedDefed(*Instruction, Modified, Used, Target); + !Accumulated) { + return std::unexpected(Accumulated.error()); + } + auto ModifiedAvailable = Modified.available(ReturnRegister, Target); + if (!ModifiedAvailable) + return std::unexpected(ModifiedAvailable.error()); + auto UsedAvailable = Used.available(ReturnRegister, Target); + if (!UsedAvailable) + return std::unexpected(UsedAvailable.error()); + if (!*ModifiedAvailable || !*UsedAvailable) + return false; + } + return false; +} + +[[nodiscard]] Expected<MachineInstr *> buildPopReturn(MachineInstr &Pop, MachineInstr &Return, + bool ReturnZero, + const PushPopOpcodes &Opcodes, + MachineFunction &Function, + const TargetDescription &Target) { + const InstructionDescriptor *PopDescriptor = Target.instructionDescriptor(Pop.opcode()); + if (PopDescriptor == nullptr || PopDescriptor->Operands.size() != 2U) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V CM_POP has an invalid instruction descriptor"); + MachineIRBuilder Builder(Function, Target); + if (auto Point = Builder.setInsertPointBefore(Return); !Point) + return std::unexpected(Point.error()); + auto Pending = Builder.buildInstr(ReturnZero ? Opcodes.PopRetZero : Opcodes.PopRet); + if (!Pending) + return std::unexpected(Pending.error()); + for (std::size_t Index = 0; Index < PopDescriptor->Operands.size(); ++Index) { + const MachineOperand *Operand = Pop.operandAt(Index); + if (Operand == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V CM_POP is missing an explicit operand"); + if (auto Added = Pending->addOperand(Operand->cloneDetached()); !Added) + return std::unexpected(Added.error()); + } + const std::size_t FirstDynamicImplicit = PopDescriptor->Operands.size() + + PopDescriptor->ImplicitDefs.size() + + PopDescriptor->ImplicitUses.size(); + for (std::size_t Index = FirstDynamicImplicit; Index < Pop.operandCount(); ++Index) { + const MachineOperand *Operand = Pop.operandAt(Index); + if (Operand == nullptr || !Operand->isImplicit()) { + return makeUnexpected(ErrorCode::VerificationError, + "RISC-V CM_POP has a malformed dynamic implicit operand"); + } + if (auto Added = Pending->addImplicitOperand(Operand->cloneDetached()); !Added) + return std::unexpected(Added.error()); + } + if (auto Flags = Pending->setFlags({MachineInstrFlag::FrameDestroy}); !Flags) + return std::unexpected(Flags.error()); + if (Return.debugLocation()) { + if (auto Location = Pending->setDebugLocation(*Return.debugLocation()); !Location) + return std::unexpected(Location.error()); + } + return Pending->build(); +} + +class RISCVPushPopOptimizationPass final : public MachineFunctionPass { +public: + explicit RISCVPushPopOptimizationPass(const RISCVSubtarget &Subtarget) noexcept + : Subtarget(&Subtarget) {} + + [[nodiscard]] const MachinePassContract &contract() const noexcept override { + return pushPopContract(); + } + + [[nodiscard]] Expected<MachinePassResult> run(MachineFunction &Function, + const TargetDescription &Target, + MachineFunctionAnalysisManager &) override { + MachinePassResult Result; + if (!Subtarget->features().has(RISCVExtension::Zcmp)) { + Result.Preserved = PreservedAnalyses::all(); + return Result; + } + + const InstructionDescriptor *Pop = Target.instructions().descriptor("CM_POP"); + const InstructionDescriptor *PopRet = Target.instructions().descriptor("CM_POPRET"); + const InstructionDescriptor *PopRetZero = Target.instructions().descriptor("CM_POPRETZ"); + if (Pop == nullptr || PopRet == nullptr || PopRetZero == nullptr) { + return makeUnexpected(ErrorCode::Unsupported, + "RISC-V Zcmp push/pop instructions are not registered"); + } + auto ReturnRegister = physicalRegister(PhysicalRegisterID::X10); + if (!ReturnRegister) + return std::unexpected(ReturnRegister.error()); + auto ZeroRegister = physicalRegister(PhysicalRegisterID::X0); + if (!ZeroRegister) + return std::unexpected(ZeroRegister.error()); + const PushPopOpcodes Opcodes{Pop->InstructionOpcode, PopRet->InstructionOpcode, + PopRetZero->InstructionOpcode}; + + for (MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr; + Block = Block->next()) { + MachineInstr *Return = firstTerminator(*Block, Target); + if (Return == nullptr || + Return->opcode() != instructionOpcode(InstructionOpcode::PseudoRET)) { + continue; + } + MachineInstr *PopInstruction = previousNonDebug(*Return); + if (PopInstruction == nullptr || PopInstruction->opcode() != Opcodes.Pop || + !PopInstruction->flags().has(MachineInstrFlag::FrameDestroy)) { + continue; + } + auto ReturnZero = + removeZeroReturnAssignment(*PopInstruction, Target, *ReturnRegister, *ZeroRegister); + if (!ReturnZero) + return std::unexpected(ReturnZero.error()); + auto NewReturn = + buildPopReturn(*PopInstruction, *Return, *ReturnZero, Opcodes, Function, Target); + if (!NewReturn) + return std::unexpected(NewReturn.error()); + if (auto Removed = PopInstruction->eraseFromParent(); !Removed) + return std::unexpected(Removed.error()); + if (auto Removed = Return->eraseFromParent(); !Removed) + return std::unexpected(Removed.error()); + Result.Changes.add(MachinePassChange::Instructions); + Result.Changes.add(MachinePassChange::Operands); + } + Result.Changed = !Result.Changes.empty(); + Result.Preserved = Result.Changed ? PreservedAnalyses::none() : PreservedAnalyses::all(); + return Result; + } + +private: + const RISCVSubtarget *Subtarget; +}; + +} // namespace + +std::unique_ptr<MachineFunctionPass> +createPushPopOptimizationPass(const RISCVSubtarget &Subtarget) { + return std::make_unique<RISCVPushPopOptimizationPass>(Subtarget); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVRegisterAllocation.cpp b/backend/lib/Target/RISCV/RISCVRegisterAllocation.cpp new file mode 100644 index 0000000..2b34f53 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVRegisterAllocation.cpp @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "rcc/CodeGen/InstructionDescriptor.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/Target/RISCV/RISCVInstructions.h" +#include "rcc/Target/RISCV/RISCVRegisters.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <algorithm> +#include <array> +#include <cstddef> +#include <cstdint> +#include <optional> +#include <utility> + +namespace rcc::riscv { +namespace { + +struct SpillForm final { + InstructionOpcode Load; + InstructionOpcode Store; + std::uint64_t SizeInBytes; +}; + +[[nodiscard]] constexpr RegisterClassID registerClassID(RegisterClass Value) noexcept { + return RegisterClassID::fromValue(static_cast<std::uint32_t>(Value)); +} + +[[nodiscard]] constexpr std::optional<SpillForm> spillForm(RegisterClassID Class) noexcept { + if (Class == registerClassID(RegisterClass::GPR) || + Class == registerClassID(RegisterClass::GPRC) || + Class == registerClassID(RegisterClass::GPR_NX0) || + Class == registerClassID(RegisterClass::GPR_NX0_NX2) || + Class == registerClassID(RegisterClass::GPRJALRNonX7)) { + return SpillForm{InstructionOpcode::LD, InstructionOpcode::SD, 8}; + } + if (Class == registerClassID(RegisterClass::FPR64) || + Class == registerClassID(RegisterClass::FPR64C)) { + return SpillForm{InstructionOpcode::FLD, InstructionOpcode::FSD, 8}; + } + if (Class == registerClassID(RegisterClass::FPR32) || + Class == registerClassID(RegisterClass::FPR32C)) { + return SpillForm{InstructionOpcode::FLW, InstructionOpcode::FSW, 4}; + } + return std::nullopt; +} + +[[nodiscard]] Expected<const MachineMemOperand *> +createStackMemoryOperand(MachineIRBuilder &Builder, FrameIndex StackSlot, MemoryAccessFlag Access, + std::uint64_t SizeInBytes, std::uint64_t AlignmentInBytes) { + auto Size = MemorySize::fixed(SizeInBytes); + if (!Size) + return std::unexpected(Size.error()); + auto Align = Alignment::create(AlignmentInBytes); + if (!Align) + return std::unexpected(Align.error()); + auto Type = LowLevelType::makeCategoryNeutralScalar(SizeInBytes * 8U); + if (!Type) + return std::unexpected(Type.error()); + + MachineMemOperandDesc Description; + Description.Accesses = {Access}; + Description.Size = *Size; + Description.MemoryType = *Type; + Description.Align = *Align; + Description.Base = MemoryBase::frameIndex(StackSlot); + return Builder.function().createMemOperand(std::move(Description)); +} + +[[nodiscard]] bool isCalleeSaved(PhysicalRegister Reg) noexcept { + const std::uint32_t Number = Reg.number(); + const std::array GPRs{ + PhysicalRegisterID::X8, PhysicalRegisterID::X9, PhysicalRegisterID::X18, + PhysicalRegisterID::X19, PhysicalRegisterID::X20, PhysicalRegisterID::X21, + PhysicalRegisterID::X22, PhysicalRegisterID::X23, PhysicalRegisterID::X24, + PhysicalRegisterID::X25, PhysicalRegisterID::X26, PhysicalRegisterID::X27, + }; + const std::array FPRs{ + PhysicalRegisterID::F8_D, PhysicalRegisterID::F9_D, PhysicalRegisterID::F18_D, + PhysicalRegisterID::F19_D, PhysicalRegisterID::F20_D, PhysicalRegisterID::F21_D, + PhysicalRegisterID::F22_D, PhysicalRegisterID::F23_D, PhysicalRegisterID::F24_D, + PhysicalRegisterID::F25_D, PhysicalRegisterID::F26_D, PhysicalRegisterID::F27_D, + PhysicalRegisterID::F8_F, PhysicalRegisterID::F9_F, PhysicalRegisterID::F18_F, + PhysicalRegisterID::F19_F, PhysicalRegisterID::F20_F, PhysicalRegisterID::F21_F, + PhysicalRegisterID::F22_F, PhysicalRegisterID::F23_F, PhysicalRegisterID::F24_F, + PhysicalRegisterID::F25_F, PhysicalRegisterID::F26_F, PhysicalRegisterID::F27_F, + }; + const auto Matches = [Number](PhysicalRegisterID ID) { + return Number == static_cast<std::uint32_t>(ID); + }; + return std::ranges::any_of(GPRs, Matches) || std::ranges::any_of(FPRs, Matches); +} + +[[nodiscard]] bool isCompressedRegister(PhysicalRegister Reg) noexcept { + const std::uint32_t Number = Reg.number(); + const std::uint32_t X8 = static_cast<std::uint32_t>(PhysicalRegisterID::X8); + const std::uint32_t X15 = static_cast<std::uint32_t>(PhysicalRegisterID::X15); + const std::uint32_t F8D = static_cast<std::uint32_t>(PhysicalRegisterID::F8_D); + const std::uint32_t F15D = static_cast<std::uint32_t>(PhysicalRegisterID::F15_D); + const std::uint32_t F8F = static_cast<std::uint32_t>(PhysicalRegisterID::F8_F); + const std::uint32_t F15F = static_cast<std::uint32_t>(PhysicalRegisterID::F15_F); + return (Number >= X8 && Number <= X15) || (Number >= F8D && Number <= F15D) || + (Number >= F8F && Number <= F15F); +} + +class RISCVRegisterAllocationInfo final : public TargetRegisterAllocationInfo { +public: + [[nodiscard]] bool + isReservedForFunction(const MachineFunction &Function, PhysicalRegister Reg, + const TargetDescription &Target) const noexcept override { + std::uint64_t RequiredAlignment = 16; + RequiredAlignment = std::max( + RequiredAlignment, Function.frameInfo().state().MaxAlignment.value_or(Alignment{}).bytes()); + for (const StackObject &Object : Function.frameInfo().objects()) { + if (!Object.isTombstone() && !Object.description()->IsDead) { + RequiredAlignment = std::max(RequiredAlignment, Object.description()->Align.bytes()); + } + } + const bool RequiresRealignment = RequiredAlignment > 16; + if (Reg.number() == static_cast<std::uint32_t>(PhysicalRegisterID::X9)) + return RequiresRealignment; + if (Reg.number() != static_cast<std::uint32_t>(PhysicalRegisterID::X8)) + return false; + const MachineFrameState &State = Function.frameInfo().state(); + bool HasCalls = State.HasCalls; + for (const MachineBasicBlock *Block = Function.firstBlock(); Block != nullptr && !HasCalls; + Block = Block->next()) { + for (const MachineInstr *Instruction = Block->firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = + Target.instructionDescriptor(Instruction->opcode()); + if (Descriptor != nullptr && Descriptor->Flags.has(InstructionFlag::Call)) { + HasCalls = true; + break; + } + } + } + const bool HasVariableObject = + std::ranges::any_of(Function.frameInfo().objects(), [](const StackObject &Object) { + return !Object.isTombstone() && !Object.description()->IsDead && Object.isVariableSized(); + }); + if (State.IsFrameAddressTaken || State.HasOpaqueSPAdjustment || HasVariableObject || + RequiresRealignment) + return true; + switch (State.FramePointerPolicy) { + case MachineFramePointerPolicy::All: + case MachineFramePointerPolicy::Reserved: + return true; + case MachineFramePointerPolicy::NonLeaf: + case MachineFramePointerPolicy::NonLeafNoReserve: + return HasCalls; + case MachineFramePointerPolicy::None: + case MachineFramePointerPolicy::Count: + return false; + } + return false; + } + + [[nodiscard]] Expected<void> validateSpillReload(RegisterClassID Class, + const TargetDescription &Target) const override { + const RegisterClassDesc *Description = Target.registers().registerClass(Class); + const auto Form = spillForm(Class); + if (!Form) { + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC register class has no scalar spill/reload form"); + } + if (Description == nullptr || !Description->Allocatable || + Description->SpillSizeInBits != Form->SizeInBytes * 8U || + Description->SpillAlignmentInBits == 0 || Description->SpillAlignmentInBits % 8U != 0) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RV64GC register class has inconsistent spill properties"); + } + for (const InstructionOpcode OpcodeValue : {Form->Load, Form->Store}) { + const InstructionDescriptor *Instruction = + Target.instructionDescriptor(instructionOpcode(OpcodeValue)); + const InstructionFlag MemoryFlag = + OpcodeValue == Form->Load ? InstructionFlag::MayLoad : InstructionFlag::MayStore; + if (Instruction == nullptr || Instruction->Operands.size() != 3 || + !Instruction->Operands[1].Kinds.contains(MachineOperand::Kind::FrameIndex) || + !Instruction->Flags.has(MemoryFlag)) { + return makeUnexpected(ErrorCode::InternalError, + "RV64GC spill instruction descriptor is incomplete"); + } + } + return {}; + } + + [[nodiscard]] Expected<MachineInstr *> emitReload(MachineIRBuilder &Builder, Register Destination, + RegisterClassID Class, FrameIndex StackSlot, + LaneMask Lanes) const override { + auto Validated = validateSpillReload(Class, Builder.target()); + if (!Validated) + return std::unexpected(Validated.error()); + const RegisterClassDesc *Description = Builder.target().registers().registerClass(Class); + if (Lanes.empty() || !Lanes.intersects(Description->Lanes)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RV64GC reload requires a nonempty class lane mask"); + } + const auto FormValue = spillForm(Class); + if (!FormValue) { + return makeUnexpected(ErrorCode::InternalError, + "RV64GC reload lost its validated spill form"); + } + const SpillForm Form = FormValue.value(); + auto Memory = + createStackMemoryOperand(Builder, StackSlot, MemoryAccessFlag::Load, Form.SizeInBytes, + Description->SpillAlignmentInBits / 8U); + if (!Memory) + return std::unexpected(Memory.error()); + auto Pending = Builder.buildInstr(instructionOpcode(Form.Load)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Result = Pending->addDef(Destination); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addFrameIndex(StackSlot); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addImmediate(0); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addMemOperand(**Memory); !Result) + return std::unexpected(Result.error()); + return Pending->build(); + } + + [[nodiscard]] Expected<MachineInstr *> emitSpill(MachineIRBuilder &Builder, Register Source, + RegisterClassID Class, FrameIndex StackSlot, + LaneMask Lanes) const override { + auto Validated = validateSpillReload(Class, Builder.target()); + if (!Validated) + return std::unexpected(Validated.error()); + const RegisterClassDesc *Description = Builder.target().registers().registerClass(Class); + if (Lanes.empty() || !Lanes.intersects(Description->Lanes)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RV64GC spill requires a nonempty class lane mask"); + } + const auto FormValue = spillForm(Class); + if (!FormValue) { + return makeUnexpected(ErrorCode::InternalError, "RV64GC spill lost its validated spill form"); + } + const SpillForm Form = FormValue.value(); + auto Memory = + createStackMemoryOperand(Builder, StackSlot, MemoryAccessFlag::Store, Form.SizeInBytes, + Description->SpillAlignmentInBits / 8U); + if (!Memory) + return std::unexpected(Memory.error()); + auto Pending = Builder.buildInstr(instructionOpcode(Form.Store)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Result = Pending->addUse(Source); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addFrameIndex(StackSlot); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addImmediate(0); !Result) + return std::unexpected(Result.error()); + if (auto Result = Pending->addMemOperand(**Memory); !Result) + return std::unexpected(Result.error()); + return Pending->build(); + } + + [[nodiscard]] Expected<bool> isRematerializable(const MachineInstr &Instruction, + Register VirtualReg, const MachineFunction &, + const TargetDescription &) const override { + const MachineOperand *Definition = Instruction.operandAt(0); + const Register *DefinedReg = Definition == nullptr ? nullptr : Definition->getIf<Register>(); + if (DefinedReg == nullptr || *DefinedReg != VirtualReg || !Definition->isDef()) + return false; + + if (Instruction.opcode() == instructionOpcode(InstructionOpcode::LUI)) + return Instruction.operandCount() == 2 && Instruction.operandAt(1) != nullptr; + if (Instruction.opcode() != instructionOpcode(InstructionOpcode::ADDI) && + Instruction.opcode() != instructionOpcode(InstructionOpcode::ADDIW)) { + return false; + } + if (Instruction.operandCount() != 3) + return false; + const MachineOperand *Source = Instruction.operandAt(1); + const Register *SourceReg = Source == nullptr ? nullptr : Source->getIf<Register>(); + const auto Zero = physicalRegister(PhysicalRegisterID::X0); + return Zero && SourceReg != nullptr && *SourceReg == Register::fromPhysical(*Zero) && + Source->isUse(); + } + + [[nodiscard]] Expected<MachineInstr *> + rematerialize(MachineIRBuilder &Builder, const MachineInstr &Original, Register Destination, + Register OriginalRegister, const TargetDescription &Target) const override { + auto Accepted = isRematerializable(Original, OriginalRegister, Builder.function(), Target); + if (!Accepted) + return std::unexpected(Accepted.error()); + if (!*Accepted) { + return makeUnexpected(ErrorCode::InvalidArgument, + "RV64GC instruction is not rematerializable"); + } + + auto Pending = Builder.buildInstr(Original.opcode()); + if (!Pending) + return std::unexpected(Pending.error()); + const MachineOperand *OriginalDef = Original.operandAt(0); + if (auto Result = Pending->addDef(Destination, {}, {}, OriginalDef->targetFlags()); !Result) + return std::unexpected(Result.error()); + for (std::size_t Index = 1; Index < Original.operandCount(); ++Index) { + const MachineOperand *Operand = Original.operandAt(Index); + if (Operand == nullptr) { + return makeUnexpected(ErrorCode::InternalError, + "RV64GC rematerialization found a missing operand"); + } + if (auto Result = Pending->addOperand(Operand->cloneDetached()); !Result) + return std::unexpected(Result.error()); + } + return Pending->build(); + } + + [[nodiscard]] std::uint64_t physicalRegisterCost(const MachineFunction &, Register, + PhysicalRegister Reg, RegisterClassID, + bool) const noexcept override { + if (Reg.number() == static_cast<std::uint32_t>(PhysicalRegisterID::X1)) + return 16; + if (isCalleeSaved(Reg)) + return 4; + return isCompressedRegister(Reg) ? 0 : 1; + } + + [[nodiscard]] std::uint64_t spillCost(const MachineFunction &, const LiveInterval &, + RegisterClassID Class) const noexcept override { + const auto Form = spillForm(Class); + return Form ? std::max<std::uint64_t>(1, Form->SizeInBytes / 4U) : 1; + } +}; + +} // namespace + +const TargetRegisterAllocationInfo ®isterAllocationInfo() noexcept { + static const RISCVRegisterAllocationInfo Info; + return Info; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVRegisterInfo.rccdesc b/backend/lib/Target/RISCV/RISCVRegisterInfo.rccdesc new file mode 100644 index 0000000..975aa56 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVRegisterInfo.rccdesc @@ -0,0 +1,413 @@ +register_units: + - { id: 1, symbol: U_X0, name: x0.unit } + - { id: 2, symbol: U_X1, name: x1.unit } + - { id: 3, symbol: U_X2, name: x2.unit } + - { id: 4, symbol: U_X3, name: x3.unit } + - { id: 5, symbol: U_X4, name: x4.unit } + - { id: 6, symbol: U_X5, name: x5.unit } + - { id: 7, symbol: U_X6, name: x6.unit } + - { id: 8, symbol: U_X7, name: x7.unit } + - { id: 9, symbol: U_X8, name: x8.unit } + - { id: 10, symbol: U_X9, name: x9.unit } + - { id: 11, symbol: U_X10, name: x10.unit } + - { id: 12, symbol: U_X11, name: x11.unit } + - { id: 13, symbol: U_X12, name: x12.unit } + - { id: 14, symbol: U_X13, name: x13.unit } + - { id: 15, symbol: U_X14, name: x14.unit } + - { id: 16, symbol: U_X15, name: x15.unit } + - { id: 17, symbol: U_X16, name: x16.unit } + - { id: 18, symbol: U_X17, name: x17.unit } + - { id: 19, symbol: U_X18, name: x18.unit } + - { id: 20, symbol: U_X19, name: x19.unit } + - { id: 21, symbol: U_X20, name: x20.unit } + - { id: 22, symbol: U_X21, name: x21.unit } + - { id: 23, symbol: U_X22, name: x22.unit } + - { id: 24, symbol: U_X23, name: x23.unit } + - { id: 25, symbol: U_X24, name: x24.unit } + - { id: 26, symbol: U_X25, name: x25.unit } + - { id: 27, symbol: U_X26, name: x26.unit } + - { id: 28, symbol: U_X27, name: x27.unit } + - { id: 29, symbol: U_X28, name: x28.unit } + - { id: 30, symbol: U_X29, name: x29.unit } + - { id: 31, symbol: U_X30, name: x30.unit } + - { id: 32, symbol: U_X31, name: x31.unit } + - { id: 33, symbol: U_F0, name: f0.unit } + - { id: 34, symbol: U_F1, name: f1.unit } + - { id: 35, symbol: U_F2, name: f2.unit } + - { id: 36, symbol: U_F3, name: f3.unit } + - { id: 37, symbol: U_F4, name: f4.unit } + - { id: 38, symbol: U_F5, name: f5.unit } + - { id: 39, symbol: U_F6, name: f6.unit } + - { id: 40, symbol: U_F7, name: f7.unit } + - { id: 41, symbol: U_F8, name: f8.unit } + - { id: 42, symbol: U_F9, name: f9.unit } + - { id: 43, symbol: U_F10, name: f10.unit } + - { id: 44, symbol: U_F11, name: f11.unit } + - { id: 45, symbol: U_F12, name: f12.unit } + - { id: 46, symbol: U_F13, name: f13.unit } + - { id: 47, symbol: U_F14, name: f14.unit } + - { id: 48, symbol: U_F15, name: f15.unit } + - { id: 49, symbol: U_F16, name: f16.unit } + - { id: 50, symbol: U_F17, name: f17.unit } + - { id: 51, symbol: U_F18, name: f18.unit } + - { id: 52, symbol: U_F19, name: f19.unit } + - { id: 53, symbol: U_F20, name: f20.unit } + - { id: 54, symbol: U_F21, name: f21.unit } + - { id: 55, symbol: U_F22, name: f22.unit } + - { id: 56, symbol: U_F23, name: f23.unit } + - { id: 57, symbol: U_F24, name: f24.unit } + - { id: 58, symbol: U_F25, name: f25.unit } + - { id: 59, symbol: U_F26, name: f26.unit } + - { id: 60, symbol: U_F27, name: f27.unit } + - { id: 61, symbol: U_F28, name: f28.unit } + - { id: 62, symbol: U_F29, name: f29.unit } + - { id: 63, symbol: U_F30, name: f30.unit } + - { id: 64, symbol: U_F31, name: f31.unit } + - { id: 65, symbol: U_FFLAGS, name: fflags.unit } + - { id: 66, symbol: U_FRM, name: frm.unit } + - { id: 67, symbol: U_V0, name: v0.unit } + - { id: 68, symbol: U_V1, name: v1.unit } + - { id: 69, symbol: U_V2, name: v2.unit } + - { id: 70, symbol: U_V3, name: v3.unit } + - { id: 71, symbol: U_V4, name: v4.unit } + - { id: 72, symbol: U_V5, name: v5.unit } + - { id: 73, symbol: U_V6, name: v6.unit } + - { id: 74, symbol: U_V7, name: v7.unit } + - { id: 75, symbol: U_V8, name: v8.unit } + - { id: 76, symbol: U_V9, name: v9.unit } + - { id: 77, symbol: U_V10, name: v10.unit } + - { id: 78, symbol: U_V11, name: v11.unit } + - { id: 79, symbol: U_V12, name: v12.unit } + - { id: 80, symbol: U_V13, name: v13.unit } + - { id: 81, symbol: U_V14, name: v14.unit } + - { id: 82, symbol: U_V15, name: v15.unit } + - { id: 83, symbol: U_V16, name: v16.unit } + - { id: 84, symbol: U_V17, name: v17.unit } + - { id: 85, symbol: U_V18, name: v18.unit } + - { id: 86, symbol: U_V19, name: v19.unit } + - { id: 87, symbol: U_V20, name: v20.unit } + - { id: 88, symbol: U_V21, name: v21.unit } + - { id: 89, symbol: U_V22, name: v22.unit } + - { id: 90, symbol: U_V23, name: v23.unit } + - { id: 91, symbol: U_V24, name: v24.unit } + - { id: 92, symbol: U_V25, name: v25.unit } + - { id: 93, symbol: U_V26, name: v26.unit } + - { id: 94, symbol: U_V27, name: v27.unit } + - { id: 95, symbol: U_V28, name: v28.unit } + - { id: 96, symbol: U_V29, name: v29.unit } + - { id: 97, symbol: U_V30, name: v30.unit } + - { id: 98, symbol: U_V31, name: v31.unit } + +registers: + - { id: 1, symbol: X0, name: x0, encoding: 0, dwarf_register: 0, units: [{ unit: U_X0, lanes: 1 }], constant: true, reserved: true, aliases: [zero] } + - { id: 2, symbol: X1, name: x1, encoding: 1, dwarf_register: 1, units: [{ unit: U_X1, lanes: 1 }], aliases: [ra] } + - { id: 3, symbol: X2, name: x2, encoding: 2, dwarf_register: 2, units: [{ unit: U_X2, lanes: 1 }], reserved: true, aliases: [sp] } + - { id: 4, symbol: X3, name: x3, encoding: 3, dwarf_register: 3, units: [{ unit: U_X3, lanes: 1 }], reserved: true, aliases: [gp] } + - { id: 5, symbol: X4, name: x4, encoding: 4, dwarf_register: 4, units: [{ unit: U_X4, lanes: 1 }], reserved: true, aliases: [tp] } + - { id: 6, symbol: X5, name: x5, encoding: 5, dwarf_register: 5, units: [{ unit: U_X5, lanes: 1 }], aliases: [t0] } + - { id: 7, symbol: X6, name: x6, encoding: 6, dwarf_register: 6, units: [{ unit: U_X6, lanes: 1 }], aliases: [t1] } + - { id: 8, symbol: X7, name: x7, encoding: 7, dwarf_register: 7, units: [{ unit: U_X7, lanes: 1 }], aliases: [t2] } + - { id: 9, symbol: X8, name: x8, encoding: 8, dwarf_register: 8, units: [{ unit: U_X8, lanes: 1 }], aliases: [s0, fp] } + - { id: 10, symbol: X9, name: x9, encoding: 9, dwarf_register: 9, units: [{ unit: U_X9, lanes: 1 }], aliases: [s1] } + - { id: 11, symbol: X10, name: x10, encoding: 10, dwarf_register: 10, units: [{ unit: U_X10, lanes: 1 }], aliases: [a0] } + - { id: 12, symbol: X11, name: x11, encoding: 11, dwarf_register: 11, units: [{ unit: U_X11, lanes: 1 }], aliases: [a1] } + - { id: 13, symbol: X12, name: x12, encoding: 12, dwarf_register: 12, units: [{ unit: U_X12, lanes: 1 }], aliases: [a2] } + - { id: 14, symbol: X13, name: x13, encoding: 13, dwarf_register: 13, units: [{ unit: U_X13, lanes: 1 }], aliases: [a3] } + - { id: 15, symbol: X14, name: x14, encoding: 14, dwarf_register: 14, units: [{ unit: U_X14, lanes: 1 }], aliases: [a4] } + - { id: 16, symbol: X15, name: x15, encoding: 15, dwarf_register: 15, units: [{ unit: U_X15, lanes: 1 }], aliases: [a5] } + - { id: 17, symbol: X16, name: x16, encoding: 16, dwarf_register: 16, units: [{ unit: U_X16, lanes: 1 }], aliases: [a6] } + - { id: 18, symbol: X17, name: x17, encoding: 17, dwarf_register: 17, units: [{ unit: U_X17, lanes: 1 }], aliases: [a7] } + - { id: 19, symbol: X18, name: x18, encoding: 18, dwarf_register: 18, units: [{ unit: U_X18, lanes: 1 }], aliases: [s2] } + - { id: 20, symbol: X19, name: x19, encoding: 19, dwarf_register: 19, units: [{ unit: U_X19, lanes: 1 }], aliases: [s3] } + - { id: 21, symbol: X20, name: x20, encoding: 20, dwarf_register: 20, units: [{ unit: U_X20, lanes: 1 }], aliases: [s4] } + - { id: 22, symbol: X21, name: x21, encoding: 21, dwarf_register: 21, units: [{ unit: U_X21, lanes: 1 }], aliases: [s5] } + - { id: 23, symbol: X22, name: x22, encoding: 22, dwarf_register: 22, units: [{ unit: U_X22, lanes: 1 }], aliases: [s6] } + - { id: 24, symbol: X23, name: x23, encoding: 23, dwarf_register: 23, units: [{ unit: U_X23, lanes: 1 }], aliases: [s7] } + - { id: 25, symbol: X24, name: x24, encoding: 24, dwarf_register: 24, units: [{ unit: U_X24, lanes: 1 }], aliases: [s8] } + - { id: 26, symbol: X25, name: x25, encoding: 25, dwarf_register: 25, units: [{ unit: U_X25, lanes: 1 }], aliases: [s9] } + - { id: 27, symbol: X26, name: x26, encoding: 26, dwarf_register: 26, units: [{ unit: U_X26, lanes: 1 }], aliases: [s10] } + - { id: 28, symbol: X27, name: x27, encoding: 27, dwarf_register: 27, units: [{ unit: U_X27, lanes: 1 }], aliases: [s11] } + - { id: 29, symbol: X28, name: x28, encoding: 28, dwarf_register: 28, units: [{ unit: U_X28, lanes: 1 }], aliases: [t3] } + - { id: 30, symbol: X29, name: x29, encoding: 29, dwarf_register: 29, units: [{ unit: U_X29, lanes: 1 }], aliases: [t4] } + - { id: 31, symbol: X30, name: x30, encoding: 30, dwarf_register: 30, units: [{ unit: U_X30, lanes: 1 }], aliases: [t5] } + - { id: 32, symbol: X31, name: x31, encoding: 31, dwarf_register: 31, units: [{ unit: U_X31, lanes: 1 }], aliases: [t6] } + - { id: 33, symbol: F0_D, name: f0_d, encoding: 0, dwarf_register: 32, units: [{ unit: U_F0, lanes: 1 }], aliases: [ft0] } + - { id: 34, symbol: F1_D, name: f1_d, encoding: 1, dwarf_register: 33, units: [{ unit: U_F1, lanes: 1 }], aliases: [ft1] } + - { id: 35, symbol: F2_D, name: f2_d, encoding: 2, dwarf_register: 34, units: [{ unit: U_F2, lanes: 1 }], aliases: [ft2] } + - { id: 36, symbol: F3_D, name: f3_d, encoding: 3, dwarf_register: 35, units: [{ unit: U_F3, lanes: 1 }], aliases: [ft3] } + - { id: 37, symbol: F4_D, name: f4_d, encoding: 4, dwarf_register: 36, units: [{ unit: U_F4, lanes: 1 }], aliases: [ft4] } + - { id: 38, symbol: F5_D, name: f5_d, encoding: 5, dwarf_register: 37, units: [{ unit: U_F5, lanes: 1 }], aliases: [ft5] } + - { id: 39, symbol: F6_D, name: f6_d, encoding: 6, dwarf_register: 38, units: [{ unit: U_F6, lanes: 1 }], aliases: [ft6] } + - { id: 40, symbol: F7_D, name: f7_d, encoding: 7, dwarf_register: 39, units: [{ unit: U_F7, lanes: 1 }], aliases: [ft7] } + - { id: 41, symbol: F8_D, name: f8_d, encoding: 8, dwarf_register: 40, units: [{ unit: U_F8, lanes: 1 }], aliases: [fs0] } + - { id: 42, symbol: F9_D, name: f9_d, encoding: 9, dwarf_register: 41, units: [{ unit: U_F9, lanes: 1 }], aliases: [fs1] } + - { id: 43, symbol: F10_D, name: f10_d, encoding: 10, dwarf_register: 42, units: [{ unit: U_F10, lanes: 1 }], aliases: [fa0] } + - { id: 44, symbol: F11_D, name: f11_d, encoding: 11, dwarf_register: 43, units: [{ unit: U_F11, lanes: 1 }], aliases: [fa1] } + - { id: 45, symbol: F12_D, name: f12_d, encoding: 12, dwarf_register: 44, units: [{ unit: U_F12, lanes: 1 }], aliases: [fa2] } + - { id: 46, symbol: F13_D, name: f13_d, encoding: 13, dwarf_register: 45, units: [{ unit: U_F13, lanes: 1 }], aliases: [fa3] } + - { id: 47, symbol: F14_D, name: f14_d, encoding: 14, dwarf_register: 46, units: [{ unit: U_F14, lanes: 1 }], aliases: [fa4] } + - { id: 48, symbol: F15_D, name: f15_d, encoding: 15, dwarf_register: 47, units: [{ unit: U_F15, lanes: 1 }], aliases: [fa5] } + - { id: 49, symbol: F16_D, name: f16_d, encoding: 16, dwarf_register: 48, units: [{ unit: U_F16, lanes: 1 }], aliases: [fa6] } + - { id: 50, symbol: F17_D, name: f17_d, encoding: 17, dwarf_register: 49, units: [{ unit: U_F17, lanes: 1 }], aliases: [fa7] } + - { id: 51, symbol: F18_D, name: f18_d, encoding: 18, dwarf_register: 50, units: [{ unit: U_F18, lanes: 1 }], aliases: [fs2] } + - { id: 52, symbol: F19_D, name: f19_d, encoding: 19, dwarf_register: 51, units: [{ unit: U_F19, lanes: 1 }], aliases: [fs3] } + - { id: 53, symbol: F20_D, name: f20_d, encoding: 20, dwarf_register: 52, units: [{ unit: U_F20, lanes: 1 }], aliases: [fs4] } + - { id: 54, symbol: F21_D, name: f21_d, encoding: 21, dwarf_register: 53, units: [{ unit: U_F21, lanes: 1 }], aliases: [fs5] } + - { id: 55, symbol: F22_D, name: f22_d, encoding: 22, dwarf_register: 54, units: [{ unit: U_F22, lanes: 1 }], aliases: [fs6] } + - { id: 56, symbol: F23_D, name: f23_d, encoding: 23, dwarf_register: 55, units: [{ unit: U_F23, lanes: 1 }], aliases: [fs7] } + - { id: 57, symbol: F24_D, name: f24_d, encoding: 24, dwarf_register: 56, units: [{ unit: U_F24, lanes: 1 }], aliases: [fs8] } + - { id: 58, symbol: F25_D, name: f25_d, encoding: 25, dwarf_register: 57, units: [{ unit: U_F25, lanes: 1 }], aliases: [fs9] } + - { id: 59, symbol: F26_D, name: f26_d, encoding: 26, dwarf_register: 58, units: [{ unit: U_F26, lanes: 1 }], aliases: [fs10] } + - { id: 60, symbol: F27_D, name: f27_d, encoding: 27, dwarf_register: 59, units: [{ unit: U_F27, lanes: 1 }], aliases: [fs11] } + - { id: 61, symbol: F28_D, name: f28_d, encoding: 28, dwarf_register: 60, units: [{ unit: U_F28, lanes: 1 }], aliases: [ft8] } + - { id: 62, symbol: F29_D, name: f29_d, encoding: 29, dwarf_register: 61, units: [{ unit: U_F29, lanes: 1 }], aliases: [ft9] } + - { id: 63, symbol: F30_D, name: f30_d, encoding: 30, dwarf_register: 62, units: [{ unit: U_F30, lanes: 1 }], aliases: [ft10] } + - { id: 64, symbol: F31_D, name: f31_d, encoding: 31, dwarf_register: 63, units: [{ unit: U_F31, lanes: 1 }], aliases: [ft11] } + - { id: 65, symbol: F0_F, name: f0_f, encoding: 0, dwarf_register: 32, units: [{ unit: U_F0, lanes: 1 }] } + - { id: 66, symbol: F1_F, name: f1_f, encoding: 1, dwarf_register: 33, units: [{ unit: U_F1, lanes: 1 }] } + - { id: 67, symbol: F2_F, name: f2_f, encoding: 2, dwarf_register: 34, units: [{ unit: U_F2, lanes: 1 }] } + - { id: 68, symbol: F3_F, name: f3_f, encoding: 3, dwarf_register: 35, units: [{ unit: U_F3, lanes: 1 }] } + - { id: 69, symbol: F4_F, name: f4_f, encoding: 4, dwarf_register: 36, units: [{ unit: U_F4, lanes: 1 }] } + - { id: 70, symbol: F5_F, name: f5_f, encoding: 5, dwarf_register: 37, units: [{ unit: U_F5, lanes: 1 }] } + - { id: 71, symbol: F6_F, name: f6_f, encoding: 6, dwarf_register: 38, units: [{ unit: U_F6, lanes: 1 }] } + - { id: 72, symbol: F7_F, name: f7_f, encoding: 7, dwarf_register: 39, units: [{ unit: U_F7, lanes: 1 }] } + - { id: 73, symbol: F8_F, name: f8_f, encoding: 8, dwarf_register: 40, units: [{ unit: U_F8, lanes: 1 }] } + - { id: 74, symbol: F9_F, name: f9_f, encoding: 9, dwarf_register: 41, units: [{ unit: U_F9, lanes: 1 }] } + - { id: 75, symbol: F10_F, name: f10_f, encoding: 10, dwarf_register: 42, units: [{ unit: U_F10, lanes: 1 }] } + - { id: 76, symbol: F11_F, name: f11_f, encoding: 11, dwarf_register: 43, units: [{ unit: U_F11, lanes: 1 }] } + - { id: 77, symbol: F12_F, name: f12_f, encoding: 12, dwarf_register: 44, units: [{ unit: U_F12, lanes: 1 }] } + - { id: 78, symbol: F13_F, name: f13_f, encoding: 13, dwarf_register: 45, units: [{ unit: U_F13, lanes: 1 }] } + - { id: 79, symbol: F14_F, name: f14_f, encoding: 14, dwarf_register: 46, units: [{ unit: U_F14, lanes: 1 }] } + - { id: 80, symbol: F15_F, name: f15_f, encoding: 15, dwarf_register: 47, units: [{ unit: U_F15, lanes: 1 }] } + - { id: 81, symbol: F16_F, name: f16_f, encoding: 16, dwarf_register: 48, units: [{ unit: U_F16, lanes: 1 }] } + - { id: 82, symbol: F17_F, name: f17_f, encoding: 17, dwarf_register: 49, units: [{ unit: U_F17, lanes: 1 }] } + - { id: 83, symbol: F18_F, name: f18_f, encoding: 18, dwarf_register: 50, units: [{ unit: U_F18, lanes: 1 }] } + - { id: 84, symbol: F19_F, name: f19_f, encoding: 19, dwarf_register: 51, units: [{ unit: U_F19, lanes: 1 }] } + - { id: 85, symbol: F20_F, name: f20_f, encoding: 20, dwarf_register: 52, units: [{ unit: U_F20, lanes: 1 }] } + - { id: 86, symbol: F21_F, name: f21_f, encoding: 21, dwarf_register: 53, units: [{ unit: U_F21, lanes: 1 }] } + - { id: 87, symbol: F22_F, name: f22_f, encoding: 22, dwarf_register: 54, units: [{ unit: U_F22, lanes: 1 }] } + - { id: 88, symbol: F23_F, name: f23_f, encoding: 23, dwarf_register: 55, units: [{ unit: U_F23, lanes: 1 }] } + - { id: 89, symbol: F24_F, name: f24_f, encoding: 24, dwarf_register: 56, units: [{ unit: U_F24, lanes: 1 }] } + - { id: 90, symbol: F25_F, name: f25_f, encoding: 25, dwarf_register: 57, units: [{ unit: U_F25, lanes: 1 }] } + - { id: 91, symbol: F26_F, name: f26_f, encoding: 26, dwarf_register: 58, units: [{ unit: U_F26, lanes: 1 }] } + - { id: 92, symbol: F27_F, name: f27_f, encoding: 27, dwarf_register: 59, units: [{ unit: U_F27, lanes: 1 }] } + - { id: 93, symbol: F28_F, name: f28_f, encoding: 28, dwarf_register: 60, units: [{ unit: U_F28, lanes: 1 }] } + - { id: 94, symbol: F29_F, name: f29_f, encoding: 29, dwarf_register: 61, units: [{ unit: U_F29, lanes: 1 }] } + - { id: 95, symbol: F30_F, name: f30_f, encoding: 30, dwarf_register: 62, units: [{ unit: U_F30, lanes: 1 }] } + - { id: 96, symbol: F31_F, name: f31_f, encoding: 31, dwarf_register: 63, units: [{ unit: U_F31, lanes: 1 }] } + - { id: 97, symbol: FFLAGS, name: fflags, encoding: 0, dwarf_register: 65, units: [{ unit: U_FFLAGS, lanes: 1 }], artificial: true, reserved: true } + - { id: 98, symbol: FRM, name: frm, encoding: 0, dwarf_register: 66, units: [{ unit: U_FRM, lanes: 1 }], artificial: true, reserved: true } + - { id: 99, symbol: V0, name: v0, encoding: 0, dwarf_register: 96, units: [{ unit: U_V0, lanes: 1 }] } + - { id: 100, symbol: V1, name: v1, encoding: 1, dwarf_register: 97, units: [{ unit: U_V1, lanes: 1 }] } + - { id: 101, symbol: V2, name: v2, encoding: 2, dwarf_register: 98, units: [{ unit: U_V2, lanes: 1 }] } + - { id: 102, symbol: V3, name: v3, encoding: 3, dwarf_register: 99, units: [{ unit: U_V3, lanes: 1 }] } + - { id: 103, symbol: V4, name: v4, encoding: 4, dwarf_register: 100, units: [{ unit: U_V4, lanes: 1 }] } + - { id: 104, symbol: V5, name: v5, encoding: 5, dwarf_register: 101, units: [{ unit: U_V5, lanes: 1 }] } + - { id: 105, symbol: V6, name: v6, encoding: 6, dwarf_register: 102, units: [{ unit: U_V6, lanes: 1 }] } + - { id: 106, symbol: V7, name: v7, encoding: 7, dwarf_register: 103, units: [{ unit: U_V7, lanes: 1 }] } + - { id: 107, symbol: V8, name: v8, encoding: 8, dwarf_register: 104, units: [{ unit: U_V8, lanes: 1 }] } + - { id: 108, symbol: V9, name: v9, encoding: 9, dwarf_register: 105, units: [{ unit: U_V9, lanes: 1 }] } + - { id: 109, symbol: V10, name: v10, encoding: 10, dwarf_register: 106, units: [{ unit: U_V10, lanes: 1 }] } + - { id: 110, symbol: V11, name: v11, encoding: 11, dwarf_register: 107, units: [{ unit: U_V11, lanes: 1 }] } + - { id: 111, symbol: V12, name: v12, encoding: 12, dwarf_register: 108, units: [{ unit: U_V12, lanes: 1 }] } + - { id: 112, symbol: V13, name: v13, encoding: 13, dwarf_register: 109, units: [{ unit: U_V13, lanes: 1 }] } + - { id: 113, symbol: V14, name: v14, encoding: 14, dwarf_register: 110, units: [{ unit: U_V14, lanes: 1 }] } + - { id: 114, symbol: V15, name: v15, encoding: 15, dwarf_register: 111, units: [{ unit: U_V15, lanes: 1 }] } + - { id: 115, symbol: V16, name: v16, encoding: 16, dwarf_register: 112, units: [{ unit: U_V16, lanes: 1 }] } + - { id: 116, symbol: V17, name: v17, encoding: 17, dwarf_register: 113, units: [{ unit: U_V17, lanes: 1 }] } + - { id: 117, symbol: V18, name: v18, encoding: 18, dwarf_register: 114, units: [{ unit: U_V18, lanes: 1 }] } + - { id: 118, symbol: V19, name: v19, encoding: 19, dwarf_register: 115, units: [{ unit: U_V19, lanes: 1 }] } + - { id: 119, symbol: V20, name: v20, encoding: 20, dwarf_register: 116, units: [{ unit: U_V20, lanes: 1 }] } + - { id: 120, symbol: V21, name: v21, encoding: 21, dwarf_register: 117, units: [{ unit: U_V21, lanes: 1 }] } + - { id: 121, symbol: V22, name: v22, encoding: 22, dwarf_register: 118, units: [{ unit: U_V22, lanes: 1 }] } + - { id: 122, symbol: V23, name: v23, encoding: 23, dwarf_register: 119, units: [{ unit: U_V23, lanes: 1 }] } + - { id: 123, symbol: V24, name: v24, encoding: 24, dwarf_register: 120, units: [{ unit: U_V24, lanes: 1 }] } + - { id: 124, symbol: V25, name: v25, encoding: 25, dwarf_register: 121, units: [{ unit: U_V25, lanes: 1 }] } + - { id: 125, symbol: V26, name: v26, encoding: 26, dwarf_register: 122, units: [{ unit: U_V26, lanes: 1 }] } + - { id: 126, symbol: V27, name: v27, encoding: 27, dwarf_register: 123, units: [{ unit: U_V27, lanes: 1 }] } + - { id: 127, symbol: V28, name: v28, encoding: 28, dwarf_register: 124, units: [{ unit: U_V28, lanes: 1 }] } + - { id: 128, symbol: V29, name: v29, encoding: 29, dwarf_register: 125, units: [{ unit: U_V29, lanes: 1 }] } + - { id: 129, symbol: V30, name: v30, encoding: 30, dwarf_register: 126, units: [{ unit: U_V30, lanes: 1 }] } + - { id: 130, symbol: V31, name: v31, encoding: 31, dwarf_register: 127, units: [{ unit: U_V31, lanes: 1 }] } + +register_banks: + - { id: 1, symbol: GPR, name: GPRB, maximum_size_bits: 64 } + - { id: 2, symbol: FPR, name: FPRB, maximum_size_bits: 64 } + - { id: 3, symbol: CSR, name: CSRB, maximum_size_bits: 32 } + - { id: 4, symbol: VR, name: VRB, maximum_size_bits: 512 } + +register_classes: + - + id: 1 + symbol: GPR + name: GPR + bank: GPR + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30, X31] + allocation_order: [X10, X11, X12, X13, X14, X15, X16, X17, X5, X6, X7, X28, X29, X30, X31, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X1] + - + id: 2 + symbol: GPRC + name: GPRC + bank: GPR + superclasses: [GPR] + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [X8, X9, X10, X11, X12, X13, X14, X15] + allocation_order: [X10, X11, X12, X13, X14, X15, X8, X9] + - + id: 3 + symbol: FPR64 + name: FPR64 + bank: FPR + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [F0_D, F1_D, F2_D, F3_D, F4_D, F5_D, F6_D, F7_D, F8_D, F9_D, F10_D, F11_D, F12_D, F13_D, F14_D, F15_D, F16_D, F17_D, F18_D, F19_D, F20_D, F21_D, F22_D, F23_D, F24_D, F25_D, F26_D, F27_D, F28_D, F29_D, F30_D, F31_D] + allocation_order: [F15_D, F14_D, F13_D, F12_D, F11_D, F10_D, F0_D, F1_D, F2_D, F3_D, F4_D, F5_D, F6_D, F7_D, F16_D, F17_D, F28_D, F29_D, F30_D, F31_D, F8_D, F9_D, F18_D, F19_D, F20_D, F21_D, F22_D, F23_D, F24_D, F25_D, F26_D, F27_D] + - + id: 4 + symbol: FPR64C + name: FPR64C + bank: FPR + superclasses: [FPR64] + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [F8_D, F9_D, F10_D, F11_D, F12_D, F13_D, F14_D, F15_D] + allocation_order: [F15_D, F14_D, F13_D, F12_D, F11_D, F10_D, F8_D, F9_D] + - + id: 5 + symbol: FPR32 + name: FPR32 + bank: FPR + size_bits: 32 + spill_size_bits: 32 + spill_alignment_bits: 32 + lanes: 1 + registers: [F0_F, F1_F, F2_F, F3_F, F4_F, F5_F, F6_F, F7_F, F8_F, F9_F, F10_F, F11_F, F12_F, F13_F, F14_F, F15_F, F16_F, F17_F, F18_F, F19_F, F20_F, F21_F, F22_F, F23_F, F24_F, F25_F, F26_F, F27_F, F28_F, F29_F, F30_F, F31_F] + allocation_order: [F15_F, F14_F, F13_F, F12_F, F11_F, F10_F, F0_F, F1_F, F2_F, F3_F, F4_F, F5_F, F6_F, F7_F, F16_F, F17_F, F28_F, F29_F, F30_F, F31_F, F8_F, F9_F, F18_F, F19_F, F20_F, F21_F, F22_F, F23_F, F24_F, F25_F, F26_F, F27_F] + - + id: 6 + symbol: FPR32C + name: FPR32C + bank: FPR + superclasses: [FPR32] + size_bits: 32 + spill_size_bits: 32 + spill_alignment_bits: 32 + lanes: 1 + registers: [F8_F, F9_F, F10_F, F11_F, F12_F, F13_F, F14_F, F15_F] + allocation_order: [F15_F, F14_F, F13_F, F12_F, F11_F, F10_F, F8_F, F9_F] + - + id: 7 + symbol: GPR_NX0 + name: GPR_NX0 + bank: GPR + superclasses: [GPR] + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30, X31] + allocation_order: [X10, X11, X12, X13, X14, X15, X16, X17, X5, X6, X7, X28, X29, X30, X31, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X1] + - + id: 8 + symbol: GPR_NX0_NX2 + name: GPR_NX0_NX2 + bank: GPR + superclasses: [GPR_NX0] + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [X1, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30, X31] + allocation_order: [X10, X11, X12, X13, X14, X15, X16, X17, X5, X6, X7, X28, X29, X30, X31, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X1] + - + id: 9 + symbol: FFLAGS + name: FFLAGS + bank: CSR + size_bits: 32 + spill_size_bits: 32 + spill_alignment_bits: 32 + lanes: 1 + registers: [FFLAGS] + allocation_order: [] + allocatable: false + - + id: 10 + symbol: FRM + name: FRM + bank: CSR + size_bits: 32 + spill_size_bits: 32 + spill_alignment_bits: 32 + lanes: 1 + registers: [FRM] + allocation_order: [] + allocatable: false + - + id: 11 + symbol: VR + name: VR + bank: VR + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [V0, V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, V21, V22, V23, V24, V25, V26, V27, V28, V29, V30, V31] + allocation_order: [] + allocatable: false + - + id: 12 + symbol: GPRJALRNonX7 + name: GPRJALRNonX7 + bank: GPR + superclasses: [GPR] + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [X6, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28, X29, X30, X31] + allocation_order: [X10, X11, X12, X13, X14, X15, X16, X17, X6, X28, X29, X30, X31, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27] + - + id: 13 + symbol: SR07 + name: SR07 + bank: GPR + superclasses: [GPR] + size_bits: 64 + spill_size_bits: 64 + spill_alignment_bits: 64 + lanes: 1 + registers: [X8, X9, X18, X19, X20, X21, X22, X23] + allocation_order: [X8, X9, X18, X19, X20, X21, X22, X23] + +register_masks: + - + name: CSR_ILP32_LP64 + preserved: [X1, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27] + - + name: CSR_ILP32D_LP64D + preserved: [X1, X8, X9, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, F8_D, F9_D, F18_D, F19_D, F20_D, F21_D, F22_D, F23_D, F24_D, F25_D, F26_D, F27_D, F8_F, F9_F, F18_F, F19_F, F20_F, F21_F, F22_F, F23_F, F24_F, F25_F, F26_F, F27_F] + +mir_operand_flags: + direct_mask: 255 + direct: + - { name: riscv-call, value: 1 } + - { name: riscv-pcrel-hi, value: 2 } + - { name: riscv-pcrel-lo, value: 4 } + - { name: riscv-copy-f32, value: 8 } + - { name: riscv-copy-f64, value: 16 } + - { name: riscv-hi, value: 32 } + - { name: riscv-lo, value: 64 } + - { name: riscv-got-pcrel-hi, value: 128 } + bitmask: [] diff --git a/backend/lib/Target/RISCV/RISCVScheduling.cpp b/backend/lib/Target/RISCV/RISCVScheduling.cpp new file mode 100644 index 0000000..b7947d4 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVScheduling.cpp @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCV.h" +#include "rcc/CodeGen/MacroFusion.h" +#include "rcc/CodeGen/ScheduleDAG.h" +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include <array> +#include <cstdint> +#include <memory> +#include <optional> +#include <span> +#include <string> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +#define GET_MACRO_FUSION_RULES +#include "RISCVGenMacroFusion.inc" + +class RISCVSchedulingHooks final : public TargetSchedulingHooks { +public: + explicit RISCVSchedulingHooks(const RISCVSubtargetFeatures &Features) { + for (const MacroFusionRule &Rule : generatedMacroFusionRules()) { + if (Features.hasFeature(Rule.Feature)) + MacroFusionRules.push_back(Rule); + } + } + + [[nodiscard]] Expected<void> mutateScheduleDAG(ScheduleDAG &DAG, const MachineFunction &, + const TargetDescription &Target, ScheduleDAGMode, + bool EmitDebugOutput) const override { + return applyMacroFusion(DAG, MacroFusionRules, Target, EmitDebugOutput); + } + +private: + std::vector<MacroFusionRule> MacroFusionRules; +}; + +enum class Resource : std::uint32_t { + Core = 1, + IntegerALU = 2, + Memory = 3, + Branch = 4, + MultiplyDivide = 5, + FloatingPoint = 6, + System = 7, +}; + +enum class PressureSet : std::uint32_t { + GPR = 1, + FPR = 2, +}; + +[[nodiscard]] constexpr ProcessorResourceID resource(Resource Value) noexcept { + return ProcessorResourceID::fromValue(static_cast<std::uint32_t>(Value)); +} + +[[nodiscard]] constexpr RegisterPressureSetID pressureSet(PressureSet Value) noexcept { + return RegisterPressureSetID::fromValue(static_cast<std::uint32_t>(Value)); +} + +[[nodiscard]] constexpr RegisterClassID registerClass(RegisterClass Value) noexcept { + return RegisterClassID::fromValue(static_cast<std::uint32_t>(Value)); +} + +[[nodiscard]] std::vector<SchedulingResourceUse> resourceUses(SchedulingClass Class) { + switch (Class) { + case SchedulingClass::ALU: + case SchedulingClass::COMPRESSED: + return {{resource(Resource::IntegerALU), 1}}; + case SchedulingClass::LOAD: + case SchedulingClass::STORE: + return {{resource(Resource::Memory), 1}}; + case SchedulingClass::BRANCH: + return {{resource(Resource::Branch), 1}}; + case SchedulingClass::SYSTEM: + case SchedulingClass::FENCE: + return {{resource(Resource::System), 1}}; + case SchedulingClass::MUL: + return {{resource(Resource::MultiplyDivide), 3}}; + case SchedulingClass::DIV: + return {{resource(Resource::MultiplyDivide), 20}}; + case SchedulingClass::ATOMIC: + return {{resource(Resource::Memory), 4}, {resource(Resource::System), 1}}; + case SchedulingClass::FP_ADD: + case SchedulingClass::FP_MUL: + case SchedulingClass::FP_CONVERT: + return {{resource(Resource::FloatingPoint), 4}}; + case SchedulingClass::FP_DIV: + case SchedulingClass::FP_SQRT: + return {{resource(Resource::FloatingPoint), 20}}; + case SchedulingClass::None: + return {}; + } + return {}; +} + +[[nodiscard]] TargetSchedulingModelDesc createDescription(const TargetSchedulingHooks &Hooks) { + TargetSchedulingModelDesc Description; + Description.IssueWidth = 2; + Description.DispatchLimit = 2; + Description.ProcessorResources = { + {resource(Resource::Core), "core", 2, std::nullopt}, + {resource(Resource::IntegerALU), "integer-alu", 2, resource(Resource::Core)}, + {resource(Resource::Memory), "memory", 1, resource(Resource::Core)}, + {resource(Resource::Branch), "branch", 1, resource(Resource::Core)}, + {resource(Resource::MultiplyDivide), "multiply-divide", 1, resource(Resource::Core)}, + {resource(Resource::FloatingPoint), "floating-point", 1, resource(Resource::Core)}, + {resource(Resource::System), "system", 1, resource(Resource::Core)}, + }; + for (const SchedulingClassInfo &Class : schedulingClasses()) { + SchedulingClassModelDesc Model{ + .ID = Class.ID, + .Name = std::string(Class.Name), + .MicroOps = Class.MicroOps, + .Latency = Class.Latency, + .Resources = resourceUses(static_cast<SchedulingClass>(Class.ID.value())), + .WriteLatencies = {}, + .ReadAdvances = {}, + }; + Model.WriteLatencies.push_back({0, Class.Latency}); + switch (static_cast<SchedulingClass>(Class.ID.value())) { + case SchedulingClass::ALU: + Model.ReadAdvances = { + {1, SchedulingClassID::fromValue(static_cast<std::uint32_t>(SchedulingClass::LOAD)), 1}, + {2, SchedulingClassID::fromValue(static_cast<std::uint32_t>(SchedulingClass::LOAD)), 1}, + }; + break; + default: + break; + } + Description.SchedulingClasses.push_back(std::move(Model)); + } + Description.PressureSets = { + {pressureSet(PressureSet::GPR), + "gpr", + {registerClass(RegisterClass::GPR), registerClass(RegisterClass::GPRC), + registerClass(RegisterClass::GPR_NX0), registerClass(RegisterClass::GPR_NX0_NX2), + registerClass(RegisterClass::GPRJALRNonX7)}, + 27}, + {pressureSet(PressureSet::FPR), + "fpr", + {registerClass(RegisterClass::FPR64), registerClass(RegisterClass::FPR64C), + registerClass(RegisterClass::FPR32), registerClass(RegisterClass::FPR32C)}, + 32}, + }; + Description.Hooks = &Hooks; + return Description; +} + +} // namespace + +std::unique_ptr<TargetSchedulingHooks> +createSchedulingHooks(const RISCVSubtargetFeatures &Features) { + return std::make_unique<RISCVSchedulingHooks>(Features); +} + +Expected<TargetSchedulingModel> createSchedulingModel(const TargetSchedulingHooks &Hooks) { + return TargetSchedulingModel::create(createDescription(Hooks)); +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVSubtarget.cpp b/backend/lib/Target/RISCV/RISCVSubtarget.cpp new file mode 100644 index 0000000..5e9efb6 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVSubtarget.cpp @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVSubtarget.h" + +#include "GISel/RISCVCallLowering.h" +#include "GISel/RISCVLegalizerInfo.h" +#include "GISel/RISCVRegisterBankInfo.h" +#include "RISCV.h" +#include "rcc/CodeGen/GlobalISel/InstructionSelector.h" +#include "rcc/CodeGen/TargetSchedulingModel.h" + +#include <array> +#include <cstddef> +#include <cstdlib> +#include <memory> +#include <string_view> +#include <utility> + +namespace rcc::riscv { +namespace { + +struct ExtensionName final { + std::string_view Name; + RISCVExtension Extension; +}; + +struct OptionName final { + std::string_view Name; + RISCVSubtargetOption Option; +}; + +constexpr std::array ExtensionNames{ + ExtensionName{"i", RISCVExtension::I}, + ExtensionName{"m", RISCVExtension::M}, + ExtensionName{"a", RISCVExtension::A}, + ExtensionName{"f", RISCVExtension::F}, + ExtensionName{"d", RISCVExtension::D}, + ExtensionName{"c", RISCVExtension::C}, + ExtensionName{"zba", RISCVExtension::Zba}, + ExtensionName{"zbb", RISCVExtension::Zbb}, + ExtensionName{"zicsr", RISCVExtension::Zicsr}, + ExtensionName{"zifencei", RISCVExtension::Zifencei}, + ExtensionName{"zacas", RISCVExtension::Zacas}, + ExtensionName{"zabha", RISCVExtension::Zabha}, + ExtensionName{"zcmp", RISCVExtension::Zcmp}, +}; + +constexpr std::array OptionNames{ + OptionName{"disable-postmisched-load-clustering", + RISCVSubtargetOption::DisablePostMISchedLoadClustering}, + OptionName{"disable-postmisched-store-clustering", + RISCVSubtargetOption::DisablePostMISchedStoreClustering}, + OptionName{"fusion-add-load", RISCVSubtargetOption::FusionAddLoad}, + OptionName{"fusion-add-mem", RISCVSubtargetOption::FusionAddMemory}, + OptionName{"fusion-addi-load", RISCVSubtargetOption::FusionAddImmediateLoad}, + OptionName{"fusion-auipc-addi", RISCVSubtargetOption::FusionAUIPCAddImmediate}, + OptionName{"fusion-auipc-load", RISCVSubtargetOption::FusionAUIPCLoad}, + OptionName{"fusion-bfext", RISCVSubtargetOption::FusionBitfieldExtract}, + OptionName{"fusion-ld-add", RISCVSubtargetOption::FusionLoadDoubleAdd}, + OptionName{"fusion-logic-imm-reg", RISCVSubtargetOption::FusionLogicImmediateRegister}, + OptionName{"fusion-logic-reg-imm", RISCVSubtargetOption::FusionLogicRegisterImmediate}, + OptionName{"fusion-logic-reg-reg", RISCVSubtargetOption::FusionLogicRegisterRegister}, + OptionName{"fusion-lui-addi", RISCVSubtargetOption::FusionLUIAddImmediate}, + OptionName{"fusion-lui-load", RISCVSubtargetOption::FusionLUILoad}, + OptionName{"fusion-mul-add", RISCVSubtargetOption::FusionMultiplyAdd}, + OptionName{"fusion-shift-bit-extract", RISCVSubtargetOption::FusionShiftBitExtract}, + OptionName{"fusion-shxadd-load", RISCVSubtargetOption::FusionShiftedAddLoad}, + OptionName{"fusion-shifted-zextw", RISCVSubtargetOption::FusionShiftedZeroExtendWord}, + OptionName{"fusion-zexth", RISCVSubtargetOption::FusionZeroExtendHalf}, + OptionName{"fusion-zextw", RISCVSubtargetOption::FusionZeroExtendWord}, + OptionName{"prefer-w-inst", RISCVSubtargetOption::PreferWordInstructions}, + OptionName{"reserve-x6", RISCVSubtargetOption::ReserveX6}, + OptionName{"reserve-x7", RISCVSubtargetOption::ReserveX7}, + OptionName{"save-restore", RISCVSubtargetOption::SaveRestore}, + OptionName{"tagged-globals", RISCVSubtargetOption::TaggedGlobals}, + OptionName{"unaligned-scalar-mem", RISCVSubtargetOption::UnalignedScalarMemory}, + OptionName{"use-postra-scheduler", RISCVSubtargetOption::UsePostRAScheduler}, +}; + +[[nodiscard]] constexpr std::uint16_t extensionBit(RISCVExtension Extension) noexcept { + return static_cast<std::uint16_t>(std::uint16_t{1} << static_cast<std::uint8_t>(Extension)); +} + +[[nodiscard]] constexpr std::uint32_t optionBit(RISCVSubtargetOption Option) noexcept { + static_assert(static_cast<std::uint8_t>(RISCVSubtargetOption::Count) <= 32); + return std::uint32_t{1} << static_cast<std::uint8_t>(Option); +} + +[[nodiscard]] const ExtensionName *findExtension(std::string_view Name) noexcept { + for (const ExtensionName &Entry : ExtensionNames) { + if (Entry.Name == Name) + return &Entry; + } + return nullptr; +} + +[[nodiscard]] const OptionName *findOption(std::string_view Name) noexcept { + for (const OptionName &Entry : OptionNames) { + if (Entry.Name == Name) + return &Entry; + } + return nullptr; +} + +} // namespace + +Expected<RISCVABI> parseRISCVABI(std::string_view Name) { + if (Name == "lp64") + return RISCVABI::LP64; + if (Name == "lp64d") + return RISCVABI::LP64D; + return makeUnexpected(ErrorCode::InvalidInvocation, + "RISC-V backend does not support target ABI '" + std::string(Name) + "'"); +} + +Expected<RISCVSubtargetFeatures> +RISCVSubtargetFeatures::fromFeatureList(std::span<const std::string> Features) { + std::uint16_t Enabled = extensionBit(RISCVExtension::I); + std::uint16_t ExplicitlyDisabled = 0; + std::uint32_t Options = 0; + for (const std::string &Feature : Features) { + if (Feature.size() < 2 || (Feature.front() != '+' && Feature.front() != '-')) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "RISC-V target feature must use canonical +/- spelling"); + } + const std::string_view Name = std::string_view(Feature).substr(1); + const ExtensionName *Extension = findExtension(Name); + const OptionName *Option = findOption(Name); + if (Extension == nullptr && Option == nullptr) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "RISC-V backend does not support target feature '" + Feature + "'"); + } + if (Extension != nullptr) { + if (Feature.front() == '+') { + Enabled |= extensionBit(Extension->Extension); + ExplicitlyDisabled &= static_cast<std::uint16_t>(~extensionBit(Extension->Extension)); + } else { + Enabled &= static_cast<std::uint16_t>(~extensionBit(Extension->Extension)); + ExplicitlyDisabled |= extensionBit(Extension->Extension); + } + } else if (Feature.front() == '+') { + Options |= optionBit(Option->Option); + } else { + Options &= ~optionBit(Option->Option); + } + } + + RISCVSubtargetFeatures Result(Enabled, Options); + if (!Result.has(RISCVExtension::I)) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "RV64 target feature configuration cannot disable the base I extension"); + } + if (Result.has(RISCVExtension::D) && + (ExplicitlyDisabled & extensionBit(RISCVExtension::F)) != 0) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "RISC-V D extension requires the F extension"); + } + if (Result.has(RISCVExtension::D)) + Enabled |= extensionBit(RISCVExtension::F); + if ((Result.has(RISCVExtension::Zacas) || Result.has(RISCVExtension::Zabha)) && + (ExplicitlyDisabled & extensionBit(RISCVExtension::A)) != 0) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "RISC-V Zacas and Zabha extensions require the A extension"); + } + if (Result.has(RISCVExtension::Zacas) || Result.has(RISCVExtension::Zabha)) + Enabled |= extensionBit(RISCVExtension::A); + return RISCVSubtargetFeatures(Enabled, Options); +} + +RISCVSubtargetFeatures RISCVSubtargetFeatures::rv64gc() noexcept { + return RISCVSubtargetFeatures(extensionBit(RISCVExtension::I) | extensionBit(RISCVExtension::M) | + extensionBit(RISCVExtension::A) | extensionBit(RISCVExtension::F) | + extensionBit(RISCVExtension::D) | extensionBit(RISCVExtension::C) | + extensionBit(RISCVExtension::Zicsr) | + extensionBit(RISCVExtension::Zifencei)); +} + +bool RISCVSubtargetFeatures::has(RISCVExtension Extension) const noexcept { + if (Extension >= RISCVExtension::Count) + return false; + return (Enabled & extensionBit(Extension)) != 0; +} + +bool RISCVSubtargetFeatures::has(RISCVSubtargetOption Option) const noexcept { + if (Option >= RISCVSubtargetOption::Count) + return false; + return (Options & optionBit(Option)) != 0; +} + +bool RISCVSubtargetFeatures::hasFeature(std::string_view Feature) const noexcept { + if (const OptionName *Option = findOption(Feature)) + return has(Option->Option); + if (const ExtensionName *Extension = findExtension(Feature)) + return has(Extension->Extension); + return false; +} + +RISCVSubtarget::RISCVSubtarget(RISCVSubtargetFeatures Features, RelocationModel Relocation, + CodeModel Code, RISCVABI ABI) + : Features(Features), Relocation(Relocation), Code(Code), ABI(ABI), + SchedulingHooks(createSchedulingHooks(Features)) { + FrameLoweringInfo = createFrameLowering(*this); + CallLoweringInfo = std::make_unique<RISCVCallLowering>(); + RegBankInfo = std::make_unique<RISCVRegisterBankInfo>(); + InstSelector.reset(createRISCVInstructionSelector(*this, *RegBankInfo)); + Legalizer = std::make_unique<RISCVLegalizerInfo>(*this); + auto Model = createSchedulingModel(*SchedulingHooks); + if (!Model) + std::abort(); + SchedulingModel = std::make_unique<TargetSchedulingModel>(std::move(*Model)); +} + +RISCVSubtarget::~RISCVSubtarget() = default; + +const TargetCallLowering *RISCVSubtarget::getCallLowering() const { + return CallLoweringInfo.get(); +} + +InstructionSelector *RISCVSubtarget::getInstructionSelector() const { + return InstSelector.get(); +} + +const LegalizerInfo *RISCVSubtarget::getLegalizerInfo() const { + return Legalizer.get(); +} + +const RISCVRegisterBankInfo *RISCVSubtarget::getRegBankInfo() const { + return RegBankInfo.get(); +} + +const TargetFrameLowering &RISCVSubtarget::frameLowering() const noexcept { + return *FrameLoweringInfo; +} + +const TargetSchedulingModel &RISCVSubtarget::schedulingModel() const noexcept { + return *SchedulingModel; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVTarget.cpp b/backend/lib/Target/RISCV/RISCVTarget.cpp new file mode 100644 index 0000000..4f33ce3 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVTarget.cpp @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVTarget.h" + +#include "rcc/CodeGen/GenericOpcodes.h" +#include "rcc/Target/RISCV/RISCVFunctionInfo.h" + +#include <algorithm> +#include <array> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <initializer_list> +#include <memory> +#include <ranges> +#include <span> +#include <string> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] PhysicalRegister physical(PhysicalRegisterID ID) { + const auto Register = physicalRegister(ID); + assert(Register.has_value()); + return *Register; +} + +[[nodiscard]] RegisterUnitLane unitLane(RegisterUnit Unit, std::uint64_t Lanes) { + return { + .Unit = RegisterUnitID::fromValue(static_cast<std::uint32_t>(Unit)), + .Lanes = LaneMask::fromRaw(Lanes), + }; +} + +[[nodiscard]] std::vector<PhysicalRegister> +physicalRegisters(std::initializer_list<PhysicalRegisterID> IDs) { + std::vector<PhysicalRegister> Result; + Result.reserve(IDs.size()); + for (const PhysicalRegisterID ID : IDs) + Result.push_back(physical(ID)); + return Result; +} + +[[nodiscard]] std::vector<RegisterClassID> +registerClasses(std::initializer_list<RegisterClass> IDs) { + std::vector<RegisterClassID> Result; + Result.reserve(IDs.size()); + for (const RegisterClass ID : IDs) + Result.push_back(RegisterClassID::fromValue(static_cast<std::uint32_t>(ID))); + return Result; +} + +[[nodiscard]] RegisterClassDesc registerClass( + RegisterClass ID, const char *Name, std::initializer_list<PhysicalRegisterID> Registers, + std::initializer_list<PhysicalRegisterID> AllocationOrder, + std::initializer_list<RegisterClass> SuperClasses, RegisterBank Bank, std::uint32_t SizeInBits, + std::uint32_t SpillSizeInBits, std::uint32_t SpillAlignmentInBits, std::uint64_t Lanes, + bool Allocatable, std::uint32_t CopyCost) { + return { + .ID = RegisterClassID::fromValue(static_cast<std::uint32_t>(ID)), + .Name = Name, + .Registers = physicalRegisters(Registers), + .AllocationOrder = physicalRegisters(AllocationOrder), + .SizeInBits = SizeInBits, + .SpillSizeInBits = SpillSizeInBits, + .SpillAlignmentInBits = SpillAlignmentInBits, + .Lanes = LaneMask::fromRaw(Lanes), + .SupportedSubRegs = {}, + .SuperClasses = registerClasses(SuperClasses), + .DefaultBank = RegisterBankID::fromValue(static_cast<std::uint32_t>(Bank)), + .Allocatable = Allocatable, + .CopyCost = CopyCost, + }; +} + +[[nodiscard]] Expected<void> appendRISCVSubRegisterInfo(TargetRegisterInfoDesc &Description) { + constexpr SubRegIndex FPR32SubReg = SubRegIndex::fromValue(1); + constexpr std::uint32_t FirstDouble = static_cast<std::uint32_t>(PhysicalRegisterID::F0_D); + constexpr std::uint32_t LastDouble = static_cast<std::uint32_t>(PhysicalRegisterID::F31_D); + constexpr std::uint32_t FirstSingle = static_cast<std::uint32_t>(PhysicalRegisterID::F0_F); + constexpr std::uint32_t LastSingle = static_cast<std::uint32_t>(PhysicalRegisterID::F31_F); + static_assert(LastDouble - FirstDouble == 31); + static_assert(LastSingle - FirstSingle == 31); + + Description.SubRegIndices.push_back({FPR32SubReg, "sub_32", LaneMask::fromRaw(1), 32, 0}); + for (std::uint32_t Index = 0; Index != 32; ++Index) { + auto Double = PhysicalRegister::create(FirstDouble + Index); + auto Single = PhysicalRegister::create(FirstSingle + Index); + if (!Double || !Single) + return makeUnexpected(ErrorCode::InternalError, "RISC-V FPR view ID is invalid"); + Description.PhysicalSubRegisters.push_back({*Double, FPR32SubReg, *Single}); + } + + const auto AddClassMapping = [&](RegisterClass Super, RegisterClass Sub) -> Expected<void> { + const RegisterClassID SuperID = RegisterClassID::fromValue(static_cast<std::uint32_t>(Super)); + const RegisterClassID SubID = RegisterClassID::fromValue(static_cast<std::uint32_t>(Sub)); + const auto Position = + std::ranges::find(Description.RegisterClasses, SuperID, &RegisterClassDesc::ID); + if (Position == Description.RegisterClasses.end()) { + return makeUnexpected(ErrorCode::InternalError, "RISC-V FPR super-register class is missing"); + } + Position->SupportedSubRegs.push_back(FPR32SubReg); + Description.SubRegClassMappings.push_back({SuperID, FPR32SubReg, SubID}); + return {}; + }; + if (auto Result = AddClassMapping(RegisterClass::FPR64, RegisterClass::FPR32); !Result) + return Result; + return AddClassMapping(RegisterClass::FPR64C, RegisterClass::FPR32C); +} + +[[nodiscard]] Expected<void> appendRISCVVectorTupleInfo(TargetRegisterInfoDesc &Description) { + constexpr std::uint32_t FirstVector = static_cast<std::uint32_t>(PhysicalRegisterID::V0); + constexpr std::uint32_t LastVector = static_cast<std::uint32_t>(PhysicalRegisterID::V31); + static_assert(LastVector - FirstVector == 31); + + const RegisterBankID VectorBankID = + RegisterBankID::fromValue(static_cast<std::uint32_t>(RegisterBank::VR)); + auto VectorBank = + std::ranges::find(Description.RegisterBanks, VectorBankID, &RegisterBankDesc::ID); + if (VectorBank == Description.RegisterBanks.end()) + return makeUnexpected(ErrorCode::InternalError, "RISC-V vector register bank is missing"); + + std::uint32_t NextRegisterID = 1; + for (const PhysicalRegisterDesc &Register : Description.PhysicalRegisters) + NextRegisterID = std::max(NextRegisterID, Register.Reg.number() + 1U); + std::uint32_t NextClassID = 1; + for (const RegisterClassDesc &Class : Description.RegisterClasses) + NextClassID = std::max(NextClassID, Class.ID.value() + 1U); + + for (std::uint32_t Length = 2; Length <= 8; ++Length) { + std::vector<PhysicalRegister> TupleRegisters; + TupleRegisters.reserve(33U - Length); + for (std::uint32_t Start = 0; Start + Length <= 32; ++Start) { + auto Tuple = PhysicalRegister::create(NextRegisterID++); + if (!Tuple) + return makeUnexpected(ErrorCode::InternalError, "RISC-V vector tuple ID is invalid"); + + std::string Name; + std::vector<RegisterUnitLane> Units; + Units.reserve(Length); + for (std::uint32_t Offset = 0; Offset < Length; ++Offset) { + auto Element = PhysicalRegister::create(FirstVector + Start + Offset); + if (!Element) + return makeUnexpected(ErrorCode::InternalError, + "RISC-V vector tuple element ID is invalid"); + const auto ElementDescription = + std::ranges::find(Description.PhysicalRegisters, *Element, &PhysicalRegisterDesc::Reg); + if (ElementDescription == Description.PhysicalRegisters.end() || + ElementDescription->Units.size() != 1) { + return makeUnexpected(ErrorCode::InternalError, + "RISC-V vector tuple element has invalid register units"); + } + if (!Name.empty()) + Name.push_back('_'); + Name.append(ElementDescription->Name); + Units.push_back(ElementDescription->Units.front()); + } + + Description.PhysicalRegisters.push_back({ + .Reg = *Tuple, + .Name = std::move(Name), + .Encoding = Start, + .Units = std::move(Units), + .IsConstant = false, + .IsArtificial = false, + .IsReserved = false, + .AlternativeNames = {}, + }); + TupleRegisters.push_back(*Tuple); + } + + const RegisterClassID ClassID = RegisterClassID::fromValue(NextClassID++); + Description.RegisterClasses.push_back({ + .ID = ClassID, + .Name = "VRTuple" + std::to_string(Length), + .Registers = std::move(TupleRegisters), + .AllocationOrder = {}, + .SizeInBits = Length * 64U, + .SpillSizeInBits = Length * 64U, + .SpillAlignmentInBits = 64, + .Lanes = LaneMask::fromRaw(1), + .SupportedSubRegs = {}, + .SuperClasses = {}, + .DefaultBank = VectorBankID, + .Allocatable = false, + .CopyCost = Length, + }); + VectorBank->RegisterClasses.push_back(ClassID); + } + return {}; +} + +[[nodiscard]] std::shared_ptr<const RegisterMask> +makePreservedMask(std::initializer_list<PhysicalRegisterID> Preserved) { + std::vector<std::uint64_t> Words; + for (const PhysicalRegisterID ID : Preserved) { + const std::uint32_t Number = static_cast<std::uint32_t>(ID); + const std::size_t Word = Number / 64U; + if (Words.size() <= Word) + Words.resize(Word + 1U); + Words[Word] |= std::uint64_t{1} << (Number % 64U); + } + return std::make_shared<const RegisterMask>(std::move(Words)); +} + +#include "RISCVGenTargetDescription.inc" + +} // namespace + +Expected<PhysicalRegister> physicalRegister(PhysicalRegisterID ID) { + return PhysicalRegister::create(static_cast<std::uint32_t>(ID)); +} + +Expected<TargetRegisterInfo> createRegisterInfo() { + TargetRegisterInfoDesc Description; + appendGeneratedRegisterInfo(Description); + if (auto Result = appendRISCVSubRegisterInfo(Description); !Result) + return std::unexpected(Result.error()); + if (auto Result = appendRISCVVectorTupleInfo(Description); !Result) + return std::unexpected(Result.error()); + return TargetRegisterInfo::create(std::move(Description)); +} + +Expected<TargetMIRTextInfo> createMIRTextInfo() { + auto Registers = createRegisterInfo(); + if (!Registers) + return std::unexpected(Registers.error()); + TargetMIRTextInfoDesc Description; + appendGeneratedMIRTextInfo(Description); + configureFunctionInfoMIRSchema(Description); + return TargetMIRTextInfo::create(std::move(Description), *Registers); +} + +Expected<TargetDescription> createTargetDescription() { + auto Registers = createRegisterInfo(); + if (!Registers) + return std::unexpected(Registers.error()); + + std::vector<InstructionDescriptor> Descriptors; + std::vector<rcc::ImmediatePredicate> Predicates; + appendGeneratedInstructionInfo(Descriptors, Predicates); + auto GenericInfo = appendGenericInstructionInfo(Descriptors, Predicates); + if (!GenericInfo) + return std::unexpected(GenericInfo.error()); + auto Instructions = InstructionSet::create(std::move(Descriptors), std::move(Predicates)); + if (!Instructions) + return std::unexpected(Instructions.error()); + + TargetMIRTextInfoDesc MIRText; + appendGeneratedMIRTextInfo(MIRText); + configureFunctionInfoMIRSchema(MIRText); + TargetBooleanContentInfo BooleanInfo{ + .ScalarInteger = BooleanContent::ZeroOrOne, + .ScalarFloatingPoint = BooleanContent::ZeroOrOne, + }; + auto StackPointer = physicalRegister(PhysicalRegisterID::X2); + if (!StackPointer) + return std::unexpected(StackPointer.error()); + TargetStackInfo StackInfo{ + .StackPointerRegisterToSaveRestore = *StackPointer, + .GrowthDirection = StackGrowthDirection::Down, + }; + return TargetDescription::create(std::move(*Instructions), std::move(*Registers), + std::move(MIRText), BooleanInfo, StackInfo); +} + +std::shared_ptr<const RegisterMask> createCallPreservedMask(CallingConvention Convention) { + const CallingConventionInfo *Info = callingConvention(Convention); + if (Info == nullptr) + return {}; + TargetMIRTextInfoDesc Description; + appendGeneratedMIRTextInfo(Description); + configureFunctionInfoMIRSchema(Description); + const auto Mask = std::ranges::find(Description.RegisterMasks, Info->PreservedMaskName, + &MIRRegisterMaskDesc::Name); + return Mask == Description.RegisterMasks.end() ? std::shared_ptr<const RegisterMask>{} + : Mask->Mask; +} + +std::span<const SchedulingClassInfo> schedulingClasses() noexcept { + return GeneratedSchedulingClasses; +} + +std::span<const EncodingCategoryInfo> encodingCategories() noexcept { + return GeneratedEncodingCategories; +} + +std::span<const AssemblyFormatInfo> assemblyFormats() noexcept { + return GeneratedAssemblyFormats; +} + +std::span<const AssemblyInstructionInfo> assemblyInstructions() noexcept { + return GeneratedAssemblyInstructions; +} + +const AssemblyInstructionInfo *assemblyInstruction(Opcode InstructionOpcode) noexcept { + for (const AssemblyInstructionInfo &Info : GeneratedAssemblyInstructions) { + if (Info.InstructionOpcode == InstructionOpcode) + return &Info; + } + return nullptr; +} + +std::span<const CallingConventionInfo> callingConventions() noexcept { + return GeneratedCallingConventions; +} + +const CallingConventionInfo *callingConvention(CallingConvention ID) noexcept { + for (const CallingConventionInfo &Info : GeneratedCallingConventions) { + if (Info.ID == ID) + return &Info; + } + return nullptr; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVTargetConstant.cpp b/backend/lib/Target/RISCV/RISCVTargetConstant.cpp new file mode 100644 index 0000000..6fda900 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVTargetConstant.cpp @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/RISCV/RISCVTargetConstant.h" + +#include "rcc/Target/RISCV/RISCVTargetRelocationKinds.h" + +#include <bit> +#include <charconv> +#include <cstddef> +#include <cstdint> +#include <string> +#include <string_view> +#include <system_error> +#include <utility> +#include <vector> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] std::string_view kindSpelling(RISCVSymbolConstantKind Kind) noexcept { + switch (Kind) { + case RISCVSymbolConstantKind::GlobalValue: + return "global"; + case RISCVSymbolConstantKind::ExternalSymbol: + return "external"; + } + return {}; +} + +[[nodiscard]] Expected<RISCVSymbolConstantKind> parseKind(std::string_view Text) { + if (Text == "global") + return RISCVSymbolConstantKind::GlobalValue; + if (Text == "external") + return RISCVSymbolConstantKind::ExternalSymbol; + return makeUnexpected(ErrorCode::ParseError, "unknown RV64GC symbol constant kind"); +} + +void appendU64(std::vector<std::byte> &Bytes, std::uint64_t Value) { + for (std::uint8_t Shift = 0; Shift < 64; Shift += 8) + Bytes.push_back(static_cast<std::byte>(Value >> Shift)); +} + +[[nodiscard]] std::uint64_t readU64(std::span<const std::byte> Bytes) noexcept { + std::uint64_t Result = 0; + for (std::uint8_t Shift = 0; Shift < 64; Shift += 8) + Result |= std::to_integer<std::uint64_t>(Bytes[Shift / 8U]) << Shift; + return Result; +} + +} // namespace + +Expected<void> RISCVSymbolConstant::verify() const { + if (kindSpelling(ConstantKind).empty()) + return makeUnexpected(ErrorCode::VerificationError, "RV64GC symbol constant kind is invalid"); + if (SymbolName.empty() || SymbolName.find('\0') != std::string::npos) { + return makeUnexpected(ErrorCode::VerificationError, + "RV64GC symbol constant requires a valid symbol name"); + } + return {}; +} + +Expected<RISCVSymbolConstant> RISCVSymbolConstant::clone() const { + return RISCVSymbolConstant(ConstantKind, SymbolName, SymbolAddend); +} + +std::uint64_t RISCVSymbolConstant::stableHash() const noexcept { + constexpr std::uint64_t OffsetBasis = 14695981039346656037ULL; + constexpr std::uint64_t Prime = 1099511628211ULL; + std::uint64_t Hash = OffsetBasis; + const auto Mix = [&Hash](std::uint8_t Byte) { + Hash ^= Byte; + Hash *= Prime; + }; + Mix(static_cast<std::uint8_t>(ConstantKind)); + const std::uint64_t AddendBits = std::bit_cast<std::uint64_t>(SymbolAddend); + for (std::uint8_t Shift = 0; Shift < 64; Shift += 8) + Mix(static_cast<std::uint8_t>(AddendBits >> Shift)); + for (const char Character : SymbolName) + Mix(static_cast<unsigned char>(Character)); + return Hash; +} + +std::string RISCVSymbolConstant::print() const { + return std::string(kindSpelling(ConstantKind)) + "," + std::to_string(SymbolAddend) + "," + + SymbolName; +} + +Expected<std::vector<std::byte>> RISCVSymbolConstant::encode() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + std::vector<std::byte> Payload; + Payload.reserve(9U + SymbolName.size()); + Payload.push_back(static_cast<std::byte>(static_cast<std::uint8_t>(ConstantKind))); + appendU64(Payload, std::bit_cast<std::uint64_t>(SymbolAddend)); + for (const char Character : SymbolName) + Payload.push_back(static_cast<std::byte>(static_cast<unsigned char>(Character))); + return Payload; +} + +Expected<TargetConstantEmission> RISCVSymbolConstant::emit() const { + auto Verification = verify(); + if (!Verification) + return std::unexpected(Verification.error()); + TargetConstantEmission Emission; + Emission.Bytes.resize(8); + Emission.Fixups.push_back(TargetConstantFixup{ + .Offset = 0, + .Kind = targetRelocationKind(RISCVTargetRelocationKind::Absolute64), + .Symbol = SymbolName, + .Addend = SymbolAddend, + }); + return Emission; +} + +Alignment RISCVSymbolConstant::requiredAlignment() const noexcept { + return *Alignment::create(8); +} + +std::optional<TargetRelocationKindID> RISCVSymbolConstant::relocationKind() const noexcept { + return targetRelocationKind(RISCVTargetRelocationKind::Absolute64); +} + +Expected<RISCVSymbolConstant> RISCVSymbolConstant::parse(std::string_view Text) { + const std::size_t KindEnd = Text.find(','); + const std::size_t AddendEnd = + KindEnd == std::string_view::npos ? std::string_view::npos : Text.find(',', KindEnd + 1U); + if (KindEnd == std::string_view::npos || AddendEnd == std::string_view::npos) { + return makeUnexpected(ErrorCode::ParseError, + "RV64GC symbol constant must be 'kind,addend,symbol'"); + } + auto Kind = parseKind(Text.substr(0, KindEnd)); + if (!Kind) + return std::unexpected(Kind.error()); + std::int64_t Addend = 0; + const std::string_view AddendText = Text.substr(KindEnd + 1U, AddendEnd - KindEnd - 1U); + const auto [End, Error] = + std::from_chars(AddendText.data(), AddendText.data() + AddendText.size(), Addend); + if (Error != std::errc{} || End != AddendText.data() + AddendText.size()) + return makeUnexpected(ErrorCode::ParseError, "RV64GC symbol constant addend is invalid"); + RISCVSymbolConstant Result(*Kind, std::string(Text.substr(AddendEnd + 1U)), Addend); + auto Verification = Result.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Result; +} + +Expected<RISCVSymbolConstant> RISCVSymbolConstant::decode(std::uint32_t Version, + std::span<const std::byte> Payload) { + if (Version != schemaVersion() || Payload.size() < 10) { + return makeUnexpected(ErrorCode::ParseError, + "encoded RV64GC symbol constant has an invalid version or size"); + } + const auto KindValue = std::to_integer<std::uint8_t>(Payload.front()); + if (KindValue > static_cast<std::uint8_t>(RISCVSymbolConstantKind::ExternalSymbol)) + return makeUnexpected(ErrorCode::ParseError, "encoded RV64GC symbol constant kind is invalid"); + std::string Symbol; + Symbol.reserve(Payload.size() - 9U); + for (const std::byte Character : Payload.subspan(9)) + Symbol.push_back(static_cast<char>(std::to_integer<unsigned char>(Character))); + RISCVSymbolConstant Result(static_cast<RISCVSymbolConstantKind>(KindValue), std::move(Symbol), + std::bit_cast<std::int64_t>(readU64(Payload.subspan(1, 8)))); + auto Verification = Result.verify(); + if (!Verification) + return std::unexpected(Verification.error()); + return Result; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVTargetMachine.cpp b/backend/lib/Target/RISCV/RISCVTargetMachine.cpp new file mode 100644 index 0000000..6f82ce8 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVTargetMachine.cpp @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/MC/TargetRegistry.h" +#include "rcc/Target/RISCV/RISCVAssemblyPrinter.h" +#include "rcc/Target/RISCV/RISCVObjectFile.h" +#include "rcc/Target/RISCV/RISCVPipeline.h" +#include "rcc/Target/RISCV/RISCVTarget.h" +#include "rcc/Target/TargetMachine.h" + +#include <algorithm> +#include <array> +#include <memory> +#include <ranges> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace rcc { +namespace { + +constexpr std::string_view RISCV64Triple = "riscv64-unknown-linux-gnu"; +constexpr std::string_view RISCV64CPU = "generic-rv64gc"; +constexpr std::string_view RISCV64DataLayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"; +constexpr std::string_view RISCV64ABILoweringVersion = "rcc-rv64gc-lp64d-v1"; +constexpr std::array<std::string_view, 1> RISCV64BaselineFeatures{"+i"}; + +[[nodiscard]] unsigned matchRISCV64Triple(std::string_view Triple) noexcept { + if (Triple == "riscv64" || Triple == "riscv64-linux-gnu" || Triple == RISCV64Triple) + return 20; + return Triple.starts_with("riscv64-") ? 10U : 0U; +} + +[[nodiscard]] std::string normalizeRISCV64Triple(std::string_view Triple) { + if (Triple == "riscv64" || Triple == "riscv64-linux-gnu") + return std::string(RISCV64Triple); + return std::string(Triple); +} + +[[nodiscard]] std::string defaultRISCV64ABI(std::span<const std::string> Features) { + return std::ranges::find(Features, "+d") == Features.end() ? "lp64" : "lp64d"; +} + +[[nodiscard]] TargetMachineOptions defaultRISCV64Options() { + return TargetMachineOptions{ + .Triple = std::string(RISCV64Triple), + .CPU = std::string(RISCV64CPU), + .TuneCPU = std::string(RISCV64CPU), + .Features = {"+i", "+m", "+a", "+f", "+d", "+c", "+zicsr", "+zifencei"}, + .ABI = "lp64d", + .DataLayout = std::string(RISCV64DataLayout), + .Relocation = RelocationModel::Static, + .Code = CodeModel::Small, + .Optimization = OptimizationLevel::O2, + .Options = {{"function-sections", "false"}, + {"omit-frame-pointer", "true"}, + {"stack-realignment", "false"}}, + .ABILoweringVersion = std::string(RISCV64ABILoweringVersion), + .AssemblerFlavor = "rcc-integrated-gnu", + .AssemblerVersion = "rcc-mc-v1", + .AssemblerOptions = {{"relax", "true"}}, + }; +} + +[[nodiscard]] bool booleanOption(std::span<const TargetOption> Options, + std::string_view Name) noexcept { + const auto Position = std::ranges::find(Options, Name, &TargetOption::Name); + return Position != Options.end() && Position->Value == "true"; +} + +class RISCVTargetMachine final : public TargetMachine { +public: + RISCVTargetMachine(const Target &RegisteredTarget, TargetMachineOptions Options, + TargetDescription Description, riscv::RISCVSubtargetFeatures Features, + RelocationModel Relocation, CodeModel Code, riscv::RISCVABI ABI) + : TargetMachine(RegisteredTarget, std::move(Options), std::move(Description)), + Subtarget(Features, Relocation, Code, ABI) {} + + [[nodiscard]] Expected<void> + addCodeGenPasses(MachineFunctionPassManager &Pipeline, const MachineFunction &Function, + TargetPipelineOptions PipelineOptions) const override { + return riscv::addCanonicalPipeline(Pipeline, Function, Subtarget, PipelineOptions); + } + + [[nodiscard]] Expected<std::unique_ptr<MachineFunctionPass>> + createMachinePass(std::string_view Name, TargetPipelineOptions PipelineOptions) const override { + return riscv::createCanonicalPipelinePass(Name, Subtarget, PipelineOptions); + } + + [[nodiscard]] Expected<std::vector<std::string>> + codeGenPassNames(const MachineFunction &Function) const override { + return riscv::canonicalPipelinePassNames(Function, Subtarget); + } + + [[nodiscard]] Expected<std::unique_ptr<TargetMachineOutlinerInfo>> + createOutlinerInfo() const override { + return riscv::createMachineOutlinerInfo( + booleanOption(options().Options, "function-sections"), + booleanOption(options().Options, "riscv-outliner-regsave")); + } + + [[nodiscard]] Expected<std::string> emitAssembly(const CompilationUnit &Unit) const override { + return riscv::emitAssembly(Unit, targetDescription()); + } + + [[nodiscard]] Expected<std::vector<std::uint8_t>> + emitObject(const CompilationUnit &Unit) const override { + return riscv::emitObject(Unit, targetDescription()); + } + +private: + riscv::RISCVSubtarget Subtarget; +}; + +[[nodiscard]] Expected<std::unique_ptr<TargetMachine>> +createRISCVTargetMachine(const Target &RegisteredTarget, TargetMachineOptions Options) { + Options.Triple = RegisteredTarget.normalizeTriple(Options.Triple); + const TargetMachineOptions Defaults = defaultRISCV64Options(); + if (Options.Triple != Defaults.Triple || Options.CPU != Defaults.CPU || + Options.TuneCPU != Defaults.TuneCPU || Options.DataLayout != Defaults.DataLayout) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "backend invocation does not select a supported RV64 target"); + } + if (Options.ABILoweringVersion != RISCV64ABILoweringVersion) { + return makeUnexpected(ErrorCode::InvalidInvocation, + "RISC-V ABI lowering version does not match this build"); + } + auto Features = riscv::RISCVSubtargetFeatures::fromFeatureList(Options.Features); + if (!Features) + return std::unexpected(Features.error()); + auto ABI = riscv::parseRISCVABI(Options.ABI); + if (!ABI) + return std::unexpected(ABI.error()); + if (*ABI == riscv::RISCVABI::LP64D && !Features->has(riscv::RISCVExtension::D)) { + return makeUnexpected(ErrorCode::InvalidInvocation, "RISC-V LP64D requires the D extension"); + } + auto Description = riscv::createTargetDescription(); + if (!Description) + return std::unexpected(Description.error()); + const RelocationModel Relocation = Options.Relocation; + const CodeModel Code = Options.Code; + return std::make_unique<RISCVTargetMachine>(RegisteredTarget, std::move(Options), + std::move(*Description), *Features, Relocation, Code, + *ABI); +} + +const Target &getRISCV64Target() { + static const Target RegisteredTarget( + "riscv64", "64-bit RISC-V", true, matchRISCV64Triple, normalizeRISCV64Triple, + defaultRISCV64Options, defaultRISCV64ABI, RISCV64BaselineFeatures, createRISCVTargetMachine); + return RegisteredTarget; +} + +} // namespace + +void initializeRISCVTarget() { + TargetRegistry::registerTarget(getRISCV64Target()); +} + +} // namespace rcc diff --git a/backend/lib/Target/RISCV/RISCVTargetObjectFile.cpp b/backend/lib/Target/RISCV/RISCVTargetObjectFile.cpp new file mode 100644 index 0000000..9a8aebb --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVTargetObjectFile.cpp @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "RISCVTargetObjectFile.h" + +#include "rcc/CodeGen/GlobalObject.h" +#include "rcc/CodeGen/MachineConstantPool.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineJumpTableInfo.h" +#include "rcc/CodeGen/MachineMCContext.h" +#include "rcc/CodeGen/TargetConstant.h" +#include "rcc/MC/MCContext.h" +#include "rcc/MC/MCSymbol.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <ranges> +#include <string> + +namespace rcc::riscv { +namespace { + +[[nodiscard]] bool initializerIsZero(const GlobalInitializer &Initializer, std::size_t Depth = 0) { + if (Depth > 1024) + return false; + switch (Initializer.kind()) { + case GlobalInitializer::Kind::Integer: { + const auto *Value = Initializer.getIf<APInt>(); + return Value != nullptr && + std::ranges::all_of(Value->words(), [](std::uint64_t Word) { return Word == 0; }); + } + case GlobalInitializer::Kind::Floating: { + const auto *Value = Initializer.getIf<APFloat>(); + return Value != nullptr && + std::ranges::all_of(Value->words(), [](std::uint64_t Word) { return Word == 0; }); + } + case GlobalInitializer::Kind::Bytes: { + const auto *Value = Initializer.getIf<std::vector<std::byte>>(); + return Value != nullptr && + std::ranges::all_of(*Value, [](std::byte Byte) { return Byte == std::byte{0}; }); + } + case GlobalInitializer::Kind::ZeroFill: + case GlobalInitializer::Kind::UndefinedStorage: + return true; + case GlobalInitializer::Kind::Repeated: { + const auto *Value = Initializer.getIf<GlobalRepeatedInitializer>(); + return Value != nullptr && Value->Value && initializerIsZero(*Value->Value, Depth + 1); + } + case GlobalInitializer::Kind::Aggregate: { + const auto *Value = Initializer.getIf<GlobalAggregateInitializer>(); + return Value != nullptr && std::ranges::all_of(Value->Elements, [Depth](const auto &Element) { + return Element && initializerIsZero(*Element, Depth + 1); + }); + } + case GlobalInitializer::Kind::AlignmentPadding: { + const auto *Value = Initializer.getIf<GlobalAlignmentPadding>(); + return Value != nullptr && Value->Fill == 0; + } + case GlobalInitializer::Kind::Target: { + const auto *Value = Initializer.getIf<TargetConstant>(); + if (Value == nullptr) + return false; + auto Emission = Value->emit(); + return Emission && Emission->Fixups.empty() && + std::ranges::all_of(Emission->Bytes, + [](std::byte Byte) { return Byte == std::byte{0}; }); + } + case GlobalInitializer::Kind::Invalid: + case GlobalInitializer::Kind::SymbolAddress: + case GlobalInitializer::Kind::SymbolDifference: + return false; + } + return false; +} + +[[nodiscard]] bool initializerHasRelocation(const GlobalInitializer &Initializer, + std::size_t Depth = 0) { + if (Depth > 1024) + return true; + switch (Initializer.kind()) { + case GlobalInitializer::Kind::SymbolAddress: + case GlobalInitializer::Kind::SymbolDifference: + return true; + case GlobalInitializer::Kind::Repeated: { + const auto *Value = Initializer.getIf<GlobalRepeatedInitializer>(); + return Value == nullptr || !Value->Value || initializerHasRelocation(*Value->Value, Depth + 1); + } + case GlobalInitializer::Kind::Aggregate: { + const auto *Value = Initializer.getIf<GlobalAggregateInitializer>(); + return Value == nullptr || std::ranges::any_of(Value->Elements, [Depth](const auto &Element) { + return !Element || initializerHasRelocation(*Element, Depth + 1); + }); + } + case GlobalInitializer::Kind::Target: { + const auto *Value = Initializer.getIf<TargetConstant>(); + if (Value == nullptr) + return true; + auto Emission = Value->emit(); + return !Emission || !Emission->Fixups.empty(); + } + case GlobalInitializer::Kind::Invalid: + return true; + case GlobalInitializer::Kind::Integer: + case GlobalInitializer::Kind::Floating: + case GlobalInitializer::Kind::Bytes: + case GlobalInitializer::Kind::ZeroFill: + case GlobalInitializer::Kind::AlignmentPadding: + case GlobalInitializer::Kind::UndefinedStorage: + return false; + } + return true; +} + +[[nodiscard]] mc::MCSectionDesc textSectionDescription() { + return mc::MCSectionDesc{ + .Name = ".text", + .Kind = mc::MCSectionKind::Text, + .Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Executable}, + .Alignment = 4, + .EntrySize = 0, + .Group = {}, + .UniqueID = std::nullopt, + }; +} + +[[nodiscard]] mc::MCSectionDesc readOnlySectionDescription() { + return mc::MCSectionDesc{ + .Name = ".rodata", + .Kind = mc::MCSectionKind::ReadOnly, + .Flags = {mc::MCSectionFlag::Alloc}, + .Alignment = 1, + .EntrySize = 0, + .Group = {}, + .UniqueID = std::nullopt, + }; +} + +} // namespace + +Expected<mc::MCSectionDesc> RISCVELFTargetObjectFile::textSection(const MachineFunction &) const { + return textSectionDescription(); +} + +Expected<mc::MCSectionDesc> +RISCVELFTargetObjectFile::globalSection(const GlobalObjectDesc &Object, + MachineMCContext &Context) const { + if (Object.AddressSpace != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC assembly does not support nonzero global address spaces"); + } + if (Object.TargetFlags != 0 || !Object.TargetAttributes.empty()) { + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC assembly does not recognize this global target state"); + } + const bool IsTLS = Object.TLSModel != GlobalTLSModel::None; + const bool IsZero = Object.Initializer && initializerIsZero(*Object.Initializer); + const bool HasRelocation = Object.Initializer && initializerHasRelocation(*Object.Initializer); + + mc::MCSectionDesc Result; + Result.Alignment = Object.Align.bytes(); + if (IsTLS) { + Result.Name = IsZero ? ".tbss" : ".tdata"; + Result.Kind = IsZero ? mc::MCSectionKind::TLSBSS : mc::MCSectionKind::TLSData; + Result.Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Writable, mc::MCSectionFlag::TLS}; + } else if (Object.IsConstant && HasRelocation) { + Result.Name = ".data.rel.ro"; + Result.Kind = mc::MCSectionKind::Data; + Result.Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Writable}; + } else if (Object.IsConstant) { + Result = readOnlySectionDescription(); + Result.Alignment = Object.Align.bytes(); + } else if (IsZero) { + Result.Name = ".bss"; + Result.Kind = mc::MCSectionKind::BSS; + Result.Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Writable}; + } else { + Result.Name = ".data"; + Result.Kind = mc::MCSectionKind::Data; + Result.Flags = {mc::MCSectionFlag::Alloc, mc::MCSectionFlag::Writable}; + } + + if (Object.Section) + Result.Name = *Object.Section; + if (Object.COMDAT) { + auto Group = Context.symbol(*Object.COMDAT); + if (!Group) + return std::unexpected(Group.error()); + Result.Flags.set(mc::MCSectionFlag::Group); + Result.Group = std::move(*Group); + if (!Object.Section) + Result.Name += "." + Object.Symbol; + } + return Result; +} + +Expected<mc::MCSectionDesc> +RISCVELFTargetObjectFile::constantPoolSection(const MachineFunction &Function, + const MachineConstantPoolEntry &Entry) const { + const MachineConstantPoolEntryDesc *Description = Entry.description(); + if (Description == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, "constant-pool entry is not live"); + if (Description->Placement.Section == ConstantPoolSectionKind::TextAdjacent) + return textSectionDescription(); + + mc::MCSectionDesc Result = readOnlySectionDescription(); + if (Description->Placement.Section == ConstantPoolSectionKind::Data) { + Result.Name = ".data"; + Result.Kind = mc::MCSectionKind::Data; + Result.Flags.set(mc::MCSectionFlag::Writable); + } else if (Description->Placement.Section == ConstantPoolSectionKind::Target) { + return makeUnexpected(ErrorCode::Unsupported, + "RV64GC constant pool has an unknown target section"); + } else if (Description->Placement.Section == ConstantPoolSectionKind::MergeableReadOnly) { + Result.Name = ".rodata.cst" + std::to_string(Description->Size); + Result.Flags.set(mc::MCSectionFlag::Merge); + Result.EntrySize = Description->Size; + } + if (Description->Placement.UniqueSection) { + Result.Name += "." + std::to_string(Function.stableID().value()) + "." + + std::to_string(Entry.index().value()); + Result.UniqueID = static_cast<std::uint64_t>(Entry.index().value()) + 1U; + } + Result.Alignment = Description->Align.bytes(); + return Result; +} + +Expected<mc::MCSectionDesc> +RISCVELFTargetObjectFile::jumpTableSection(const MachineFunction &Function, + const MachineJumpTableEntry &Entry) const { + const MachineJumpTableDesc *Description = Entry.description(); + if (Description == nullptr) + return makeUnexpected(ErrorCode::InvalidArgument, "jump-table entry is not live"); + mc::MCSectionDesc Result = Description->Placement.InTextSection ? textSectionDescription() + : readOnlySectionDescription(); + if (Description->Placement.UniqueSection) { + Result.Name += "." + std::to_string(Function.stableID().value()) + ".jt." + + std::to_string(Entry.index().value()); + Result.UniqueID = static_cast<std::uint64_t>(Entry.index().value()) + 1U; + } + Result.Alignment = Description->Align.bytes(); + return Result; +} + +} // namespace rcc::riscv diff --git a/backend/lib/Target/RISCV/RISCVTargetObjectFile.h b/backend/lib/Target/RISCV/RISCVTargetObjectFile.h new file mode 100644 index 0000000..cf8db36 --- /dev/null +++ b/backend/lib/Target/RISCV/RISCVTargetObjectFile.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef RCC_TARGET_RISCV_RISCVTARGETOBJECTFILE_H +#define RCC_TARGET_RISCV_RISCVTARGETOBJECTFILE_H + +#include "rcc/CodeGen/TargetLoweringObjectFile.h" + +namespace rcc::riscv { + +class RISCVELFTargetObjectFile final : public TargetLoweringObjectFile { +public: + [[nodiscard]] Expected<mc::MCSectionDesc> + textSection(const MachineFunction &Function) const override; + [[nodiscard]] Expected<mc::MCSectionDesc> globalSection(const GlobalObjectDesc &Object, + MachineMCContext &Context) const override; + [[nodiscard]] Expected<mc::MCSectionDesc> + constantPoolSection(const MachineFunction &Function, + const MachineConstantPoolEntry &Entry) const override; + [[nodiscard]] Expected<mc::MCSectionDesc> + jumpTableSection(const MachineFunction &Function, + const MachineJumpTableEntry &Entry) const override; +}; + +} // namespace rcc::riscv + +#endif // RCC_TARGET_RISCV_RISCVTARGETOBJECTFILE_H diff --git a/backend/lib/Target/TargetMachine.cpp b/backend/lib/Target/TargetMachine.cpp new file mode 100644 index 0000000..bc6b704 --- /dev/null +++ b/backend/lib/Target/TargetMachine.cpp @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/Target/TargetMachine.h" + +#include "rcc/MC/TargetRegistry.h" + +#include <utility> + +namespace rcc { + +TargetMachine::TargetMachine(const Target &RegisteredTarget, TargetMachineOptions Options, + TargetDescription Description) + : RegisteredTarget(RegisteredTarget), Options(std::move(Options)), + Description(std::move(Description)) {} + +TargetMachine::~TargetMachine() = default; + +} // namespace rcc |
