aboutsummaryrefslogtreecommitdiff
path: root/backend/lib/Target
diff options
context:
space:
mode:
Diffstat (limited to 'backend/lib/Target')
-rw-r--r--backend/lib/Target/CMakeLists.txt38
-rw-r--r--backend/lib/Target/RISCV/CMakeLists.txt258
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVCallLowering.cpp1102
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVCallLowering.h49
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVGISelCombiner.cpp56
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVInstructionSelector.cpp1343
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.cpp1382
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVLegalizerInfo.h37
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.cpp388
-rw-r--r--backend/lib/Target/RISCV/GISel/RISCVRegisterBankInfo.h30
-rw-r--r--backend/lib/Target/RISCV/RISCV.h59
-rw-r--r--backend/lib/Target/RISCV/RISCV.rccdesc11
-rw-r--r--backend/lib/Target/RISCV/RISCVAsmBackend.cpp405
-rw-r--r--backend/lib/Target/RISCV/RISCVAsmPrinter.cpp190
-rw-r--r--backend/lib/Target/RISCV/RISCVAsmPrinter.h28
-rw-r--r--backend/lib/Target/RISCV/RISCVAssemblyPrinter.cpp71
-rw-r--r--backend/lib/Target/RISCV/RISCVCallingConv.cpp507
-rw-r--r--backend/lib/Target/RISCV/RISCVCallingConv.h52
-rw-r--r--backend/lib/Target/RISCV/RISCVCallingConv.rccdesc33
-rw-r--r--backend/lib/Target/RISCV/RISCVFinalizeISel.cpp496
-rw-r--r--backend/lib/Target/RISCV/RISCVFrameLowering.cpp2739
-rw-r--r--backend/lib/Target/RISCV/RISCVFunctionInfo.cpp131
-rw-r--r--backend/lib/Target/RISCV/RISCVGISelAddressing.rccdesc282
-rw-r--r--backend/lib/Target/RISCV/RISCVGISelAtomic.rccdesc363
-rw-r--r--backend/lib/Target/RISCV/RISCVGISelCombiner.rccdesc173
-rw-r--r--backend/lib/Target/RISCV/RISCVGISelControlFlow.rccdesc280
-rw-r--r--backend/lib/Target/RISCV/RISCVGISelFloating.rccdesc853
-rw-r--r--backend/lib/Target/RISCV/RISCVGISelInteger.rccdesc1396
-rw-r--r--backend/lib/Target/RISCV/RISCVGISelShift.rccdesc198
-rw-r--r--backend/lib/Target/RISCV/RISCVImmediateMaterialization.cpp205
-rw-r--r--backend/lib/Target/RISCV/RISCVImmediateMaterialization.h43
-rw-r--r--backend/lib/Target/RISCV/RISCVInstPrinter.cpp340
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfo.cpp1441
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfo.h25
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoA.rccdesc329
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoBase.rccdesc107
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoC.rccdesc515
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoD.rccdesc509
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoF.rccdesc477
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoI.rccdesc887
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoM.rccdesc196
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoPseudo.rccdesc461
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoZb.rccdesc237
-rw-r--r--backend/lib/Target/RISCV/RISCVInstrInfoZcmp.rccdesc101
-rw-r--r--backend/lib/Target/RISCV/RISCVMCCodeEmitter.cpp1075
-rw-r--r--backend/lib/Target/RISCV/RISCVMCPseudoLowering.rccdesc35
-rw-r--r--backend/lib/Target/RISCV/RISCVMCTargetLowering.cpp334
-rw-r--r--backend/lib/Target/RISCV/RISCVMachineCombiner.cpp57
-rw-r--r--backend/lib/Target/RISCV/RISCVMachineMemoryFolding.cpp48
-rw-r--r--backend/lib/Target/RISCV/RISCVMacroFusion.rccdesc139
-rw-r--r--backend/lib/Target/RISCV/RISCVMakeCompressible.cpp422
-rw-r--r--backend/lib/Target/RISCV/RISCVMergeBaseOffset.cpp495
-rw-r--r--backend/lib/Target/RISCV/RISCVMoveMerger.cpp309
-rw-r--r--backend/lib/Target/RISCV/RISCVObjectFile.cpp150
-rw-r--r--backend/lib/Target/RISCV/RISCVOptWInstrs.cpp621
-rw-r--r--backend/lib/Target/RISCV/RISCVOptimizationPipeline.cpp23
-rw-r--r--backend/lib/Target/RISCV/RISCVOutliner.cpp344
-rw-r--r--backend/lib/Target/RISCV/RISCVPipeline.cpp248
-rw-r--r--backend/lib/Target/RISCV/RISCVPseudoExpansion.cpp605
-rw-r--r--backend/lib/Target/RISCV/RISCVPushPopOptimizer.cpp222
-rw-r--r--backend/lib/Target/RISCV/RISCVRegisterAllocation.cpp341
-rw-r--r--backend/lib/Target/RISCV/RISCVRegisterInfo.rccdesc413
-rw-r--r--backend/lib/Target/RISCV/RISCVScheduling.cpp165
-rw-r--r--backend/lib/Target/RISCV/RISCVSubtarget.cpp239
-rw-r--r--backend/lib/Target/RISCV/RISCVTarget.cpp318
-rw-r--r--backend/lib/Target/RISCV/RISCVTargetConstant.cpp169
-rw-r--r--backend/lib/Target/RISCV/RISCVTargetMachine.cpp161
-rw-r--r--backend/lib/Target/RISCV/RISCVTargetObjectFile.cpp244
-rw-r--r--backend/lib/Target/RISCV/RISCVTargetObjectFile.h26
-rw-r--r--backend/lib/Target/TargetMachine.cpp18
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 &registerAllocationInfo() 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 &registerAllocationInfo() 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