aboutsummaryrefslogtreecommitdiff
path: root/backend/lib/Target
diff options
context:
space:
mode:
authorRichard-Qin-X <richard.qin.001@gmail.com>2026-08-11 17:38:11 +0800
committerRichard-Qin-X <richard.qin.001@gmail.com>2026-08-11 17:38:11 +0800
commit4fe5ee649927d54d6fb74904d7b97ad0f63fd587 (patch)
tree75f85f5e6cae3a871fe42d025dca04eeb7c31cfd /backend/lib/Target
parent6a7b083f322f4ac47e58cbf9d4d5fd4d1f6fecd0 (diff)
downloadrcc-project-4fe5ee649927d54d6fb74904d7b97ad0f63fd587.tar.gz
rcc-project-4fe5ee649927d54d6fb74904d7b97ad0f63fd587.tar.xz
[RISCV] Legalize fixed-baseline RV64 MachineIR
Diffstat (limited to 'backend/lib/Target')
-rw-r--r--backend/lib/Target/RISCV/CMakeLists.txt8
-rw-r--r--backend/lib/Target/RISCV/RISCV.rccdesc27
-rw-r--r--backend/lib/Target/RISCV/RISCVBranchInfo.cpp15
-rw-r--r--backend/lib/Target/RISCV/RISCVCodeGen.cpp2455
-rw-r--r--backend/lib/Target/RISCV/RISCVFunctionInfo.cpp137
-rw-r--r--backend/lib/Target/RISCV/RISCVImmediateMaterialization.cpp148
-rw-r--r--backend/lib/Target/RISCV/RISCVImmediateMaterialization.h22
-rw-r--r--backend/lib/Target/RISCV/RISCVPseudoExpansion.cpp40
-rw-r--r--backend/lib/Target/RISCV/RISCVTarget.cpp4
9 files changed, 2824 insertions, 32 deletions
diff --git a/backend/lib/Target/RISCV/CMakeLists.txt b/backend/lib/Target/RISCV/CMakeLists.txt
index 156dfd6..eae426c 100644
--- a/backend/lib/Target/RISCV/CMakeLists.txt
+++ b/backend/lib/Target/RISCV/CMakeLists.txt
@@ -52,6 +52,8 @@ add_library(
RISCVCallLowering.cpp
RISCVAssemblyPrinter.cpp
RISCVFrameLowering.cpp
+ RISCVFunctionInfo.cpp
+ RISCVImmediateMaterialization.cpp
RISCVInstPrinter.cpp
RISCVAsmBackend.cpp
RISCVMCCodeEmitter.cpp
@@ -118,6 +120,9 @@ rcc_backend_register_format_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCodeGen.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLowering.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"
@@ -138,6 +143,7 @@ rcc_backend_register_format_files(
"${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"
@@ -152,6 +158,8 @@ rcc_backend_register_tidy_files(
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCodeGen.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVCallLowering.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVFrameLowering.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVFunctionInfo.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/RISCVImmediateMaterialization.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVInstPrinter.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVAsmBackend.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/RISCVMCCodeEmitter.cpp"
diff --git a/backend/lib/Target/RISCV/RISCV.rccdesc b/backend/lib/Target/RISCV/RISCV.rccdesc
index 237e294..f8e22d4 100644
--- a/backend/lib/Target/RISCV/RISCV.rccdesc
+++ b/backend/lib/Target/RISCV/RISCV.rccdesc
@@ -143,8 +143,8 @@ registers:
- { id: 64, symbol: F31, name: f31, encoding: 31, dwarf_register: 63, units: [{ unit: U_F31, lanes: 1 }], aliases: [ft11] }
register_banks:
- - { id: 1, symbol: GPR, name: GPR, maximum_size_bits: 64 }
- - { id: 2, symbol: FPR, name: FPR, maximum_size_bits: 64 }
+ - { id: 1, symbol: GPR, name: GPRB, maximum_size_bits: 64 }
+ - { id: 2, symbol: FPR, name: FPRB, maximum_size_bits: 64 }
register_classes:
-
@@ -3277,6 +3277,18 @@ instructions:
assembly_format: RD_RS2_PAREN_RS1
requires_memory_operands: true
-
+ id: 1229
+ symbol: G_SLLW
+ name: G_SLLW
+ operands:
+ - { kinds: [register], role: def }
+ - { kinds: [register], role: use }
+ - { kinds: [register], role: use }
+ num_explicit_defs: 1
+ flags: [generic, pseudo, allowed_pre_legalizer]
+ owner: instruction_select
+ deadline: selected
+ -
id: 1216
symbol: PseudoCC_COPY
name: PseudoCC_COPY
@@ -3386,7 +3398,6 @@ instructions:
name: PseudoRET
operands: []
num_explicit_defs: 0
- implicit_uses: [{ register: X1 }]
allow_extra_implicit_operands: true
flags: [pseudo, produces_code, allowed_pre_legalizer, terminator, return, barrier]
owner: post_ra_expansion
@@ -3443,3 +3454,13 @@ instructions:
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]
+ owner: post_ra_expansion
+ deadline: post_ra_finalized
diff --git a/backend/lib/Target/RISCV/RISCVBranchInfo.cpp b/backend/lib/Target/RISCV/RISCVBranchInfo.cpp
index 18812d8..a00ed67 100644
--- a/backend/lib/Target/RISCV/RISCVBranchInfo.cpp
+++ b/backend/lib/Target/RISCV/RISCVBranchInfo.cpp
@@ -75,15 +75,19 @@ constexpr std::uint32_t PCRelativeLowFlag = 4;
}
[[nodiscard]] bool isUnconditionalBranch(const MachineInstr &Instruction) noexcept {
- if (Instruction.opcode() == targetOpcode(InstructionOpcode::C_J))
+ if (Instruction.opcode() == targetOpcode(InstructionOpcode::C_J) ||
+ Instruction.opcode() == targetOpcode(InstructionOpcode::PseudoBR)) {
return true;
+ }
return Instruction.opcode() == targetOpcode(InstructionOpcode::JAL) &&
isZeroRegister(Instruction.operandAt(0));
}
[[nodiscard]] std::size_t branchTargetIndex(Opcode Value) noexcept {
- if (Value == targetOpcode(InstructionOpcode::C_J))
+ if (Value == targetOpcode(InstructionOpcode::C_J) ||
+ Value == targetOpcode(InstructionOpcode::PseudoBR)) {
return 0;
+ }
if (Value == targetOpcode(InstructionOpcode::C_BEQZ) ||
Value == targetOpcode(InstructionOpcode::C_BNEZ)) {
return 1;
@@ -96,7 +100,8 @@ constexpr std::uint32_t PCRelativeLowFlag = 4;
[[nodiscard]] Expected<MachineBasicBlock *> directTarget(MachineInstr &Instruction) {
if (!isConditionalBranch(Instruction.opcode()) &&
Instruction.opcode() != targetOpcode(InstructionOpcode::JAL) &&
- Instruction.opcode() != targetOpcode(InstructionOpcode::C_J)) {
+ Instruction.opcode() != targetOpcode(InstructionOpcode::C_J) &&
+ Instruction.opcode() != targetOpcode(InstructionOpcode::PseudoBR)) {
return static_cast<MachineBasicBlock *>(nullptr);
}
MachineOperand *Operand = Instruction.operandAt(branchTargetIndex(Instruction.opcode()));
@@ -697,8 +702,10 @@ public:
return Offset >= -2048 && Offset <= 2046;
if (isConditionalBranch(Value))
return Offset >= -4096 && Offset <= 4094;
- if (Value == targetOpcode(InstructionOpcode::JAL))
+ if (Value == targetOpcode(InstructionOpcode::JAL) ||
+ Value == targetOpcode(InstructionOpcode::PseudoBR)) {
return Offset >= -1048576 && Offset <= 1048574;
+ }
return true;
}
diff --git a/backend/lib/Target/RISCV/RISCVCodeGen.cpp b/backend/lib/Target/RISCV/RISCVCodeGen.cpp
index 642982a..bfbf9a6 100644
--- a/backend/lib/Target/RISCV/RISCVCodeGen.cpp
+++ b/backend/lib/Target/RISCV/RISCVCodeGen.cpp
@@ -2,6 +2,8 @@
#include "rcc/Target/RISCV/RISCVCodeGen.h"
+#include "RISCVImmediateMaterialization.h"
+
#include "rcc/CodeGen/GenericOpcodes.h"
#include "rcc/CodeGen/LowLevelType.h"
#include "rcc/CodeGen/MachineBasicBlock.h"
@@ -64,39 +66,655 @@ namespace {
}
[[nodiscard]] bool isSupportedIntegerType(LLT Type) noexcept {
- return (Type.isInteger() && Type.scalarBitWidth() != 0 && Type.scalarBitWidth() <= 64) ||
+ return ((Type.isInteger() || Type.isAnyScalar()) && Type.scalarBitWidth() != 0 &&
+ Type.scalarBitWidth() <= 64) ||
(Type.isPointer() && Type.scalarBitWidth() == 64);
}
[[nodiscard]] bool isSupportedIntegerScalar(LLT Type) noexcept {
- return Type.isInteger() && Type.scalarBitWidth() != 0 && Type.scalarBitWidth() <= 64;
+ return (Type.isInteger() || Type.isAnyScalar()) && Type.scalarBitWidth() != 0 &&
+ Type.scalarBitWidth() <= 64;
}
[[nodiscard]] bool isNativeIntegerScalar(LLT Type) noexcept {
- return Type.isInteger() && (Type.scalarBitWidth() == 32 || Type.scalarBitWidth() == 64);
+ return (Type.isInteger() || Type.isAnyScalar()) &&
+ (Type.scalarBitWidth() == 32 || Type.scalarBitWidth() == 64);
}
[[nodiscard]] bool isScalarGPRType(LLT Type) noexcept {
- return (Type.isInteger() && Type.scalarBitWidth() != 0 && Type.scalarBitWidth() <= 64) ||
+ return ((Type.isInteger() || Type.isAnyScalar()) && Type.scalarBitWidth() != 0 &&
+ Type.scalarBitWidth() <= 64) ||
(Type.isPointer() && Type.scalarBitWidth() == 64);
}
[[nodiscard]] bool isI1Type(LLT Type) noexcept {
- return Type.isInteger() && Type.scalarBitWidth() == 1;
+ return (Type.isInteger() || Type.isAnyScalar()) && Type.scalarBitWidth() == 1;
}
[[nodiscard]] bool isScalarIntegerExtension(LLT Destination, LLT Source) noexcept {
- return Destination.isInteger() && Source.isInteger() && Destination.scalarBitWidth() != 0 &&
+ return (Destination.isInteger() || Destination.isAnyScalar()) &&
+ (Source.isInteger() || Source.isAnyScalar()) && Destination.scalarBitWidth() != 0 &&
Source.scalarBitWidth() != 0 && Source.scalarBitWidth() < Destination.scalarBitWidth() &&
Destination.scalarBitWidth() <= 64;
}
+[[nodiscard]] Expected<IntegerImmediate>
+extendIntegerImmediate(const IntegerImmediate &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 IntegerImmediate::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 MachineOperand *SourceDefinition = Function.registerInfo().getUniqueVRegDef(*Source);
+ 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 DestinationInfo = Function.registerInfo().virtualRegisterInfo(*Destination);
+ if (!DestinationInfo || !DestinationInfo->get().Type)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V constant extension destination has no type");
+ const std::uint32_t DestinationWidth = DestinationInfo->get().Type->scalarBitWidth();
+ const bool SignExtend = Generic != GenericOpcode::G_ZEXT;
+ auto Extended = extendIntegerImmediate(Integer->Value, DestinationWidth, SignExtend);
+ if (!Extended)
+ return std::unexpected(Extended.error());
+
+ 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(*Source)) {
+ MachineInstr *Def = const_cast<MachineInstr *>(SourceDef);
+ MachineBasicBlock *Parent = Def->parent();
+ if (Parent == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V constant extension source definition is detached");
+ auto Removed = Parent->removeInstruction(*Def);
+ 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 {};
+}
+
+struct WideConstantUnmergePlan final {
+ MachineInstr *Extension = nullptr;
+ MachineInstr *Unmerge = nullptr;
+ std::vector<Register> Outputs;
+ bool SignExtend = false;
+};
+
+[[nodiscard]] MachineInstr *singleRegisterUse(const MachineFunction &Function, Register Reg) {
+ MachineInstr *UseInstruction = nullptr;
+ auto &RegisterInfo = const_cast<MachineRegisterInfo &>(Function.registerInfo());
+ for (MachineOperand &Operand : RegisterInfo.useOperands(Reg)) {
+ if (Operand.parent() == nullptr)
+ continue;
+ if (UseInstruction != nullptr)
+ return nullptr;
+ UseInstruction = Operand.parent();
+ }
+ return UseInstruction;
+}
+
+[[nodiscard]] std::optional<WideConstantUnmergePlan>
+wideConstantUnmergePlan(const MachineInstr &Constant, const MachineFunction &Function) {
+ const MachineOperand *ResultOperand = Constant.operandAt(0);
+ const Register *Result = ResultOperand == nullptr ? nullptr : ResultOperand->getIf<Register>();
+ if (Result == nullptr || !Result->isVirtual())
+ return std::nullopt;
+
+ MachineInstr *FirstUse = singleRegisterUse(Function, *Result);
+ if (FirstUse == nullptr)
+ return std::nullopt;
+ MachineInstr *Extension = nullptr;
+ MachineInstr *Unmerge = FirstUse;
+ if (FirstUse->opcode() == opcode(GenericOpcode::G_ANYEXT) ||
+ FirstUse->opcode() == opcode(GenericOpcode::G_SEXT) ||
+ FirstUse->opcode() == opcode(GenericOpcode::G_ZEXT)) {
+ Extension = FirstUse;
+ const MachineOperand *ExtendedOperand = Extension->operandAt(0);
+ const Register *Extended =
+ ExtendedOperand == nullptr ? nullptr : ExtendedOperand->getIf<Register>();
+ if (Extended == nullptr || !Extended->isVirtual())
+ return std::nullopt;
+ Unmerge = singleRegisterUse(Function, *Extended);
+ if (Unmerge == nullptr)
+ return std::nullopt;
+ }
+ if (Unmerge->opcode() != opcode(GenericOpcode::G_UNMERGE_VALUES) ||
+ Unmerge->operandCount() < 3)
+ return std::nullopt;
+
+ WideConstantUnmergePlan Plan;
+ Plan.Extension = Extension;
+ Plan.Unmerge = Unmerge;
+ Plan.SignExtend = Extension != nullptr && Extension->opcode() != opcode(GenericOpcode::G_ZEXT);
+ for (std::size_t Index = 0; Index + 1U < Unmerge->operandCount(); ++Index) {
+ const MachineOperand *Operand = Unmerge->operandAt(Index);
+ const Register *Reg = Operand == nullptr ? nullptr : Operand->getIf<Register>();
+ if (Reg == nullptr || !Reg->isVirtual() || !Operand->isDef())
+ return std::nullopt;
+ const auto Info = Function.registerInfo().virtualRegisterInfo(*Reg);
+ if (!Info || !Info->get().Type || Info->get().Type->scalarBitWidth() == 0 ||
+ Info->get().Type->scalarBitWidth() > 64 ||
+ (!Info->get().Type->isInteger() && !Info->get().Type->isAnyScalar()))
+ return std::nullopt;
+ Plan.Outputs.push_back(*Reg);
+ }
+ const MachineOperand *SourceOperand = Unmerge->operandAt(Unmerge->operandCount() - 1U);
+ const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>();
+ const Register *ExpectedSource = Result;
+ if (Extension != nullptr) {
+ const MachineOperand *ExtendedOperand = Extension->operandAt(0);
+ ExpectedSource = ExtendedOperand == nullptr ? nullptr : ExtendedOperand->getIf<Register>();
+ }
+ if (Source == nullptr || ExpectedSource == nullptr || *Source != *ExpectedSource)
+ return std::nullopt;
+ return Plan;
+}
+
+[[nodiscard]] Expected<IntegerImmediate>
+wideConstantWord(const IntegerImmediate &Value, std::size_t WordIndex, std::uint32_t Width,
+ bool SignExtend) {
+ std::uint64_t Raw = 0;
+ if (WordIndex < Value.words().size())
+ Raw = Value.words()[WordIndex];
+ else if (SignExtend && Value.isNegative())
+ Raw = std::numeric_limits<std::uint64_t>::max();
+ const std::size_t WordBitOffset = WordIndex * 64U;
+ if (SignExtend && Value.isNegative() && WordBitOffset < Value.bitWidth()) {
+ const std::uint32_t Remaining =
+ Value.bitWidth() - static_cast<std::uint32_t>(WordBitOffset);
+ if (Remaining < 64)
+ Raw |= ~std::uint64_t{0} << Remaining;
+ }
+ if (Width < 64)
+ Raw &= (std::uint64_t{1} << Width) - 1U;
+ return IntegerImmediate::create(Width, std::span<const std::uint64_t>(&Raw, 1));
+}
+
+[[nodiscard]] Expected<void> foldWideConstant(MachineInstr &Instruction, MachineFunction &Function,
+ const TargetDescription &Target) {
+ const auto Plan = wideConstantUnmergePlan(Instruction, Function);
+ if (!Plan)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide constant requires a G_UNMERGE_VALUES use");
+ const MachineOperand *ImmediateOperand = Instruction.operandAt(1);
+ const auto *Integer = ImmediateOperand == nullptr
+ ? nullptr
+ : ImmediateOperand->getIf<IntegerImmediateOperand>();
+ if (Integer == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide constant has no integer immediate");
+
+ std::vector<IntegerImmediate> Words;
+ Words.reserve(Plan->Outputs.size());
+ for (std::size_t Index = 0; Index < Plan->Outputs.size(); ++Index) {
+ const auto Info = Function.registerInfo().virtualRegisterInfo(Plan->Outputs[Index]);
+ if (!Info || !Info->get().Type)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide constant output has no virtual-register type");
+ auto Word = wideConstantWord(Integer->Value, Index, Info->get().Type->scalarBitWidth(),
+ Plan->SignExtend);
+ if (!Word)
+ return std::unexpected(Word.error());
+ Words.push_back(std::move(*Word));
+ }
+
+ std::vector<std::size_t> Representatives(Plan->Outputs.size());
+ for (std::size_t Index = 0; Index < Representatives.size(); ++Index) {
+ Representatives[Index] = Index;
+ for (std::size_t Previous = 0; Previous < Index; ++Previous) {
+ if (Words[Previous] == Words[Index]) {
+ Representatives[Index] = Previous;
+ break;
+ }
+ }
+ }
+
+ const Register FirstOutput = Plan->Outputs.front();
+ auto Retargeted = Instruction.operandAt(0)->setRegister(FirstOutput);
+ if (!Retargeted)
+ return std::unexpected(Retargeted.error());
+ 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(Words.front())));
+ if (!Added)
+ return std::unexpected(Added.error());
+
+ MachineBasicBlock *Block = Instruction.parent();
+ if (Block == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide constant is detached during legalization");
+ if (Plan->Unmerge != &Instruction) {
+ auto RemovedUnmerge = Plan->Unmerge->parent()->removeInstruction(*Plan->Unmerge);
+ if (!RemovedUnmerge)
+ return std::unexpected(RemovedUnmerge.error());
+ }
+ if (Plan->Extension != nullptr) {
+ auto RemovedExtension = Plan->Extension->parent()->removeInstruction(*Plan->Extension);
+ if (!RemovedExtension)
+ return std::unexpected(RemovedExtension.error());
+ }
+
+ for (std::size_t Index = 1; Index < Plan->Outputs.size(); ++Index) {
+ if (Representatives[Index] == Index)
+ continue;
+ auto Replaced = Function.registerInfo().replaceRegWith(
+ Plan->Outputs[Index], Plan->Outputs[Representatives[Index]]);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ }
+
+ MachineInstr *Last = &Instruction;
+ for (std::size_t Index = 1; Index < Plan->Outputs.size(); ++Index) {
+ if (Representatives[Index] != Index)
+ continue;
+ MachineIRBuilder Builder(Function, Target);
+ auto Point = Builder.setInsertPointAfter(*Last);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(opcode(GenericOpcode::G_CONSTANT));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ auto Def = Pending->addDef(Plan->Outputs[Index]);
+ if (!Def)
+ return std::unexpected(Def.error());
+ auto Value = Pending->addIntegerImmediate(std::move(Words[Index]));
+ if (!Value)
+ return std::unexpected(Value.error());
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ Last = *Built;
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<Register>
+buildIntegerConstantBefore(MachineIRBuilder &Builder, MachineInstr &Anchor,
+ std::uint32_t Width, std::uint64_t Value);
+
+[[nodiscard]] Expected<Register>
+buildBinaryBefore(MachineIRBuilder &Builder, MachineInstr &Anchor,
+ GenericOpcode Generic, LLT Type, Register Left,
+ Register Right);
+
+struct NarrowAnyExtBinaryPlan final {
+ MachineInstr *Binary = nullptr;
+ GenericOpcode Generic = GenericOpcode::Invalid;
+ std::array<Register, 2> WideInputs{};
+ std::vector<MachineInstr *> Truncations;
+ std::vector<MachineInstr *> Constants;
+ std::uint32_t SourceWidth = 0;
+};
+
+[[nodiscard]] std::optional<NarrowAnyExtBinaryPlan>
+narrowAnyExtBinaryPlan(const MachineInstr &Extension, const MachineFunction &Function) {
+ const MachineOperand *DestinationOperand = Extension.operandAt(0);
+ const MachineOperand *SourceOperand = Extension.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 || !Destination->isVirtual() ||
+ !Source->isVirtual() || singleRegisterUse(Function, *Source) != &Extension)
+ return std::nullopt;
+ const auto DestinationInfo = Function.registerInfo().virtualRegisterInfo(*Destination);
+ const auto SourceInfo = Function.registerInfo().virtualRegisterInfo(*Source);
+ if (!DestinationInfo || !SourceInfo || !DestinationInfo->get().Type || !SourceInfo->get().Type)
+ return std::nullopt;
+ const LLT DestinationType = *DestinationInfo->get().Type;
+ const LLT SourceType = *SourceInfo->get().Type;
+ if (!isScalarIntegerExtension(DestinationType, SourceType) ||
+ DestinationType.scalarBitWidth() != 64)
+ return std::nullopt;
+
+ const MachineOperand *SourceDefinition = Function.registerInfo().getUniqueVRegDef(*Source);
+ MachineInstr *Binary =
+ SourceDefinition == nullptr ? nullptr : const_cast<MachineInstr *>(SourceDefinition->parent());
+ if (Binary == nullptr || Binary->operandCount() != 3)
+ return std::nullopt;
+ const GenericOpcode Generic = static_cast<GenericOpcode>(Binary->opcode().value());
+ if (Generic != GenericOpcode::G_ADD && Generic != GenericOpcode::G_SUB &&
+ Generic != GenericOpcode::G_AND && Generic != GenericOpcode::G_OR &&
+ Generic != GenericOpcode::G_XOR && Generic != GenericOpcode::G_SHL)
+ return std::nullopt;
+
+ NarrowAnyExtBinaryPlan Plan;
+ Plan.Binary = Binary;
+ Plan.Generic = Generic;
+ Plan.SourceWidth = SourceType.scalarBitWidth();
+ for (std::size_t Index = 0; Index != Plan.WideInputs.size(); ++Index) {
+ const MachineOperand *Operand = Binary->operandAt(Index + 1U);
+ const Register *Reg = Operand == nullptr ? nullptr : Operand->getIf<Register>();
+ if (Reg == nullptr || !Reg->isVirtual())
+ return std::nullopt;
+ const auto Info = Function.registerInfo().virtualRegisterInfo(*Reg);
+ if (!Info || !Info->get().Type)
+ return std::nullopt;
+ if (*Info->get().Type == DestinationType) {
+ Plan.WideInputs[Index] = *Reg;
+ continue;
+ }
+ if (*Info->get().Type != SourceType)
+ return std::nullopt;
+
+ const MachineOperand *Definition = Function.registerInfo().getUniqueVRegDef(*Reg);
+ MachineInstr *Def = Definition == nullptr ? nullptr : const_cast<MachineInstr *>(Definition->parent());
+ if (Def != nullptr && Def->opcode() == opcode(GenericOpcode::G_TRUNC) &&
+ Def->operandCount() == 2) {
+ const MachineOperand *WideOperand = Def->operandAt(1);
+ const Register *Wide = WideOperand == nullptr ? nullptr : WideOperand->getIf<Register>();
+ if (Wide == nullptr || !Wide->isVirtual())
+ return std::nullopt;
+ const auto WideInfo = Function.registerInfo().virtualRegisterInfo(*Wide);
+ if (!WideInfo || !WideInfo->get().Type || *WideInfo->get().Type != DestinationType)
+ return std::nullopt;
+ Plan.WideInputs[Index] = *Wide;
+ Plan.Truncations.push_back(Def);
+ continue;
+ }
+ if (Def != nullptr && Def->opcode() == opcode(GenericOpcode::G_CONSTANT) &&
+ singleRegisterUse(Function, *Reg) == Binary) {
+ Plan.WideInputs[Index] = *Reg;
+ Plan.Constants.push_back(Def);
+ continue;
+ }
+ return std::nullopt;
+ }
+ return Plan;
+}
+
+[[nodiscard]] Expected<void> widenConstantToRV64(MachineInstr &Instruction,
+ MachineFunction &Function) {
+ 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 narrow constant legalization has invalid operands");
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ auto SetType = Function.registerInfo().setType(*Destination, *Type);
+ if (!SetType)
+ return std::unexpected(SetType.error());
+ auto Extended = extendIntegerImmediate(Immediate->Value, 64, true);
+ if (!Extended)
+ return std::unexpected(Extended.error());
+ 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());
+ return {};
+}
+
+[[nodiscard]] Expected<void> foldNarrowAnyExtBinary(MachineInstr &Extension,
+ MachineFunction &Function,
+ const TargetDescription &Target) {
+ const auto Plan = narrowAnyExtBinaryPlan(Extension, Function);
+ if (!Plan)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V narrow any-extension does not have a legal binary chain");
+ for (MachineInstr *Constant : Plan->Constants) {
+ auto Widened = widenConstantToRV64(*Constant, Function);
+ if (!Widened)
+ return std::unexpected(Widened.error());
+ }
+ for (std::size_t Index = 0; Index != Plan->WideInputs.size(); ++Index) {
+ auto Replaced = Plan->Binary->operandAt(Index + 1U)->setRegister(Plan->WideInputs[Index]);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ }
+ for (MachineInstr *Truncation : Plan->Truncations) {
+ auto Removed = Truncation->parent()->removeInstruction(*Truncation);
+ if (!Removed)
+ return std::unexpected(Removed.error());
+ }
+
+ if (Plan->Generic == GenericOpcode::G_SHL) {
+ Register Amount = Plan->WideInputs[1];
+ if (Plan->SourceWidth < 32 || Plan->SourceWidth > 32) {
+ const std::uint64_t Mask =
+ Plan->SourceWidth == 64
+ ? std::numeric_limits<std::uint64_t>::max()
+ : (std::uint64_t{1} << Plan->SourceWidth) - 1U;
+ auto WideType = LLT::makeAnyScalar(64);
+ if (!WideType)
+ return std::unexpected(WideType.error());
+ MachineIRBuilder Builder(Function, Target);
+ auto MaskValue =
+ buildIntegerConstantBefore(Builder, Extension, 64, Mask);
+ if (!MaskValue)
+ return std::unexpected(MaskValue.error());
+ auto MaskedAmount = buildBinaryBefore(Builder, Extension,
+ GenericOpcode::G_AND, *WideType,
+ Amount, *MaskValue);
+ if (!MaskedAmount)
+ return std::unexpected(MaskedAmount.error());
+ Amount = *MaskedAmount;
+ }
+ auto SetOpcode = Extension.setOpcode(
+ Plan->SourceWidth == 32 ? targetOpcode(InstructionOpcode::G_SLLW)
+ : opcode(GenericOpcode::G_SHL));
+ if (!SetOpcode)
+ return std::unexpected(SetOpcode.error());
+ while (Extension.operandCount() > 1) {
+ auto Erased = Extension.eraseOperand(1);
+ if (!Erased)
+ return std::unexpected(Erased.error());
+ }
+ for (Register Input : {Plan->WideInputs[0], Amount}) {
+ auto Operand = MachineOperand::registerOperand(
+ Input, RegisterOperandFlags{RegisterOperandFlag::Use});
+ if (!Operand)
+ return std::unexpected(Operand.error());
+ auto Added = Extension.addOperand(std::move(*Operand));
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ MachineBasicBlock *BinaryBlock = Plan->Binary->parent();
+ if (BinaryBlock == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V narrow shift is detached during legalization");
+ return BinaryBlock->removeInstruction(*Plan->Binary);
+ }
+
+ if (Plan->SourceWidth == 32 &&
+ (Plan->Generic == GenericOpcode::G_ADD || Plan->Generic == GenericOpcode::G_SUB)) {
+ auto WideType = LLT::makeAnyScalar(64);
+ if (!WideType)
+ return std::unexpected(WideType.error());
+ auto Temporary = Function.registerInfo().createVirtualRegister(*WideType);
+ if (!Temporary)
+ return std::unexpected(Temporary.error());
+ auto Retargeted = Plan->Binary->operandAt(0)->setRegister(*Temporary);
+ if (!Retargeted)
+ return std::unexpected(Retargeted.error());
+ auto SetOpcode = Extension.setOpcode(opcode(GenericOpcode::G_SEXT_INREG));
+ if (!SetOpcode)
+ return std::unexpected(SetOpcode.error());
+ auto ReplacedSource = Extension.operandAt(1)->setRegister(*Temporary);
+ if (!ReplacedSource)
+ return std::unexpected(ReplacedSource.error());
+ auto Width = Extension.addOperand(MachineOperand::immediate(32));
+ if (!Width)
+ return std::unexpected(Width.error());
+ return {};
+ }
+
+ auto SetOpcode = Extension.setOpcode(opcode(Plan->Generic));
+ if (!SetOpcode)
+ return std::unexpected(SetOpcode.error());
+ while (Extension.operandCount() > 1) {
+ auto Erased = Extension.eraseOperand(1);
+ if (!Erased)
+ return std::unexpected(Erased.error());
+ }
+ for (Register Input : Plan->WideInputs) {
+ auto Operand = MachineOperand::registerOperand(
+ Input, RegisterOperandFlags{RegisterOperandFlag::Use});
+ if (!Operand)
+ return std::unexpected(Operand.error());
+ auto Added = Extension.addOperand(std::move(*Operand));
+ if (!Added)
+ return std::unexpected(Added.error());
+ }
+ MachineBasicBlock *BinaryBlock = Plan->Binary->parent();
+ if (BinaryBlock == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V narrow binary instruction is detached during legalization");
+ auto Removed = BinaryBlock->removeInstruction(*Plan->Binary);
+ if (!Removed)
+ return std::unexpected(Removed.error());
+ (void)Target;
+ return {};
+}
+
[[nodiscard]] 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]] 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 DestinationInfo = Function.registerInfo().virtualRegisterInfo(*Destination);
+ if (!DestinationInfo || !DestinationInfo->get().Type ||
+ DestinationInfo->get().Type->scalarBitWidth() != 64)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V constant-pool legalization requires a 64-bit destination");
+ const LLT DestinationType = *DestinationInfo->get().Type;
+ 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 = LLT::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]] bool isSupportedNonAtomicMemoryAccess(const MachineInstr &Instruction) noexcept {
if (Instruction.memOperands().size() != 1)
return false;
@@ -236,6 +854,1611 @@ memoryAccessBytes(const MachineInstr &Instruction) noexcept {
return std::bit_cast<std::int64_t>(Raw);
}
+[[nodiscard]] Expected<void> replaceRegisterUses(MachineFunction &Function, Register OldReg,
+ Register NewReg) {
+ std::vector<MachineOperand *> Uses;
+ for (MachineOperand &Operand : Function.registerInfo().useOperands(OldReg))
+ Uses.push_back(&Operand);
+ for (MachineOperand *Operand : Uses) {
+ auto Replaced = Operand->setRegister(NewReg);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ }
+ return {};
+}
+
+[[nodiscard]] Expected<void> eraseInstruction(MachineInstr &Instruction) {
+ MachineBasicBlock *Block = Instruction.parent();
+ if (Block == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V legalizer cannot erase a detached instruction");
+ return Block->removeInstruction(Instruction);
+}
+
+[[nodiscard]] bool allDefsUnused(const MachineInstr &Instruction,
+ const MachineFunction &Function) {
+ for (const MachineOperand &Operand : Instruction.operands()) {
+ const Register *Reg = Operand.getIf<Register>();
+ if (Reg != nullptr && Operand.isDef() && Reg->isVirtual() &&
+ !Function.registerInfo().useEmpty(*Reg))
+ return false;
+ }
+ return true;
+}
+
+void eraseDeadArtifacts(std::span<MachineInstr *const> Candidates,
+ const MachineFunction &Function) {
+ bool Changed = false;
+ do {
+ Changed = false;
+ for (auto It = Candidates.rbegin(); It != Candidates.rend(); ++It) {
+ MachineInstr *Instruction = *It;
+ if (Instruction == nullptr || Instruction->parent() == nullptr ||
+ !allDefsUnused(*Instruction, Function))
+ continue;
+ const Opcode Value = Instruction->opcode();
+ if (Value != opcode(GenericOpcode::G_TRUNC) &&
+ Value != opcode(GenericOpcode::G_ANYEXT) &&
+ Value != opcode(GenericOpcode::G_SEXT) && Value != opcode(GenericOpcode::G_ZEXT) &&
+ Value != opcode(GenericOpcode::G_MERGE_VALUES) &&
+ Value != opcode(GenericOpcode::G_UNMERGE_VALUES) &&
+ Value != opcode(GenericOpcode::COPY))
+ continue;
+ if (eraseInstruction(*Instruction))
+ Changed = true;
+ }
+ } while (Changed);
+}
+
+[[nodiscard]] Expected<Register> buildIntegerConstantBefore(MachineIRBuilder &Builder,
+ MachineInstr &Anchor,
+ std::uint32_t Width,
+ std::uint64_t Value) {
+ auto Type = LLT::makeAnyScalar(Width);
+ if (!Type)
+ return std::unexpected(Type.error());
+ auto Result = Builder.function().registerInfo().createVirtualRegister(*Type);
+ if (!Result)
+ return std::unexpected(Result.error());
+ auto Immediate = IntegerImmediate::fromUnsigned(Width, Value);
+ if (!Immediate)
+ return std::unexpected(Immediate.error());
+ auto Point = Builder.setInsertPointBefore(Anchor);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(opcode(GenericOpcode::G_CONSTANT));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ if (auto Added = Pending->addDef(*Result); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addIntegerImmediate(std::move(*Immediate)); !Added)
+ return std::unexpected(Added.error());
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return *Result;
+}
+
+[[nodiscard]] Expected<Register> buildUnaryBefore(MachineIRBuilder &Builder, MachineInstr &Anchor,
+ GenericOpcode Generic, LLT Type,
+ Register Source) {
+ auto Result = Builder.function().registerInfo().createVirtualRegister(Type);
+ if (!Result)
+ return std::unexpected(Result.error());
+ auto Point = Builder.setInsertPointBefore(Anchor);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(opcode(Generic));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ if (auto Added = Pending->addDef(*Result); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addUse(Source); !Added)
+ return std::unexpected(Added.error());
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return *Result;
+}
+
+[[nodiscard]] Expected<Register> buildBinaryBefore(MachineIRBuilder &Builder, MachineInstr &Anchor,
+ GenericOpcode Generic, LLT Type, Register Left,
+ Register Right) {
+ auto Result = Builder.function().registerInfo().createVirtualRegister(Type);
+ if (!Result)
+ return std::unexpected(Result.error());
+ auto Point = Builder.setInsertPointBefore(Anchor);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(opcode(Generic));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ if (auto Added = Pending->addDef(*Result); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addUse(Left); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addUse(Right); !Added)
+ return std::unexpected(Added.error());
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return *Result;
+}
+
+[[nodiscard]] Expected<void> rewriteUnary(MachineInstr &Instruction, GenericOpcode Generic,
+ Register Destination, Register Source) {
+ auto SetOpcode = Instruction.setOpcode(opcode(Generic));
+ if (!SetOpcode)
+ return std::unexpected(SetOpcode.error());
+ while (Instruction.operandCount() != 0) {
+ auto Erased = Instruction.eraseOperand(Instruction.operandCount() - 1U);
+ if (!Erased)
+ return std::unexpected(Erased.error());
+ }
+ auto Def = MachineOperand::registerOperand(
+ Destination, RegisterOperandFlags{RegisterOperandFlag::Define});
+ if (!Def)
+ return std::unexpected(Def.error());
+ if (auto Added = Instruction.addOperand(std::move(*Def)); !Added)
+ return std::unexpected(Added.error());
+ auto Use = MachineOperand::registerOperand(Source, RegisterOperandFlags{RegisterOperandFlag::Use});
+ if (!Use)
+ return std::unexpected(Use.error());
+ return Instruction.addOperand(std::move(*Use));
+}
+
+[[nodiscard]] Expected<void> rewriteBinary(MachineInstr &Instruction, GenericOpcode Generic,
+ Register Destination, Register Left, Register Right) {
+ auto Rewritten = rewriteUnary(Instruction, Generic, Destination, Left);
+ if (!Rewritten)
+ return std::unexpected(Rewritten.error());
+ auto Use = MachineOperand::registerOperand(Right, RegisterOperandFlags{RegisterOperandFlag::Use});
+ if (!Use)
+ return std::unexpected(Use.error());
+ return Instruction.addOperand(std::move(*Use));
+}
+
+[[nodiscard]] bool isKnownZeroExtended(const MachineFunction &Function, Register Reg,
+ std::uint32_t ActiveBits, unsigned Depth = 0) {
+ if (Depth > 8 || !Reg.isVirtual())
+ return false;
+ const auto Info = Function.registerInfo().virtualRegisterInfo(Reg);
+ if (!Info || !Info->get().Type)
+ return false;
+ const std::uint32_t Width = Info->get().Type->scalarBitWidth();
+ if (Width <= ActiveBits)
+ return true;
+ const MachineOperand *Definition = Function.registerInfo().getUniqueVRegDef(Reg);
+ const MachineInstr *Def = Definition == nullptr ? nullptr : Definition->parent();
+ if (Def == nullptr)
+ return false;
+ if (Def->opcode() == opcode(GenericOpcode::G_ASSERT_ZEXT)) {
+ const MachineOperand *BitsOperand = Def->operandAt(2);
+ auto Bits = BitsOperand == nullptr
+ ? Expected<std::int64_t>(std::unexpected(
+ Error(ErrorCode::InvalidArgument, "missing asserted extension width")))
+ : signedImmediate(*BitsOperand);
+ return Bits && *Bits >= 0 && static_cast<std::uint64_t>(*Bits) <= ActiveBits;
+ }
+ if (Def->opcode() == opcode(GenericOpcode::G_ZEXT)) {
+ const MachineOperand *SourceOperand = Def->operandAt(1);
+ const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>();
+ if (Source == nullptr)
+ return false;
+ const auto SourceInfo = Function.registerInfo().virtualRegisterInfo(*Source);
+ return SourceInfo && SourceInfo->get().Type &&
+ SourceInfo->get().Type->scalarBitWidth() <= ActiveBits;
+ }
+ if (Def->opcode() == opcode(GenericOpcode::G_TRUNC)) {
+ const MachineOperand *SourceOperand = Def->operandAt(1);
+ const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>();
+ return Source != nullptr && isKnownZeroExtended(Function, *Source, ActiveBits, Depth + 1U);
+ }
+ return false;
+}
+
+[[nodiscard]] Expected<Register>
+zeroExtendToRV64(MachineIRBuilder &Builder, MachineInstr &Anchor, MachineFunction &Function,
+ Register Source, std::vector<MachineInstr *> &DeadCandidates) {
+ const auto Info = Function.registerInfo().virtualRegisterInfo(Source);
+ if (!Info || !Info->get().Type)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V merge source has no scalar type");
+ const LLT SourceType = *Info->get().Type;
+ if (SourceType.scalarBitWidth() == 64)
+ return Source;
+ const MachineOperand *Definition = Function.registerInfo().getUniqueVRegDef(Source);
+ MachineInstr *Def = Definition == nullptr ? nullptr : const_cast<MachineInstr *>(Definition->parent());
+ if (Def != nullptr && Def->opcode() == opcode(GenericOpcode::G_TRUNC)) {
+ const MachineOperand *WideOperand = Def->operandAt(1);
+ const Register *Wide = WideOperand == nullptr ? nullptr : WideOperand->getIf<Register>();
+ if (Wide != nullptr) {
+ const auto WideInfo = Function.registerInfo().virtualRegisterInfo(*Wide);
+ if (WideInfo && WideInfo->get().Type && WideInfo->get().Type->scalarBitWidth() == 64 &&
+ isKnownZeroExtended(Function, *Wide, SourceType.scalarBitWidth())) {
+ DeadCandidates.push_back(Def);
+ return *Wide;
+ }
+ }
+ }
+ auto WideType = LLT::makeAnyScalar(64);
+ if (!WideType)
+ return std::unexpected(WideType.error());
+ return buildUnaryBefore(Builder, Anchor, GenericOpcode::G_ZEXT, *WideType, Source);
+}
+
+[[nodiscard]] MachineInstr *singleUseInstruction(const MachineFunction &Function, Register Reg) {
+ MachineInstr *Result = nullptr;
+ auto &RegisterInfo = const_cast<MachineRegisterInfo &>(Function.registerInfo());
+ for (MachineOperand &Operand : RegisterInfo.useOperands(Reg)) {
+ MachineInstr *Instruction = Operand.parent();
+ if (Instruction == nullptr || Instruction->parent() == nullptr)
+ continue;
+ if (Result != nullptr && Result != Instruction)
+ return nullptr;
+ Result = Instruction;
+ }
+ return Result;
+}
+
+[[nodiscard]] Expected<void> lowerWideMergeValues(MachineInstr &Instruction,
+ MachineFunction &Function) {
+ const MachineOperand *DestinationOperand = Instruction.operandAt(0);
+ const Register *Destination =
+ DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>();
+ const MachineOperand *LowOperand = Instruction.operandAt(1);
+ const Register *Low = LowOperand == nullptr ? nullptr : LowOperand->getIf<Register>();
+ if (Destination == nullptr || Low == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide merge has invalid register operands");
+
+ std::vector<MachineInstr *> Truncations;
+ auto &RegisterInfo = Function.registerInfo();
+ std::vector<MachineOperand *> Uses;
+ for (MachineOperand &Use : RegisterInfo.useOperands(*Destination))
+ Uses.push_back(&Use);
+ for (MachineOperand *Use : Uses) {
+ MachineInstr *Trunc = Use->parent();
+ if (Trunc == nullptr || Trunc->parent() == nullptr ||
+ Trunc->opcode() != opcode(GenericOpcode::G_TRUNC) || Trunc->operandAt(1) != Use)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide merge is only representable through low-part truncation");
+ const MachineOperand *TruncResultOperand = Trunc->operandAt(0);
+ const Register *TruncResult =
+ TruncResultOperand == nullptr ? nullptr : TruncResultOperand->getIf<Register>();
+ const auto LowInfo = RegisterInfo.virtualRegisterInfo(*Low);
+ const auto ResultInfo = TruncResult == nullptr
+ ? Expected<std::reference_wrapper<const VirtualRegisterInfo>>(
+ std::unexpected(Error(ErrorCode::InvalidArgument,
+ "missing truncation result")))
+ : RegisterInfo.virtualRegisterInfo(*TruncResult);
+ if (!LowInfo || !LowInfo->get().Type || !ResultInfo || !ResultInfo->get().Type ||
+ ResultInfo->get().Type->scalarBitWidth() > LowInfo->get().Type->scalarBitWidth())
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide merge truncation spans more than its low source");
+ if (*ResultInfo->get().Type == *LowInfo->get().Type) {
+ auto Replaced = replaceRegisterUses(Function, *TruncResult, *Low);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ Truncations.push_back(Trunc);
+ } else {
+ auto Replaced = Use->setRegister(*Low);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ }
+ }
+ for (MachineInstr *Trunc : Truncations) {
+ auto Erased = eraseInstruction(*Trunc);
+ if (!Erased)
+ return std::unexpected(Erased.error());
+ }
+ return eraseInstruction(Instruction);
+}
+
+[[nodiscard]] Expected<void> lowerMergeValues(MachineInstr &Instruction,
+ MachineFunction &Function,
+ const TargetDescription &Target) {
+ const std::optional<LLT> DestinationType = resultType(Function, Instruction);
+ if (!DestinationType || DestinationType->scalarBitWidth() == 0)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V merge destination has no scalar type");
+ if (DestinationType->scalarBitWidth() > 64)
+ return lowerWideMergeValues(Instruction, Function);
+
+ const MachineOperand *DestinationOperand = Instruction.operandAt(0);
+ const Register *Destination =
+ DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>();
+ if (Destination == nullptr || Instruction.operandCount() < 3)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V merge has invalid register operands");
+
+ Register Result = *Destination;
+ MachineInstr *FoldedExtension = nullptr;
+ if (DestinationType->scalarBitWidth() < 64) {
+ MachineInstr *Use = singleUseInstruction(Function, *Destination);
+ if (Use != nullptr && Use->opcode() == opcode(GenericOpcode::G_ZEXT) &&
+ operandType(Function, *Use, 0).transform([](LLT Type) {
+ return Type.scalarBitWidth() == 64;
+ }).value_or(false)) {
+ const MachineOperand *ExtendedOperand = Use->operandAt(0);
+ const Register *Extended =
+ ExtendedOperand == nullptr ? nullptr : ExtendedOperand->getIf<Register>();
+ if (Extended != nullptr) {
+ Result = *Extended;
+ FoldedExtension = Use;
+ }
+ }
+ }
+
+ auto WideType = LLT::makeAnyScalar(64);
+ if (!WideType)
+ return std::unexpected(WideType.error());
+ MachineIRBuilder Builder(Function, Target);
+ std::vector<MachineInstr *> DeadCandidates;
+ std::vector<Register> Terms;
+ Terms.reserve(Instruction.operandCount() - 1U);
+ for (std::size_t Index = 1; Index < Instruction.operandCount(); ++Index) {
+ const MachineOperand *Operand = Instruction.operandAt(Index);
+ const Register *Source = Operand == nullptr ? nullptr : Operand->getIf<Register>();
+ if (Source == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V merge source is not a register");
+ auto WideSource = zeroExtendToRV64(Builder, Instruction, Function, *Source, DeadCandidates);
+ if (!WideSource)
+ return std::unexpected(WideSource.error());
+ if (Index == 1) {
+ Terms.push_back(*WideSource);
+ continue;
+ }
+ const std::optional<LLT> SourceType = registerType(Function, *Operand);
+ if (!SourceType)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V merge source has no scalar type");
+ const std::uint64_t Shift =
+ static_cast<std::uint64_t>(SourceType->scalarBitWidth()) * (Index - 1U);
+ auto Amount = buildIntegerConstantBefore(Builder, Instruction, 64, Shift);
+ if (!Amount)
+ return std::unexpected(Amount.error());
+ auto Shifted = buildBinaryBefore(Builder, Instruction, GenericOpcode::G_SHL, *WideType,
+ *WideSource, *Amount);
+ if (!Shifted)
+ return std::unexpected(Shifted.error());
+ Terms.push_back(*Shifted);
+ }
+
+ Register Accumulator = Terms.front();
+ for (std::size_t Index = 1; Index + 1U < Terms.size(); ++Index) {
+ auto Combined = buildBinaryBefore(Builder, Instruction, GenericOpcode::G_OR, *WideType,
+ Accumulator, Terms[Index]);
+ if (!Combined)
+ return std::unexpected(Combined.error());
+ Accumulator = *Combined;
+ }
+ if (DestinationType->scalarBitWidth() == 64 || FoldedExtension != nullptr) {
+ auto Rewritten = rewriteBinary(Instruction, GenericOpcode::G_OR, Result, Accumulator,
+ Terms.back());
+ if (!Rewritten)
+ return std::unexpected(Rewritten.error());
+ } else {
+ auto Combined = buildBinaryBefore(Builder, Instruction, GenericOpcode::G_OR, *WideType,
+ Accumulator, Terms.back());
+ if (!Combined)
+ return std::unexpected(Combined.error());
+ auto Rewritten = rewriteUnary(Instruction, GenericOpcode::G_TRUNC, *Destination, *Combined);
+ if (!Rewritten)
+ return std::unexpected(Rewritten.error());
+ }
+ if (FoldedExtension != nullptr) {
+ auto Erased = eraseInstruction(*FoldedExtension);
+ if (!Erased)
+ return std::unexpected(Erased.error());
+ }
+ eraseDeadArtifacts(DeadCandidates, Function);
+ return {};
+}
+
+[[nodiscard]] Expected<void>
+foldWideExtendedUnmerge(MachineInstr &Instruction, MachineFunction &Function) {
+ const std::size_t OutputCount = Instruction.operandCount() - 1U;
+ const MachineOperand *SourceOperand = Instruction.operandAt(OutputCount);
+ const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>();
+ const MachineOperand *Definition =
+ Source == nullptr ? nullptr : Function.registerInfo().getUniqueVRegDef(*Source);
+ MachineInstr *Extension =
+ Definition == nullptr ? nullptr : const_cast<MachineInstr *>(Definition->parent());
+ if (Extension == nullptr ||
+ (Extension->opcode() != opcode(GenericOpcode::G_ZEXT) &&
+ Extension->opcode() != opcode(GenericOpcode::G_ANYEXT) &&
+ Extension->opcode() != opcode(GenericOpcode::G_SEXT)))
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide unmerge requires an extension-defined source");
+ const MachineOperand *NarrowOperand = Extension->operandAt(1);
+ const Register *Narrow = NarrowOperand == nullptr ? nullptr : NarrowOperand->getIf<Register>();
+ if (Narrow == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide unmerge extension has no register source");
+ const auto NarrowInfo = Function.registerInfo().virtualRegisterInfo(*Narrow);
+ if (!NarrowInfo || !NarrowInfo->get().Type)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide unmerge extension source has no type");
+ const std::uint32_t NarrowWidth = NarrowInfo->get().Type->scalarBitWidth();
+
+ std::vector<MachineInstr *> DeadUsers;
+ for (std::size_t Index = 0; Index < OutputCount; ++Index) {
+ const MachineOperand *OutputOperand = Instruction.operandAt(Index);
+ const Register *Output = OutputOperand == nullptr ? nullptr : OutputOperand->getIf<Register>();
+ if (Output == nullptr || Function.registerInfo().useEmpty(*Output))
+ continue;
+ const auto OutputInfo = Function.registerInfo().virtualRegisterInfo(*Output);
+ if (!OutputInfo || !OutputInfo->get().Type)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide unmerge output has no type");
+ const std::uint64_t Offset =
+ static_cast<std::uint64_t>(Index) * OutputInfo->get().Type->scalarBitWidth();
+ if (Offset != 0)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide unmerge cannot materialize a used high wide part");
+ if (OutputInfo->get().Type->scalarBitWidth() == NarrowWidth) {
+ auto Replaced = replaceRegisterUses(Function, *Output, *Narrow);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ continue;
+ }
+ if (OutputInfo->get().Type->scalarBitWidth() < NarrowWidth)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide unmerge output unexpectedly narrows its source");
+ std::vector<MachineOperand *> Uses;
+ for (MachineOperand &Use : Function.registerInfo().useOperands(*Output))
+ Uses.push_back(&Use);
+ for (MachineOperand *Use : Uses) {
+ MachineInstr *Trunc = Use->parent();
+ if (Trunc == nullptr || Trunc->parent() == nullptr ||
+ Trunc->opcode() != opcode(GenericOpcode::G_TRUNC) || Trunc->operandAt(1) != Use)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide unmerge result is only representable through truncation");
+ const MachineOperand *ResultOperand = Trunc->operandAt(0);
+ const Register *Result = ResultOperand == nullptr ? nullptr : ResultOperand->getIf<Register>();
+ const auto ResultInfo = Result == nullptr
+ ? Expected<std::reference_wrapper<const VirtualRegisterInfo>>(
+ std::unexpected(Error(ErrorCode::InvalidArgument,
+ "missing truncation result")))
+ : Function.registerInfo().virtualRegisterInfo(*Result);
+ if (!ResultInfo || !ResultInfo->get().Type ||
+ ResultInfo->get().Type->scalarBitWidth() > NarrowWidth)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide unmerge truncation exceeds its source width");
+ if (ResultInfo->get().Type->scalarBitWidth() == NarrowWidth) {
+ auto Replaced = replaceRegisterUses(Function, *Result, *Narrow);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ DeadUsers.push_back(Trunc);
+ } else {
+ auto Replaced = Use->setRegister(*Narrow);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ }
+ }
+ }
+ for (MachineInstr *User : DeadUsers) {
+ auto Erased = eraseInstruction(*User);
+ if (!Erased)
+ return std::unexpected(Erased.error());
+ }
+ auto Erased = eraseInstruction(Instruction);
+ if (!Erased)
+ return std::unexpected(Erased.error());
+ if (Function.registerInfo().useEmpty(*Source))
+ return eraseInstruction(*Extension);
+ return {};
+}
+
+[[nodiscard]] Expected<void> lowerUnmergeValues(MachineInstr &Instruction,
+ MachineFunction &Function,
+ const TargetDescription &Target) {
+ if (Instruction.operandCount() < 3)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V unmerge has invalid register operands");
+ const std::size_t OutputCount = Instruction.operandCount() - 1U;
+ const std::optional<LLT> OutputType = operandType(Function, Instruction, 0);
+ const std::optional<LLT> SourceType = operandType(Function, Instruction, OutputCount);
+ if (!OutputType || !SourceType)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V unmerge operands have no scalar types");
+ if (SourceType->scalarBitWidth() > 64 || OutputType->scalarBitWidth() > 64)
+ return foldWideExtendedUnmerge(Instruction, Function);
+
+ const MachineOperand *SourceOperand = Instruction.operandAt(OutputCount);
+ const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>();
+ if (Source == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V unmerge source is not a register");
+ auto WideType = LLT::makeAnyScalar(64);
+ if (!WideType)
+ return std::unexpected(WideType.error());
+ MachineIRBuilder Builder(Function, Target);
+ std::vector<MachineInstr *> FoldedExtensions;
+ for (std::size_t Index = 0; Index < OutputCount; ++Index) {
+ const MachineOperand *OutputOperand = Instruction.operandAt(Index);
+ const Register *Output = OutputOperand == nullptr ? nullptr : OutputOperand->getIf<Register>();
+ if (Output == nullptr || Function.registerInfo().useEmpty(*Output))
+ continue;
+ Register Extracted = *Source;
+ const std::uint64_t Offset =
+ static_cast<std::uint64_t>(Index) * OutputType->scalarBitWidth();
+ if (Offset != 0) {
+ auto Amount = buildIntegerConstantBefore(Builder, Instruction, 64, Offset);
+ if (!Amount)
+ return std::unexpected(Amount.error());
+ auto Shifted = buildBinaryBefore(Builder, Instruction, GenericOpcode::G_LSHR, *WideType,
+ *Source, *Amount);
+ if (!Shifted)
+ return std::unexpected(Shifted.error());
+ Extracted = *Shifted;
+ }
+
+ MachineInstr *Extension = singleUseInstruction(Function, *Output);
+ const bool FoldZExt = Extension != nullptr &&
+ Extension->opcode() == opcode(GenericOpcode::G_ZEXT) &&
+ operandType(Function, *Extension, 0)
+ .transform([](LLT Type) { return Type.scalarBitWidth() == 64; })
+ .value_or(false);
+ if (FoldZExt) {
+ const MachineOperand *ExtendedOperand = Extension->operandAt(0);
+ const Register *Extended =
+ ExtendedOperand == nullptr ? nullptr : ExtendedOperand->getIf<Register>();
+ if (Extended == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V unmerge extension has no destination");
+ const std::uint64_t Mask = OutputType->scalarBitWidth() == 64
+ ? std::numeric_limits<std::uint64_t>::max()
+ : (std::uint64_t{1} << OutputType->scalarBitWidth()) - 1U;
+ auto MaskReg = buildIntegerConstantBefore(Builder, Instruction, 64, Mask);
+ if (!MaskReg)
+ return std::unexpected(MaskReg.error());
+ auto Masked = buildBinaryBefore(Builder, Instruction, GenericOpcode::G_AND, *WideType,
+ Extracted, *MaskReg);
+ if (!Masked)
+ return std::unexpected(Masked.error());
+ auto Replaced = replaceRegisterUses(Function, *Extended, *Masked);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ FoldedExtensions.push_back(Extension);
+ continue;
+ }
+ auto Truncated = buildUnaryBefore(Builder, Instruction, GenericOpcode::G_TRUNC, *OutputType,
+ Extracted);
+ if (!Truncated)
+ return std::unexpected(Truncated.error());
+ auto Replaced = replaceRegisterUses(Function, *Output, *Truncated);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ }
+ for (MachineInstr *Extension : FoldedExtensions) {
+ auto Erased = eraseInstruction(*Extension);
+ if (!Erased)
+ return std::unexpected(Erased.error());
+ }
+ return eraseInstruction(Instruction);
+}
+
+struct WideValueParts final {
+ std::uint32_t BitWidth = 0;
+ std::vector<Register> Parts;
+ std::vector<MachineInstr *> Artifacts;
+};
+
+[[nodiscard]] Expected<WideValueParts> collectWideValueParts(Register Value,
+ MachineFunction &Function,
+ unsigned Depth = 0) {
+ if (Depth > 16)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide-value artifact chain is too deep");
+ const auto Info = Function.registerInfo().virtualRegisterInfo(Value);
+ if (!Info || !Info->get().Type)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide value has no scalar type");
+ const LLT Type = *Info->get().Type;
+ if ((!Type.isInteger() && !Type.isAnyScalar()) || Type.scalarBitWidth() == 0)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide-value decomposition requires an integer scalar");
+ if (Type.scalarBitWidth() <= 64)
+ return WideValueParts{
+ .BitWidth = Type.scalarBitWidth(), .Parts = {Value}, .Artifacts = {}};
+
+ const MachineOperand *Definition = Function.registerInfo().getUniqueVRegDef(Value);
+ MachineInstr *Def =
+ Definition == nullptr ? nullptr : const_cast<MachineInstr *>(Definition->parent());
+ if (Def == nullptr)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide value has no decomposable definition");
+ if (Def->opcode() == opcode(GenericOpcode::G_MERGE_VALUES)) {
+ WideValueParts Result;
+ Result.BitWidth = Type.scalarBitWidth();
+ for (std::size_t Index = 1; Index < Def->operandCount(); ++Index) {
+ const MachineOperand *Operand = Def->operandAt(Index);
+ const Register *Part = Operand == nullptr ? nullptr : Operand->getIf<Register>();
+ const std::optional<LLT> PartType =
+ Operand == nullptr ? std::nullopt : registerType(Function, *Operand);
+ if (Part == nullptr || !PartType || PartType->scalarBitWidth() != 64)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide merge must already use 64-bit limbs");
+ Result.Parts.push_back(*Part);
+ }
+ Result.Artifacts.push_back(Def);
+ return Result;
+ }
+ if (Def->opcode() == opcode(GenericOpcode::G_TRUNC)) {
+ const MachineOperand *SourceOperand = Def->operandAt(1);
+ const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>();
+ if (Source == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide truncation has no source register");
+ auto Result = collectWideValueParts(*Source, Function, Depth + 1U);
+ if (!Result)
+ return std::unexpected(Result.error());
+ Result->BitWidth = Type.scalarBitWidth();
+ const std::size_t RequiredParts = (Type.scalarBitWidth() + 63U) / 64U;
+ if (Result->Parts.size() < RequiredParts)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide truncation source has too few limbs");
+ Result->Parts.resize(RequiredParts);
+ Result->Artifacts.push_back(Def);
+ return Result;
+ }
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide value is not defined by merge/trunc artifacts");
+}
+
+struct WideResultPlan final {
+ std::vector<Register> Outputs;
+ MachineInstr *Extension = nullptr;
+ MachineInstr *Unmerge = nullptr;
+};
+
+[[nodiscard]] Expected<WideResultPlan> wideResultPlan(Register Result,
+ MachineFunction &Function,
+ std::size_t RequiredParts) {
+ MachineInstr *Use = singleUseInstruction(Function, Result);
+ WideResultPlan Plan;
+ if (Use != nullptr &&
+ (Use->opcode() == opcode(GenericOpcode::G_ANYEXT) ||
+ Use->opcode() == opcode(GenericOpcode::G_SEXT) ||
+ Use->opcode() == opcode(GenericOpcode::G_ZEXT))) {
+ Plan.Extension = Use;
+ const MachineOperand *ExtendedOperand = Use->operandAt(0);
+ const Register *Extended =
+ ExtendedOperand == nullptr ? nullptr : ExtendedOperand->getIf<Register>();
+ Use = Extended == nullptr ? nullptr : singleUseInstruction(Function, *Extended);
+ }
+ if (Use == nullptr || Use->opcode() != opcode(GenericOpcode::G_UNMERGE_VALUES))
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide result must feed an unmerge artifact");
+ Plan.Unmerge = Use;
+ const std::size_t OutputCount = Use->operandCount() - 1U;
+ if (OutputCount < RequiredParts)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide result unmerge has too few outputs");
+ Plan.Outputs.reserve(RequiredParts);
+ for (std::size_t Index = 0; Index < RequiredParts; ++Index) {
+ const MachineOperand *Operand = Use->operandAt(Index);
+ const Register *Output = Operand == nullptr ? nullptr : Operand->getIf<Register>();
+ const std::optional<LLT> Type =
+ Operand == nullptr ? std::nullopt : registerType(Function, *Operand);
+ if (Output == nullptr || !Type || Type->scalarBitWidth() != 64)
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V wide result must unmerge to 64-bit limbs");
+ Plan.Outputs.push_back(*Output);
+ }
+ return Plan;
+}
+
+[[nodiscard]] Expected<Register>
+buildIntegerCompareBefore(MachineIRBuilder &Builder, MachineInstr &Anchor,
+ IntegerPredicate Predicate, Register Left, Register Right) {
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ auto Result = Builder.function().registerInfo().createVirtualRegister(*Type);
+ if (!Result)
+ return std::unexpected(Result.error());
+ auto Point = Builder.setInsertPointBefore(Anchor);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(opcode(GenericOpcode::G_ICMP));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ if (auto Added = Pending->addDef(*Result); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addIntegerPredicate(Predicate); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addUse(Left); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addUse(Right); !Added)
+ return std::unexpected(Added.error());
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return *Result;
+}
+
+[[nodiscard]] Expected<Register>
+buildSelectBefore(MachineIRBuilder &Builder, MachineInstr &Anchor, LLT Type,
+ Register Condition, Register TrueValue, Register FalseValue) {
+ auto Result = Builder.function().registerInfo().createVirtualRegister(Type);
+ if (!Result)
+ return std::unexpected(Result.error());
+ auto Point = Builder.setInsertPointBefore(Anchor);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(opcode(GenericOpcode::G_SELECT));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ if (auto Added = Pending->addDef(*Result); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addUse(Condition); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addUse(TrueValue); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addUse(FalseValue); !Added)
+ return std::unexpected(Added.error());
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return *Result;
+}
+
+struct TwoLimbShiftResult final {
+ Register Low;
+ Register High;
+};
+
+[[nodiscard]] Expected<TwoLimbShiftResult>
+emitTwoLimbLeftShiftBody(MachineIRBuilder &Builder, MachineInstr &Anchor,
+ Register Low, Register High, Register Amount,
+ Register AmountExcess, Register AmountLack,
+ Register Zero, Register IsShort, Register IsZero) {
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ auto LowShort =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SHL, *Type, Low, Amount);
+ if (!LowShort)
+ return std::unexpected(LowShort.error());
+ auto CrossLow =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_LSHR, *Type, Low, AmountLack);
+ if (!CrossLow)
+ return std::unexpected(CrossLow.error());
+ auto HighShort =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SHL, *Type, High, Amount);
+ if (!HighShort)
+ return std::unexpected(HighShort.error());
+ auto ShortHigh = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_OR, *Type,
+ *CrossLow, *HighShort);
+ if (!ShortHigh)
+ return std::unexpected(ShortHigh.error());
+ auto LongHigh =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SHL, *Type, Low, AmountExcess);
+ if (!LongHigh)
+ return std::unexpected(LongHigh.error());
+ auto ResultLow =
+ buildSelectBefore(Builder, Anchor, *Type, IsShort, *LowShort, Zero);
+ if (!ResultLow)
+ return std::unexpected(ResultLow.error());
+ auto ShiftedHigh =
+ buildSelectBefore(Builder, Anchor, *Type, IsShort, *ShortHigh, *LongHigh);
+ if (!ShiftedHigh)
+ return std::unexpected(ShiftedHigh.error());
+ auto ResultHigh =
+ buildSelectBefore(Builder, Anchor, *Type, IsZero, High, *ShiftedHigh);
+ if (!ResultHigh)
+ return std::unexpected(ResultHigh.error());
+ return TwoLimbShiftResult{.Low = *ResultLow, .High = *ResultHigh};
+}
+
+[[nodiscard]] Expected<TwoLimbShiftResult>
+emitTwoLimbLeftShift(MachineIRBuilder &Builder, MachineInstr &Anchor,
+ Register Low, Register High, Register Amount) {
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ auto Boundary = buildIntegerConstantBefore(Builder, Anchor, 64, 64);
+ if (!Boundary)
+ return std::unexpected(Boundary.error());
+ auto AmountExcess = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type,
+ Amount, *Boundary);
+ if (!AmountExcess)
+ return std::unexpected(AmountExcess.error());
+ auto AmountLack = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type,
+ *Boundary, Amount);
+ if (!AmountLack)
+ return std::unexpected(AmountLack.error());
+ auto Zero = buildIntegerConstantBefore(Builder, Anchor, 64, 0);
+ if (!Zero)
+ return std::unexpected(Zero.error());
+ auto IsShort = buildIntegerCompareBefore(Builder, Anchor,
+ IntegerPredicate::UnsignedLessThan,
+ Amount, *Boundary);
+ if (!IsShort)
+ return std::unexpected(IsShort.error());
+ auto IsZero = buildIntegerCompareBefore(Builder, Anchor, IntegerPredicate::Equal,
+ Amount, *Zero);
+ if (!IsZero)
+ return std::unexpected(IsZero.error());
+ return emitTwoLimbLeftShiftBody(Builder, Anchor, Low, High, Amount,
+ *AmountExcess, *AmountLack, *Zero, *IsShort,
+ *IsZero);
+}
+
+[[nodiscard]] Expected<Register>
+emitLowLimbOfTwoLimbLogicalRightShift(
+ MachineIRBuilder &Builder, MachineInstr &Anchor, Register Low, Register High,
+ Register Amount, Register Boundary, Register Zero) {
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ auto AmountExcess = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type,
+ Amount, Boundary);
+ if (!AmountExcess)
+ return std::unexpected(AmountExcess.error());
+ auto AmountLack = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type,
+ Boundary, Amount);
+ if (!AmountLack)
+ return std::unexpected(AmountLack.error());
+ auto IsShort = buildIntegerCompareBefore(Builder, Anchor,
+ IntegerPredicate::UnsignedLessThan,
+ Amount, Boundary);
+ if (!IsShort)
+ return std::unexpected(IsShort.error());
+ auto IsZero = buildIntegerCompareBefore(Builder, Anchor, IntegerPredicate::Equal,
+ Amount, Zero);
+ if (!IsZero)
+ return std::unexpected(IsZero.error());
+ auto LowShort =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_LSHR, *Type, Low, Amount);
+ if (!LowShort)
+ return std::unexpected(LowShort.error());
+ auto HighShort =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SHL, *Type, High, *AmountLack);
+ if (!HighShort)
+ return std::unexpected(HighShort.error());
+ auto ShortValue = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_OR, *Type,
+ *LowShort, *HighShort);
+ if (!ShortValue)
+ return std::unexpected(ShortValue.error());
+ auto LongValue = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_LSHR, *Type,
+ High, *AmountExcess);
+ if (!LongValue)
+ return std::unexpected(LongValue.error());
+ auto Shifted = buildSelectBefore(Builder, Anchor, *Type, *IsShort, *ShortValue,
+ *LongValue);
+ if (!Shifted)
+ return std::unexpected(Shifted.error());
+ return buildSelectBefore(Builder, Anchor, *Type, *IsZero, Low, *Shifted);
+}
+
+[[nodiscard]] Expected<std::vector<Register>>
+emitThreeLimbLeftShift(MachineIRBuilder &Builder, MachineInstr &Anchor,
+ std::span<const Register> Parts, Register Amount) {
+ if (Parts.size() != 3)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V three-limb shift requires exactly three limbs");
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+
+ auto OuterBoundary = buildIntegerConstantBefore(Builder, Anchor, 64, 128);
+ if (!OuterBoundary)
+ return std::unexpected(OuterBoundary.error());
+ auto OuterExcess = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type,
+ Amount, *OuterBoundary);
+ if (!OuterExcess)
+ return std::unexpected(OuterExcess.error());
+ auto OuterLack = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type,
+ *OuterBoundary, Amount);
+ if (!OuterLack)
+ return std::unexpected(OuterLack.error());
+ auto Zero = buildIntegerConstantBefore(Builder, Anchor, 64, 0);
+ if (!Zero)
+ return std::unexpected(Zero.error());
+ auto OuterIsShort = buildIntegerCompareBefore(
+ Builder, Anchor, IntegerPredicate::UnsignedLessThan, Amount, *OuterBoundary);
+ if (!OuterIsShort)
+ return std::unexpected(OuterIsShort.error());
+ auto OuterIsZero = buildIntegerCompareBefore(Builder, Anchor, IntegerPredicate::Equal,
+ Amount, *Zero);
+ if (!OuterIsZero)
+ return std::unexpected(OuterIsZero.error());
+
+ auto InnerBoundary = buildIntegerConstantBefore(Builder, Anchor, 64, 64);
+ if (!InnerBoundary)
+ return std::unexpected(InnerBoundary.error());
+ auto InnerExcess = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type,
+ Amount, *InnerBoundary);
+ if (!InnerExcess)
+ return std::unexpected(InnerExcess.error());
+ auto InnerLack = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type,
+ *InnerBoundary, Amount);
+ if (!InnerLack)
+ return std::unexpected(InnerLack.error());
+ auto InnerIsShort = buildIntegerCompareBefore(
+ Builder, Anchor, IntegerPredicate::UnsignedLessThan, Amount, *InnerBoundary);
+ if (!InnerIsShort)
+ return std::unexpected(InnerIsShort.error());
+ auto InnerIsZero = buildIntegerCompareBefore(Builder, Anchor, IntegerPredicate::Equal,
+ Amount, *Zero);
+ if (!InnerIsZero)
+ return std::unexpected(InnerIsZero.error());
+ auto Lower = emitTwoLimbLeftShiftBody(
+ Builder, Anchor, Parts[0], Parts[1], Amount, *InnerExcess, *InnerLack,
+ *Zero, *InnerIsShort, *InnerIsZero);
+ if (!Lower)
+ return std::unexpected(Lower.error());
+
+ auto Cross = emitLowLimbOfTwoLimbLogicalRightShift(
+ Builder, Anchor, Parts[0], Parts[1], *OuterLack, *InnerBoundary, *Zero);
+ if (!Cross)
+ return std::unexpected(Cross.error());
+ auto HighIsShort = buildIntegerCompareBefore(
+ Builder, Anchor, IntegerPredicate::UnsignedLessThan, Amount, *InnerBoundary);
+ if (!HighIsShort)
+ return std::unexpected(HighIsShort.error());
+ auto HighShort = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SHL, *Type,
+ Parts[2], Amount);
+ if (!HighShort)
+ return std::unexpected(HighShort.error());
+ auto BoundedHigh = buildSelectBefore(Builder, Anchor, *Type, *HighIsShort,
+ *HighShort, *Zero);
+ if (!BoundedHigh)
+ return std::unexpected(BoundedHigh.error());
+ auto ShortHigh = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_OR, *Type,
+ *Cross, *BoundedHigh);
+ if (!ShortHigh)
+ return std::unexpected(ShortHigh.error());
+
+ auto LongIsShort = buildIntegerCompareBefore(
+ Builder, Anchor, IntegerPredicate::UnsignedLessThan, *OuterExcess,
+ *InnerBoundary);
+ if (!LongIsShort)
+ return std::unexpected(LongIsShort.error());
+ auto LongShift = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SHL, *Type,
+ Parts[0], *OuterExcess);
+ if (!LongShift)
+ return std::unexpected(LongShift.error());
+ auto LongHigh = buildSelectBefore(Builder, Anchor, *Type, *LongIsShort,
+ *LongShift, *Zero);
+ if (!LongHigh)
+ return std::unexpected(LongHigh.error());
+
+ auto ResultLow = buildSelectBefore(Builder, Anchor, *Type, *OuterIsShort,
+ Lower->Low, *Zero);
+ if (!ResultLow)
+ return std::unexpected(ResultLow.error());
+ auto ResultMiddle = buildSelectBefore(Builder, Anchor, *Type, *OuterIsShort,
+ Lower->High, *Zero);
+ if (!ResultMiddle)
+ return std::unexpected(ResultMiddle.error());
+ auto ShiftedHigh = buildSelectBefore(Builder, Anchor, *Type, *OuterIsShort,
+ *ShortHigh, *LongHigh);
+ if (!ShiftedHigh)
+ return std::unexpected(ShiftedHigh.error());
+ auto ResultHigh = buildSelectBefore(Builder, Anchor, *Type, *OuterIsZero,
+ Parts[2], *ShiftedHigh);
+ if (!ResultHigh)
+ return std::unexpected(ResultHigh.error());
+ return std::vector<Register>{*ResultLow, *ResultMiddle, *ResultHigh};
+}
+
+[[nodiscard]] Expected<void> lowerWideShiftLeft(MachineInstr &Instruction,
+ MachineFunction &Function,
+ const TargetDescription &Target) {
+ const MachineOperand *DestinationOperand = Instruction.operandAt(0);
+ const MachineOperand *ValueOperand = Instruction.operandAt(1);
+ const MachineOperand *AmountOperand = Instruction.operandAt(2);
+ const Register *Destination =
+ DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>();
+ const Register *Value = ValueOperand == nullptr ? nullptr : ValueOperand->getIf<Register>();
+ const Register *Amount = AmountOperand == nullptr ? nullptr : AmountOperand->getIf<Register>();
+ if (Destination == nullptr || Value == nullptr || Amount == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide shift has invalid register operands");
+ auto ValueParts = collectWideValueParts(*Value, Function);
+ if (!ValueParts)
+ return std::unexpected(ValueParts.error());
+ auto AmountParts = collectWideValueParts(*Amount, Function);
+ if (!AmountParts)
+ return std::unexpected(AmountParts.error());
+ if (ValueParts->Parts.size() < 2 || AmountParts->Parts.empty())
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide shift has an invalid limb decomposition");
+ auto ResultPlan =
+ wideResultPlan(*Destination, Function, ValueParts->Parts.size());
+ if (!ResultPlan)
+ return std::unexpected(ResultPlan.error());
+
+ MachineIRBuilder Builder(Function, Target);
+ Expected<std::vector<Register>> Results =
+ ValueParts->Parts.size() == 2
+ ? emitTwoLimbLeftShift(Builder, Instruction, ValueParts->Parts[0],
+ ValueParts->Parts[1], AmountParts->Parts[0])
+ .transform([](TwoLimbShiftResult Result) {
+ return std::vector<Register>{Result.Low, Result.High};
+ })
+ : ValueParts->Parts.size() == 3
+ ? emitThreeLimbLeftShift(Builder, Instruction, ValueParts->Parts,
+ AmountParts->Parts[0])
+ : Expected<std::vector<Register>>(std::unexpected(
+ Error(ErrorCode::Unsupported,
+ "RISC-V wide shift currently requires two or three 64-bit limbs")));
+ if (!Results)
+ return std::unexpected(Results.error());
+ for (std::size_t Index = 0; Index < Results->size(); ++Index) {
+ auto Replaced = replaceRegisterUses(Function, ResultPlan->Outputs[Index],
+ (*Results)[Index]);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ }
+ if (auto Erased = eraseInstruction(*ResultPlan->Unmerge); !Erased)
+ return std::unexpected(Erased.error());
+ if (ResultPlan->Extension != nullptr) {
+ if (auto Erased = eraseInstruction(*ResultPlan->Extension); !Erased)
+ return std::unexpected(Erased.error());
+ }
+ if (auto Erased = eraseInstruction(Instruction); !Erased)
+ return std::unexpected(Erased.error());
+ for (std::size_t Index = 1; Index < AmountParts->Parts.size(); ++Index) {
+ const MachineOperand *Definition =
+ Function.registerInfo().getUniqueVRegDef(AmountParts->Parts[Index]);
+ if (Definition != nullptr && Definition->parent() != nullptr)
+ AmountParts->Artifacts.push_back(
+ const_cast<MachineInstr *>(Definition->parent()));
+ }
+ ValueParts->Artifacts.insert(ValueParts->Artifacts.end(),
+ AmountParts->Artifacts.begin(),
+ AmountParts->Artifacts.end());
+ eraseDeadArtifacts(ValueParts->Artifacts, Function);
+ return {};
+}
+
+[[nodiscard]] Expected<std::vector<Register>>
+emitWideAdd(MachineIRBuilder &Builder, MachineInstr &Anchor, std::span<const Register> Left,
+ std::span<const Register> Right) {
+ if (Left.size() != Right.size() || Left.size() < 2)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide add requires matching multi-limb operands");
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ std::vector<Register> Results;
+ Results.reserve(Left.size());
+ auto Low = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_ADD, *Type, Left[0], Right[0]);
+ if (!Low)
+ return std::unexpected(Low.error());
+ auto Carry = buildIntegerCompareBefore(Builder, Anchor, IntegerPredicate::UnsignedLessThan,
+ *Low, Right[0]);
+ if (!Carry)
+ return std::unexpected(Carry.error());
+ auto LowCopy = buildUnaryBefore(Builder, Anchor, GenericOpcode::COPY, *Type, *Low);
+ if (!LowCopy)
+ return std::unexpected(LowCopy.error());
+ Results.push_back(*LowCopy);
+ for (std::size_t Index = 1; Index < Left.size(); ++Index) {
+ auto Base =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_ADD, *Type, Left[Index], Right[Index]);
+ if (!Base)
+ return std::unexpected(Base.error());
+ std::optional<Register> BaseCarry;
+ if (Index + 1U < Left.size()) {
+ auto Compared = buildIntegerCompareBefore(Builder, Anchor,
+ IntegerPredicate::UnsignedLessThan, *Base,
+ Left[Index]);
+ if (!Compared)
+ return std::unexpected(Compared.error());
+ BaseCarry = *Compared;
+ }
+ auto Sum = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_ADD, *Type, *Base, *Carry);
+ if (!Sum)
+ return std::unexpected(Sum.error());
+ if (Index + 1U < Left.size()) {
+ auto Zero = buildIntegerConstantBefore(Builder, Anchor, 64, 0);
+ if (!Zero)
+ return std::unexpected(Zero.error());
+ auto IsZero = buildIntegerCompareBefore(Builder, Anchor, IntegerPredicate::Equal, *Sum, *Zero);
+ if (!IsZero)
+ return std::unexpected(IsZero.error());
+ auto CarryFromIncoming =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_AND, *Type, *IsZero, *Carry);
+ if (!CarryFromIncoming)
+ return std::unexpected(CarryFromIncoming.error());
+ auto NextCarry = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_OR, *Type, *BaseCarry,
+ *CarryFromIncoming);
+ if (!NextCarry)
+ return std::unexpected(NextCarry.error());
+ Carry = *NextCarry;
+ }
+ auto Copy = buildUnaryBefore(Builder, Anchor, GenericOpcode::COPY, *Type, *Sum);
+ if (!Copy)
+ return std::unexpected(Copy.error());
+ Results.push_back(*Copy);
+ }
+ return Results;
+}
+
+[[nodiscard]] Expected<std::vector<Register>>
+emitWideSub(MachineIRBuilder &Builder, MachineInstr &Anchor, std::span<const Register> Left,
+ std::span<const Register> Right) {
+ if (Left.size() != Right.size() || Left.size() < 2)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide sub requires matching multi-limb operands");
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ std::vector<Register> Results;
+ Results.reserve(Left.size());
+ auto Low = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type, Left[0], Right[0]);
+ if (!Low)
+ return std::unexpected(Low.error());
+ Results.push_back(*Low);
+ auto Borrow = buildIntegerCompareBefore(Builder, Anchor, IntegerPredicate::UnsignedLessThan,
+ Left[0], Right[0]);
+ if (!Borrow)
+ return std::unexpected(Borrow.error());
+ for (std::size_t Index = 1; Index < Left.size(); ++Index) {
+ auto Base =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type, Left[Index], Right[Index]);
+ if (!Base)
+ return std::unexpected(Base.error());
+ std::optional<Register> BaseBorrow;
+ if (Index + 1U < Left.size()) {
+ auto Compared = buildIntegerCompareBefore(Builder, Anchor,
+ IntegerPredicate::UnsignedGreaterThan, *Base,
+ Left[Index]);
+ if (!Compared)
+ return std::unexpected(Compared.error());
+ BaseBorrow = *Compared;
+ }
+ auto Difference =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_SUB, *Type, *Base, *Borrow);
+ if (!Difference)
+ return std::unexpected(Difference.error());
+ Results.push_back(*Difference);
+ if (Index + 1U < Left.size()) {
+ auto Zero = buildIntegerConstantBefore(Builder, Anchor, 64, 0);
+ if (!Zero)
+ return std::unexpected(Zero.error());
+ auto IsZero =
+ buildIntegerCompareBefore(Builder, Anchor, IntegerPredicate::Equal, *Base, *Zero);
+ if (!IsZero)
+ return std::unexpected(IsZero.error());
+ auto BorrowFromIncoming =
+ buildBinaryBefore(Builder, Anchor, GenericOpcode::G_AND, *Type, *IsZero, *Borrow);
+ if (!BorrowFromIncoming)
+ return std::unexpected(BorrowFromIncoming.error());
+ auto NextBorrow = buildBinaryBefore(Builder, Anchor, GenericOpcode::G_OR, *Type, *BaseBorrow,
+ *BorrowFromIncoming);
+ if (!NextBorrow)
+ return std::unexpected(NextBorrow.error());
+ Borrow = *NextBorrow;
+ }
+ }
+ return Results;
+}
+
+[[nodiscard]] Expected<std::vector<Register>>
+emitWideBitwise(MachineIRBuilder &Builder, MachineInstr &Anchor, GenericOpcode Generic,
+ std::span<const Register> Left, std::span<const Register> Right) {
+ if (Left.size() != Right.size() || Left.size() < 2)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide bitwise operation requires matching multi-limb operands");
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ std::vector<Register> Results;
+ Results.reserve(Left.size());
+ for (std::size_t Index = 0; Index < Left.size(); ++Index) {
+ auto Part = buildBinaryBefore(Builder, Anchor, Generic, *Type, Left[Index], Right[Index]);
+ if (!Part)
+ return std::unexpected(Part.error());
+ Results.push_back(*Part);
+ }
+ return Results;
+}
+
+[[nodiscard]] Expected<void> lowerWideBinary(MachineInstr &Instruction,
+ MachineFunction &Function,
+ const TargetDescription &Target,
+ GenericOpcode Generic) {
+ const MachineOperand *DestinationOperand = Instruction.operandAt(0);
+ const MachineOperand *LeftOperand = Instruction.operandAt(1);
+ const MachineOperand *RightOperand = Instruction.operandAt(2);
+ 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>();
+ if (Destination == nullptr || Left == nullptr || Right == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide binary instruction has invalid operands");
+ auto LeftParts = collectWideValueParts(*Left, Function);
+ if (!LeftParts)
+ return std::unexpected(LeftParts.error());
+ auto RightParts = collectWideValueParts(*Right, Function);
+ if (!RightParts)
+ return std::unexpected(RightParts.error());
+ if (LeftParts->Parts.size() != RightParts->Parts.size())
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide binary operands have different limb counts");
+ auto ResultPlan = wideResultPlan(*Destination, Function, LeftParts->Parts.size());
+ if (!ResultPlan)
+ return std::unexpected(ResultPlan.error());
+
+ MachineIRBuilder Builder(Function, Target);
+ Expected<std::vector<Register>> Results =
+ Generic == GenericOpcode::G_ADD
+ ? emitWideAdd(Builder, Instruction, LeftParts->Parts, RightParts->Parts)
+ : Generic == GenericOpcode::G_SUB
+ ? emitWideSub(Builder, Instruction, LeftParts->Parts, RightParts->Parts)
+ : emitWideBitwise(Builder, Instruction, Generic, LeftParts->Parts, RightParts->Parts);
+ if (!Results)
+ return std::unexpected(Results.error());
+ for (std::size_t Index = 0; Index < Results->size(); ++Index) {
+ auto Replaced = replaceRegisterUses(Function, ResultPlan->Outputs[Index], (*Results)[Index]);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ }
+ if (auto Erased = eraseInstruction(*ResultPlan->Unmerge); !Erased)
+ return std::unexpected(Erased.error());
+ if (ResultPlan->Extension != nullptr) {
+ if (auto Erased = eraseInstruction(*ResultPlan->Extension); !Erased)
+ return std::unexpected(Erased.error());
+ }
+ if (auto Erased = eraseInstruction(Instruction); !Erased)
+ return std::unexpected(Erased.error());
+ LeftParts->Artifacts.insert(LeftParts->Artifacts.end(), RightParts->Artifacts.begin(),
+ RightParts->Artifacts.end());
+ eraseDeadArtifacts(LeftParts->Artifacts, Function);
+ return {};
+}
+
+[[nodiscard]] bool canLowerNarrowBranchCondition(const MachineInstr &Instruction,
+ const MachineFunction &Function) {
+ if (Instruction.opcode() != opcode(GenericOpcode::G_BRCOND) ||
+ Instruction.operandCount() < 2)
+ return false;
+ const MachineOperand *ConditionOperand = Instruction.operandAt(0);
+ const Register *Condition =
+ ConditionOperand == nullptr ? nullptr : ConditionOperand->getIf<Register>();
+ if (Condition == nullptr || !Condition->isVirtual() ||
+ singleRegisterUse(Function, *Condition) != &Instruction)
+ return false;
+ const auto ConditionInfo = Function.registerInfo().virtualRegisterInfo(*Condition);
+ const MachineOperand *Definition =
+ Function.registerInfo().getUniqueVRegDef(*Condition);
+ const MachineInstr *Truncation =
+ Definition == nullptr ? nullptr : Definition->parent();
+ if (!ConditionInfo || !ConditionInfo->get().Type ||
+ ConditionInfo->get().Type->scalarBitWidth() != 1 || Truncation == nullptr ||
+ Truncation->opcode() != opcode(GenericOpcode::G_TRUNC) ||
+ Truncation->operandCount() != 2)
+ return false;
+ return operandType(Function, *Truncation, 1)
+ .transform([](LLT Type) { return isScalarGPRType(Type) && Type.scalarBitWidth() == 64; })
+ .value_or(false);
+}
+
+[[nodiscard]] Expected<void>
+lowerNarrowBranchCondition(MachineInstr &Instruction, MachineFunction &Function,
+ const TargetDescription &Target) {
+ if (!canLowerNarrowBranchCondition(Instruction, Function))
+ return makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V branch condition is not a narrow truncation");
+ const MachineOperand *ConditionOperand = Instruction.operandAt(0);
+ const Register Condition = *ConditionOperand->getIf<Register>();
+ const MachineOperand *Definition =
+ Function.registerInfo().getUniqueVRegDef(Condition);
+ MachineInstr *Truncation =
+ const_cast<MachineInstr *>(Definition->parent());
+ const Register Source = *Truncation->operandAt(1)->getIf<Register>();
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ MachineIRBuilder Builder(Function, Target);
+ auto One = buildIntegerConstantBefore(Builder, Instruction, 64, 1);
+ if (!One)
+ return std::unexpected(One.error());
+ auto Masked = buildBinaryBefore(Builder, Instruction, GenericOpcode::G_AND, *Type,
+ Source, *One);
+ if (!Masked)
+ return std::unexpected(Masked.error());
+ auto Replaced = Instruction.operandAt(0)->setRegister(*Masked);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ if (Function.registerInfo().useEmpty(Condition))
+ return eraseInstruction(*Truncation);
+ return {};
+}
+
+struct NarrowPhiIncoming final {
+ Register Value;
+ MachineBasicBlock *Block = nullptr;
+ MachineInstr *ReusableTruncation = nullptr;
+};
+
+[[nodiscard]] MachineInstr *firstInstructionAfterLeadingCopies(MachineBasicBlock &Block) {
+ MachineInstr *Instruction = Block.firstInstruction();
+ while (Instruction != nullptr &&
+ Instruction->opcode() == opcode(GenericOpcode::COPY))
+ Instruction = Instruction->next();
+ return Instruction;
+}
+
+[[nodiscard]] Expected<Register>
+buildIncomingConversion(MachineFunction &Function, const TargetDescription &Target,
+ MachineBasicBlock &Block, MachineInstr *Anchor,
+ GenericOpcode Generic, LLT Type, Register Source) {
+ auto Result = Function.registerInfo().createVirtualRegister(Type);
+ if (!Result)
+ return std::unexpected(Result.error());
+ MachineIRBuilder Builder(Function, Target);
+ auto Point = Anchor == nullptr ? Builder.setInsertPoint(Block)
+ : Builder.setInsertPointBefore(*Anchor);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(opcode(Generic));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ if (auto Added = Pending->addDef(*Result); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addUse(Source); !Added)
+ return std::unexpected(Added.error());
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ return *Result;
+}
+
+[[nodiscard]] Expected<void> lowerNarrowPhi(MachineInstr &Instruction,
+ MachineFunction &Function,
+ const TargetDescription &Target) {
+ const std::optional<LLT> OriginalType = resultType(Function, Instruction);
+ const MachineOperand *DestinationOperand = Instruction.operandAt(0);
+ const Register *Destination =
+ DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>();
+ if (!OriginalType || Destination == nullptr || OriginalType->scalarBitWidth() >= 32 ||
+ Instruction.operandCount() < 3 || (Instruction.operandCount() % 2U) == 0)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V narrow PHI has invalid operands");
+ auto LegalType = LLT::makeAnyScalar(32);
+ if (!LegalType)
+ return std::unexpected(LegalType.error());
+
+ std::vector<NarrowPhiIncoming> Incoming;
+ Incoming.reserve((Instruction.operandCount() - 1U) / 2U);
+ for (std::size_t Index = 1; Index < Instruction.operandCount(); Index += 2) {
+ const MachineOperand *ValueOperand = Instruction.operandAt(Index);
+ const Register *Value =
+ ValueOperand == nullptr ? nullptr : ValueOperand->getIf<Register>();
+ const MachineOperand *BlockOperand = Instruction.operandAt(Index + 1U);
+ const auto *Block =
+ BlockOperand == nullptr ? nullptr : BlockOperand->getIf<BasicBlockOperand>();
+ if (Value == nullptr || Block == nullptr || Block->Block == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V narrow PHI has an invalid incoming edge");
+ MachineInstr *Reusable = nullptr;
+ const MachineOperand *Definition =
+ Value->isVirtual() ? Function.registerInfo().getUniqueVRegDef(*Value) : nullptr;
+ MachineInstr *Def =
+ Definition == nullptr ? nullptr : const_cast<MachineInstr *>(Definition->parent());
+ if (Def != nullptr && Def->opcode() == opcode(GenericOpcode::G_TRUNC) &&
+ Def->operandCount() == 2 && singleRegisterUse(Function, *Value) == &Instruction &&
+ operandType(Function, *Def, 1)
+ .transform([](LLT Type) { return Type.scalarBitWidth() >= 32; })
+ .value_or(false)) {
+ Reusable = Def;
+ }
+ Incoming.push_back(
+ NarrowPhiIncoming{.Value = *Value, .Block = Block->Block,
+ .ReusableTruncation = Reusable});
+ }
+ for (NarrowPhiIncoming &Entry : Incoming) {
+ if (Entry.ReusableTruncation == nullptr)
+ continue;
+ const std::size_t Count = static_cast<std::size_t>(std::ranges::count(
+ Incoming, Entry.ReusableTruncation,
+ &NarrowPhiIncoming::ReusableTruncation));
+ if (Count != 1)
+ Entry.ReusableTruncation = nullptr;
+ }
+ for (NarrowPhiIncoming &Entry : Incoming) {
+ if (Entry.ReusableTruncation == nullptr)
+ continue;
+ MachineBasicBlock *OriginalBlock = Entry.ReusableTruncation->parent();
+ if (OriginalBlock == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V narrow PHI input truncation is detached");
+ auto Removed = OriginalBlock->removeInstruction(*Entry.ReusableTruncation);
+ if (!Removed)
+ return std::unexpected(Removed.error());
+ }
+
+ std::vector<std::pair<MachineBasicBlock *, MachineInstr *>> Anchors;
+ for (const NarrowPhiIncoming &Entry : Incoming) {
+ if (std::ranges::find(Anchors, Entry.Block,
+ &decltype(Anchors)::value_type::first) == Anchors.end()) {
+ Anchors.emplace_back(Entry.Block,
+ firstInstructionAfterLeadingCopies(*Entry.Block));
+ }
+ }
+ std::vector<Register> LegalIncoming;
+ LegalIncoming.reserve(Incoming.size());
+ for (NarrowPhiIncoming &Entry : Incoming) {
+ const auto AnchorPosition = std::ranges::find(
+ Anchors, Entry.Block, &decltype(Anchors)::value_type::first);
+ MachineInstr *Anchor = AnchorPosition->second;
+ if (Entry.ReusableTruncation != nullptr) {
+ auto SetType = Function.registerInfo().setType(Entry.Value, *LegalType);
+ if (!SetType)
+ return std::unexpected(SetType.error());
+ auto Inserted = Anchor == nullptr
+ ? Entry.Block->appendInstruction(*Entry.ReusableTruncation)
+ : Entry.Block->insertInstructionBefore(
+ *Anchor, *Entry.ReusableTruncation);
+ if (!Inserted)
+ return std::unexpected(Inserted.error());
+ LegalIncoming.push_back(Entry.Value);
+ continue;
+ }
+ const auto ValueInfo = Function.registerInfo().virtualRegisterInfo(Entry.Value);
+ if (!ValueInfo || !ValueInfo->get().Type)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V narrow PHI input has no scalar type");
+ const std::uint32_t Width = ValueInfo->get().Type->scalarBitWidth();
+ const GenericOpcode Conversion = Width < 32 ? GenericOpcode::G_ANYEXT
+ : Width > 32 ? GenericOpcode::G_TRUNC
+ : GenericOpcode::COPY;
+ auto Converted = buildIncomingConversion(Function, Target, *Entry.Block, Anchor,
+ Conversion, *LegalType, Entry.Value);
+ if (!Converted)
+ return std::unexpected(Converted.error());
+ LegalIncoming.push_back(*Converted);
+ }
+
+ auto LegalResult = Function.registerInfo().createVirtualRegister(*LegalType);
+ if (!LegalResult)
+ return std::unexpected(LegalResult.error());
+ MachineIRBuilder Builder(Function, Target);
+ auto Point = Builder.setInsertPointBefore(Instruction);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(opcode(GenericOpcode::G_PHI));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ if (auto Added = Pending->addDef(*LegalResult); !Added)
+ return std::unexpected(Added.error());
+ for (std::size_t Index = 0; Index < Incoming.size(); ++Index) {
+ if (auto Added = Pending->addUse(LegalIncoming[Index]); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addBasicBlock(*Incoming[Index].Block); !Added)
+ return std::unexpected(Added.error());
+ }
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ auto Replaced = replaceRegisterUses(Function, *Destination, *LegalResult);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ return eraseInstruction(Instruction);
+}
+
+[[nodiscard]] Expected<void> lowerWidePhi(MachineInstr &Instruction,
+ MachineFunction &Function,
+ const TargetDescription &Target) {
+ const MachineOperand *DestinationOperand = Instruction.operandAt(0);
+ const Register *Destination =
+ DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>();
+ if (Destination == nullptr || Instruction.operandCount() < 5 ||
+ (Instruction.operandCount() % 2U) == 0)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide PHI has invalid operands");
+
+ std::vector<WideValueParts> IncomingParts;
+ std::vector<MachineBasicBlock *> IncomingBlocks;
+ for (std::size_t Index = 1; Index < Instruction.operandCount(); Index += 2) {
+ const MachineOperand *ValueOperand = Instruction.operandAt(Index);
+ const Register *Value = ValueOperand == nullptr ? nullptr : ValueOperand->getIf<Register>();
+ const MachineOperand *BlockOperand = Instruction.operandAt(Index + 1U);
+ const auto *Block = BlockOperand == nullptr ? nullptr : BlockOperand->getIf<BasicBlockOperand>();
+ if (Value == nullptr || Block == nullptr || Block->Block == nullptr)
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide PHI has an invalid incoming edge");
+ auto Parts = collectWideValueParts(*Value, Function);
+ if (!Parts)
+ return std::unexpected(Parts.error());
+ if (!IncomingParts.empty() && Parts->Parts.size() != IncomingParts.front().Parts.size())
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V wide PHI incoming values have different limb counts");
+ IncomingParts.push_back(std::move(*Parts));
+ IncomingBlocks.push_back(Block->Block);
+ }
+ const std::size_t PartCount = IncomingParts.front().Parts.size();
+ auto ResultPlan = wideResultPlan(*Destination, Function, PartCount);
+ if (!ResultPlan)
+ return std::unexpected(ResultPlan.error());
+ auto Type = LLT::makeAnyScalar(64);
+ if (!Type)
+ return std::unexpected(Type.error());
+ MachineIRBuilder Builder(Function, Target);
+ std::vector<Register> Results;
+ Results.reserve(PartCount);
+ for (std::size_t PartIndex = 0; PartIndex < PartCount; ++PartIndex) {
+ auto Result = Function.registerInfo().createVirtualRegister(*Type);
+ if (!Result)
+ return std::unexpected(Result.error());
+ auto Point = Builder.setInsertPointBefore(Instruction);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(opcode(GenericOpcode::G_PHI));
+ if (!Pending)
+ return std::unexpected(Pending.error());
+ if (auto Added = Pending->addDef(*Result); !Added)
+ return std::unexpected(Added.error());
+ for (std::size_t IncomingIndex = 0; IncomingIndex < IncomingParts.size();
+ ++IncomingIndex) {
+ if (auto Added = Pending->addUse(IncomingParts[IncomingIndex].Parts[PartIndex]); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = Pending->addBasicBlock(*IncomingBlocks[IncomingIndex]); !Added)
+ return std::unexpected(Added.error());
+ }
+ auto Built = Pending->build();
+ if (!Built)
+ return std::unexpected(Built.error());
+ Results.push_back(*Result);
+ }
+ for (std::size_t Index = 0; Index < PartCount; ++Index) {
+ auto Replaced = replaceRegisterUses(Function, ResultPlan->Outputs[Index], Results[Index]);
+ if (!Replaced)
+ return std::unexpected(Replaced.error());
+ }
+ if (auto Erased = eraseInstruction(*ResultPlan->Unmerge); !Erased)
+ return std::unexpected(Erased.error());
+ if (ResultPlan->Extension != nullptr) {
+ if (auto Erased = eraseInstruction(*ResultPlan->Extension); !Erased)
+ return std::unexpected(Erased.error());
+ }
+ if (auto Erased = eraseInstruction(Instruction); !Erased)
+ return std::unexpected(Erased.error());
+ std::vector<MachineInstr *> Artifacts;
+ for (WideValueParts &Parts : IncomingParts)
+ Artifacts.insert(Artifacts.end(), Parts.Artifacts.begin(), Parts.Artifacts.end());
+ eraseDeadArtifacts(Artifacts, Function);
+ return {};
+}
+
+[[nodiscard]] bool canFoldWideMergeImmediately(const MachineInstr &Instruction,
+ const MachineFunction &Function) {
+ const MachineOperand *LowOperand = Instruction.operandAt(1);
+ const Register *Low = LowOperand == nullptr ? nullptr : LowOperand->getIf<Register>();
+ if (Low == nullptr)
+ return false;
+ const auto LowInfo = Function.registerInfo().virtualRegisterInfo(*Low);
+ if (!LowInfo || !LowInfo->get().Type)
+ return false;
+ const MachineOperand *DestinationOperand = Instruction.operandAt(0);
+ const Register *Destination =
+ DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>();
+ if (Destination == nullptr)
+ return false;
+ auto &RegisterInfo = const_cast<MachineRegisterInfo &>(Function.registerInfo());
+ for (MachineOperand &Use : RegisterInfo.useOperands(*Destination)) {
+ MachineInstr *Trunc = Use.parent();
+ if (Trunc == nullptr || Trunc->opcode() != opcode(GenericOpcode::G_TRUNC) ||
+ Trunc->operandAt(1) != &Use)
+ return false;
+ const std::optional<LLT> Result = resultType(Function, *Trunc);
+ if (!Result || Result->scalarBitWidth() > LowInfo->get().Type->scalarBitWidth())
+ return false;
+ }
+ return true;
+}
+
class RISCVLegalizerInfo final : public TargetLegalizerInfo {
public:
[[nodiscard]] Expected<LegalizationAction> classify(const MachineInstr &Instruction,
@@ -251,17 +2474,26 @@ public:
return LegalizationAction::Legal;
case GenericOpcode::G_BRCOND: {
const std::optional<LLT> Condition = operandType(Function, Instruction, 0);
- return Condition && isI1Type(*Condition) ? LegalizationAction::Legal
- : LegalizationAction::Unsupported;
+ if (!Condition || !isScalarGPRType(*Condition))
+ return LegalizationAction::Unsupported;
+ return canLowerNarrowBranchCondition(Instruction, Function)
+ ? LegalizationAction::Custom
+ : LegalizationAction::Legal;
}
case GenericOpcode::G_PHI: {
- if (!Type || (!isSupportedIntegerType(*Type) && !isRVFloatType(*Type)))
+ if (!Type)
return LegalizationAction::Unsupported;
for (std::size_t Index = 1; Index < Instruction.operandCount(); Index += 2) {
const std::optional<LLT> Incoming = operandType(Function, Instruction, Index);
if (!Incoming || *Incoming != *Type)
return LegalizationAction::Unsupported;
}
+ if ((Type->isInteger() || Type->isAnyScalar()) && Type->scalarBitWidth() > 64)
+ return LegalizationAction::Custom;
+ if ((Type->isInteger() || Type->isAnyScalar()) && Type->scalarBitWidth() < 32)
+ return LegalizationAction::Custom;
+ if (!isSupportedIntegerType(*Type) && !isRVFloatType(*Type))
+ return LegalizationAction::Unsupported;
return LegalizationAction::Legal;
}
case GenericOpcode::G_IMPLICIT_DEF:
@@ -436,18 +2668,75 @@ public:
case GenericOpcode::G_SEXT:
case GenericOpcode::G_ZEXT: {
const std::optional<LLT> Source = operandType(Function, Instruction, 1);
- return Type && Source && isScalarIntegerExtension(*Type, *Source)
- ? LegalizationAction::Legal
- : LegalizationAction::Unsupported;
+ if (!Type || !Source)
+ return LegalizationAction::Unsupported;
+ if (!isScalarIntegerExtension(*Type, *Source)) {
+ const MachineOperand *DestinationOperand = Instruction.operandAt(0);
+ const Register *Destination = DestinationOperand == nullptr
+ ? nullptr
+ : DestinationOperand->getIf<Register>();
+ MachineInstr *OnlyUse = Destination == nullptr
+ ? nullptr
+ : singleUseInstruction(Function, *Destination);
+ return (Type->isInteger() || Type->isAnyScalar()) &&
+ (Source->isInteger() || Source->isAnyScalar()) &&
+ Type->scalarBitWidth() > 64 &&
+ Source->scalarBitWidth() < Type->scalarBitWidth() &&
+ OnlyUse != nullptr &&
+ OnlyUse->opcode() == opcode(GenericOpcode::G_UNMERGE_VALUES)
+ ? LegalizationAction::Legal
+ : LegalizationAction::Unsupported;
+ }
+ const MachineOperand *SourceOperand = Instruction.operandAt(1);
+ const Register *SourceRegister =
+ SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>();
+ const MachineOperand *SourceDefinition =
+ SourceRegister == nullptr || !SourceRegister->isVirtual()
+ ? nullptr
+ : Function.registerInfo().getUniqueVRegDef(*SourceRegister);
+ const MachineInstr *SourceDef =
+ SourceDefinition == nullptr ? nullptr : SourceDefinition->parent();
+ if (SourceDef != nullptr && SourceDef->opcode() == opcode(GenericOpcode::G_CONSTANT))
+ return LegalizationAction::Custom;
+ if (Generic == GenericOpcode::G_ANYEXT &&
+ narrowAnyExtBinaryPlan(Instruction, Function).has_value())
+ return LegalizationAction::Custom;
+ return LegalizationAction::Legal;
}
case GenericOpcode::G_TRUNC: {
const std::optional<LLT> Source = operandType(Function, Instruction, 1);
+ if (Type && Source && (Type->isInteger() || Type->isAnyScalar()) &&
+ (Source->isInteger() || Source->isAnyScalar()) && Type->scalarBitWidth() > 64 &&
+ Source->scalarBitWidth() > Type->scalarBitWidth())
+ return LegalizationAction::Legal;
return Type && Source && isSupportedIntegerScalar(*Type) &&
isSupportedIntegerScalar(*Source) && Type->scalarBitWidth() != 0 &&
Source->scalarBitWidth() >= Type->scalarBitWidth()
? LegalizationAction::Legal
: LegalizationAction::Unsupported;
}
+ case GenericOpcode::G_MERGE_VALUES:
+ case GenericOpcode::G_UNMERGE_VALUES: {
+ if (Instruction.operandCount() < 3)
+ return LegalizationAction::Unsupported;
+ const std::size_t SmallTypeIndex =
+ Generic == GenericOpcode::G_MERGE_VALUES ? 1 : 0;
+ const std::size_t BigTypeIndex = Generic == GenericOpcode::G_MERGE_VALUES
+ ? 0
+ : Instruction.operandCount() - 1U;
+ const std::optional<LLT> SmallType = operandType(Function, Instruction, SmallTypeIndex);
+ const std::optional<LLT> BigType = operandType(Function, Instruction, BigTypeIndex);
+ if (SmallType && BigType && Generic == GenericOpcode::G_MERGE_VALUES &&
+ BigType->scalarBitWidth() > 64 &&
+ !canFoldWideMergeImmediately(Instruction, Function))
+ return LegalizationAction::Legal;
+ return SmallType && BigType &&
+ (SmallType->isInteger() || SmallType->isAnyScalar()) &&
+ (BigType->isInteger() || BigType->isAnyScalar()) &&
+ SmallType->scalarBitWidth() != 0 && BigType->scalarBitWidth() != 0
+ ? LegalizationAction::Custom
+ : LegalizationAction::Unsupported;
+ }
case GenericOpcode::G_SEXT_INREG: {
const std::optional<LLT> Source = operandType(Function, Instruction, 1);
const MachineOperand *WidthOperand = Instruction.operandAt(2);
@@ -467,7 +2756,7 @@ public:
Predicate == nullptr ? nullptr : Predicate->getIf<PredicateOperand>();
const std::optional<LLT> Left = operandType(Function, Instruction, 2);
const std::optional<LLT> Right = operandType(Function, Instruction, 3);
- return Type && isI1Type(*Type) && IntegerPredicate != nullptr &&
+ return Type && isScalarGPRType(*Type) && IntegerPredicate != nullptr &&
IntegerPredicate->integerPredicate() != nullptr && Left && Right &&
*Left == *Right && isSupportedIntegerType(*Left)
? LegalizationAction::Legal
@@ -523,7 +2812,7 @@ public:
const std::optional<LLT> Condition = operandType(Function, Instruction, 1);
const std::optional<LLT> TrueValue = operandType(Function, Instruction, 2);
const std::optional<LLT> FalseValue = operandType(Function, Instruction, 3);
- return Type && Condition && TrueValue && FalseValue && isI1Type(*Condition) &&
+ return Type && Condition && TrueValue && FalseValue && isScalarGPRType(*Condition) &&
*TrueValue == *Type && *FalseValue == *Type &&
(isSupportedIntegerType(*Type) || isRVFloatType(*Type))
? LegalizationAction::Legal
@@ -637,6 +2926,26 @@ public:
? LegalizationAction::Legal
: LegalizationAction::Unsupported;
}
+ case GenericOpcode::G_CONSTANT_POOL: {
+ const MachineOperand *PoolOperand = Instruction.operandAt(1);
+ const auto *Pool = PoolOperand == nullptr ? nullptr : PoolOperand->getIf<ConstantPoolOperand>();
+ return Type && Type->isPointer() && Type->addressSpace() == 0 &&
+ Type->scalarBitWidth() == 64 && Pool != nullptr &&
+ Function.constantPool().entry(Pool->Index) != nullptr
+ ? LegalizationAction::Legal
+ : LegalizationAction::Unsupported;
+ }
+ case GenericOpcode::G_GLOBAL_VALUE: {
+ const MachineOperand *GlobalOperand = Instruction.operandAt(1);
+ const auto *Global = GlobalOperand == nullptr
+ ? nullptr
+ : GlobalOperand->getIf<GlobalSymbolOperand>();
+ return Type && Type->isPointer() && Type->addressSpace() == 0 &&
+ Type->scalarBitWidth() == 64 && Global != nullptr &&
+ !Global->Name.empty()
+ ? LegalizationAction::Legal
+ : LegalizationAction::Unsupported;
+ }
case GenericOpcode::G_BRINDIRECT:
return operandType(Function, Instruction, 0)
.transform([](LLT Value) {
@@ -655,11 +2964,21 @@ public:
: LegalizationAction::Unsupported;
case GenericOpcode::G_CONSTANT: {
const MachineOperand *Immediate = Instruction.operandAt(1);
- return Type && isSupportedIntegerType(*Type) && Immediate != nullptr &&
- (Immediate->kind() == MachineOperand::Kind::Immediate ||
- Immediate->kind() == MachineOperand::Kind::IntegerImmediate)
- ? LegalizationAction::Legal
- : LegalizationAction::Unsupported;
+ if (!Type || Immediate == nullptr ||
+ (Immediate->kind() != MachineOperand::Kind::Immediate &&
+ Immediate->kind() != MachineOperand::Kind::IntegerImmediate))
+ return LegalizationAction::Unsupported;
+ if (Type->scalarBitWidth() > 64)
+ return wideConstantUnmergePlan(Instruction, Function).has_value()
+ ? LegalizationAction::Custom
+ : LegalizationAction::Unsupported;
+ if ((Type->isInteger() || Type->isAnyScalar()) && Type->scalarBitWidth() == 64) {
+ auto Value = signedImmediate(*Immediate);
+ if (Value && shouldUseRV64IntegerConstantPool(*Value))
+ return LegalizationAction::Custom;
+ }
+ return isSupportedIntegerType(*Type) ? LegalizationAction::Legal
+ : LegalizationAction::Unsupported;
}
case GenericOpcode::G_ADD:
case GenericOpcode::G_SUB:
@@ -676,10 +2995,18 @@ public:
case GenericOpcode::G_ASHR: {
const std::optional<LLT> Left = operandType(Function, Instruction, 1);
const std::optional<LLT> Right = operandType(Function, Instruction, 2);
- return Type && Left && Right && isSupportedIntegerScalar(*Type) && *Left == *Type &&
- *Right == *Type
- ? LegalizationAction::Legal
- : LegalizationAction::Unsupported;
+ if (!Type || !Left || !Right || *Left != *Type || *Right != *Type ||
+ (!Type->isInteger() && !Type->isAnyScalar()))
+ return LegalizationAction::Unsupported;
+ if (Type->scalarBitWidth() > 64) {
+ return Generic == GenericOpcode::G_ADD || Generic == GenericOpcode::G_SUB ||
+ Generic == GenericOpcode::G_AND || Generic == GenericOpcode::G_OR ||
+ Generic == GenericOpcode::G_XOR || Generic == GenericOpcode::G_SHL
+ ? LegalizationAction::Custom
+ : LegalizationAction::Unsupported;
+ }
+ return isSupportedIntegerScalar(*Type) ? LegalizationAction::Legal
+ : LegalizationAction::Unsupported;
}
case GenericOpcode::G_PTR_ADD: {
const std::optional<LLT> Pointer = operandType(Function, Instruction, 1);
@@ -713,8 +3040,60 @@ public:
"RISC-V legalizer received an invalid generic opcode value");
}
- [[nodiscard]] Expected<void> lower(MachineInstr &, MachineFunction &, const TargetDescription &,
- LegalizationAction) const override {
+ [[nodiscard]] Expected<void> lower(MachineInstr &Instruction, MachineFunction &Function,
+ const TargetDescription &Target,
+ LegalizationAction Action) const override {
+ if (Action == LegalizationAction::Custom &&
+ resultType(Function, Instruction)
+ .transform([](LLT Type) { return Type.scalarBitWidth() > 64; })
+ .value_or(false)) {
+ const Opcode Value = Instruction.opcode();
+ if (Value == opcode(GenericOpcode::G_ADD) || Value == opcode(GenericOpcode::G_SUB) ||
+ Value == opcode(GenericOpcode::G_AND) || Value == opcode(GenericOpcode::G_OR) ||
+ Value == opcode(GenericOpcode::G_XOR)) {
+ return lowerWideBinary(Instruction, Function, Target,
+ static_cast<GenericOpcode>(Value.value()));
+ }
+ if (Value == opcode(GenericOpcode::G_SHL))
+ return lowerWideShiftLeft(Instruction, Function, Target);
+ if (Value == opcode(GenericOpcode::G_PHI))
+ return lowerWidePhi(Instruction, Function, Target);
+ }
+ if (Action == LegalizationAction::Custom &&
+ Instruction.opcode() == opcode(GenericOpcode::G_MERGE_VALUES))
+ return lowerMergeValues(Instruction, Function, Target);
+ if (Action == LegalizationAction::Custom &&
+ Instruction.opcode() == opcode(GenericOpcode::G_UNMERGE_VALUES))
+ return lowerUnmergeValues(Instruction, Function, Target);
+ if (Action == LegalizationAction::Custom &&
+ Instruction.opcode() == opcode(GenericOpcode::G_BRCOND))
+ return lowerNarrowBranchCondition(Instruction, Function, Target);
+ if (Action == LegalizationAction::Custom &&
+ Instruction.opcode() == opcode(GenericOpcode::G_PHI))
+ return lowerNarrowPhi(Instruction, Function, Target);
+ if (Action == LegalizationAction::Custom &&
+ (Instruction.opcode() == opcode(GenericOpcode::G_ANYEXT) ||
+ Instruction.opcode() == opcode(GenericOpcode::G_SEXT) ||
+ Instruction.opcode() == opcode(GenericOpcode::G_ZEXT))) {
+ if (Instruction.opcode() == opcode(GenericOpcode::G_ANYEXT) &&
+ narrowAnyExtBinaryPlan(Instruction, Function).has_value())
+ return foldNarrowAnyExtBinary(Instruction, Function, Target);
+ return foldConstantExtension(
+ Instruction, Function,
+ static_cast<GenericOpcode>(Instruction.opcode().value()));
+ }
+ if (Action == LegalizationAction::Custom &&
+ Instruction.opcode() == opcode(GenericOpcode::G_CONSTANT)) {
+ const MachineOperand *Immediate = Instruction.operandAt(1);
+ const std::optional<LLT> Type = resultType(Function, Instruction);
+ if (Immediate != nullptr && Type && (Type->isInteger() || Type->isAnyScalar()) &&
+ Type->scalarBitWidth() == 64) {
+ auto Value = signedImmediate(*Immediate);
+ if (Value && shouldUseRV64IntegerConstantPool(*Value))
+ return lowerConstantToPool(Instruction, Function, Target);
+ }
+ return foldWideConstant(Instruction, Function, Target);
+ }
return makeUnexpected(ErrorCode::Unsupported,
"RISC-V legalizer has no lowering rule for this instruction");
}
@@ -786,9 +3165,25 @@ public:
class RISCVInstructionSelector final : public TargetInstructionSelector {
public:
+ [[nodiscard]] Expected<void>
+ constrainSelectedInstruction(MachineInstr &Instruction, MachineFunction &Function,
+ const TargetDescription &Target) const override {
+ return registerBankInfo().constrainInstruction(Instruction, Function, Target);
+ }
+
[[nodiscard]] Expected<void> canSelect(const MachineInstr &Instruction,
const MachineFunction &Function,
const TargetDescription &Target) const override {
+ if (Instruction.opcode() == targetOpcode(InstructionOpcode::G_SLLW)) {
+ const std::optional<LLT> Type = resultType(Function, Instruction);
+ const std::optional<LLT> Left = operandType(Function, Instruction, 1);
+ const std::optional<LLT> Right = operandType(Function, Instruction, 2);
+ return Type && Left && Right && isScalarGPRType(*Type) &&
+ Type->scalarBitWidth() == 64 && *Left == *Type && *Right == *Type
+ ? Expected<void>{}
+ : makeUnexpected(ErrorCode::Unsupported,
+ "RISC-V G_SLLW requires three 64-bit GPR operands");
+ }
if (!isGenericOpcode(Instruction.opcode()))
return {};
const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value());
@@ -938,6 +3333,8 @@ public:
auto Match = canSelect(Instruction, Function, Target);
if (!Match)
return std::unexpected(Match.error());
+ if (Instruction.opcode() == targetOpcode(InstructionOpcode::G_SLLW))
+ return Instruction.setOpcode(targetOpcode(InstructionOpcode::SLLW));
const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value());
const std::optional<LLT> Type = resultType(Function, Instruction);
@@ -2974,6 +5371,7 @@ private:
}
const Register Destination = *DestinationValue;
const std::int64_t Value = *ImmediateValue;
+ const std::uint32_t ImmediateTargetFlags = ImmediateOperand->targetFlags();
const bool IsWord = Type->isInteger() && Type->scalarBitWidth() <= 32;
const InstructionOpcode AddImmediate =
IsWord ? InstructionOpcode::ADDIW : InstructionOpcode::ADDI;
@@ -2986,6 +5384,13 @@ private:
auto Source = insertPhysicalRegister(Instruction, 1, *Zero, RegisterOperandFlag::Use);
if (!Source)
return std::unexpected(Source.error());
+ auto EraseImmediate = Instruction.eraseOperand(2);
+ if (!EraseImmediate)
+ return std::unexpected(EraseImmediate.error());
+ auto NormalizeImmediate = Instruction.insertOperand(
+ 2, MachineOperand::immediate(Value, ImmediateTargetFlags));
+ if (!NormalizeImmediate)
+ return std::unexpected(NormalizeImmediate.error());
return Instruction.setOpcode(targetOpcode(AddImmediate));
}
diff --git a/backend/lib/Target/RISCV/RISCVFunctionInfo.cpp b/backend/lib/Target/RISCV/RISCVFunctionInfo.cpp
new file mode 100644
index 0000000..f25bb8c
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVFunctionInfo.cpp
@@ -0,0 +1,137 @@
+// 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/RISCVImmediateMaterialization.cpp b/backend/lib/Target/RISCV/RISCVImmediateMaterialization.cpp
new file mode 100644
index 0000000..56bce25
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVImmediateMaterialization.cpp
@@ -0,0 +1,148 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "RISCVImmediateMaterialization.h"
+
+#include <algorithm>
+#include <bit>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+
+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 {
+ 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]] std::size_t baseBuildCost(std::int64_t Value) noexcept {
+ const std::uint64_t Bits = static_cast<std::uint64_t>(Value);
+ if (fitsSigned(Value, 32)) {
+ const std::uint64_t High20 = ((Bits + 0x800U) >> 12U) & 0xFFFFFU;
+ const std::int64_t Low12 = signExtend(Bits, 12);
+ return static_cast<std::size_t>(High20 != 0) +
+ static_cast<std::size_t>(Low12 != 0 || High20 == 0);
+ }
+
+ 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 Shift = 0;
+ if (!fitsSigned(Remaining, 32)) {
+ Shift = static_cast<std::uint32_t>(std::countr_zero(WithoutLow12));
+ Remaining >>= Shift;
+
+ if (Shift > 12 && !fitsSigned(Remaining, 12)) {
+ const std::int64_t WithLUILowBits =
+ std::bit_cast<std::int64_t>(static_cast<std::uint64_t>(Remaining) << 12U);
+ if (fitsSigned(WithLUILowBits, 32)) {
+ Shift -= 12;
+ Remaining = WithLUILowBits;
+ }
+ }
+ }
+
+ return baseBuildCost(Remaining) + static_cast<std::size_t>(Shift != 0) +
+ static_cast<std::size_t>(Low12 != 0);
+}
+
+[[nodiscard]] std::optional<std::size_t>
+leadingZeroBuildCost(std::uint64_t Value) noexcept {
+ if (Value == 0 || (Value & (std::uint64_t{1} << 63U)) != 0)
+ return std::nullopt;
+
+ const std::uint32_t LeadingZeros =
+ static_cast<std::uint32_t>(std::countl_zero(Value));
+ const std::uint64_t Shifted = Value << LeadingZeros;
+ const std::uint64_t LowMask = (std::uint64_t{1} << LeadingZeros) - 1U;
+ std::optional<std::size_t> Best;
+ for (const std::uint64_t Candidate : {Shifted | LowMask, Shifted}) {
+ const std::size_t Cost = baseBuildCost(std::bit_cast<std::int64_t>(Candidate));
+ if (Cost >= 8)
+ continue;
+ const std::size_t WithShift = Cost + 1U;
+ Best = Best ? std::min(*Best, WithShift) : WithShift;
+ }
+ return Best;
+}
+
+[[nodiscard]] std::optional<std::size_t>
+twoRegisterBuildCost(std::int64_t Value) noexcept {
+ const std::uint64_t Bits = static_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 Shift = HighZeros - LowZeros;
+ if (Shift >= 64 || HighContribution != (static_cast<std::uint64_t>(Low) << Shift))
+ return std::nullopt;
+ return estimateRV64IntegerBuildCost(Low) + 2U;
+}
+
+} // namespace
+
+std::size_t estimateRV64IntegerBuildCost(std::int64_t Value) noexcept {
+ const std::uint64_t Bits = static_cast<std::uint64_t>(Value);
+ std::size_t Best = baseBuildCost(Value);
+
+ if ((Bits & 0xFFFU) != 0 && (Bits & 1U) == 0 && Best >= 2) {
+ const std::uint32_t TrailingZeros =
+ static_cast<std::uint32_t>(std::countr_zero(Bits));
+ const std::int64_t Shifted = Value >> TrailingZeros;
+ const std::size_t Candidate = baseBuildCost(Shifted) + 1U;
+ if (Candidate < Best || fitsSigned(Shifted, 6))
+ Best = Candidate;
+ }
+ if (Best <= 2)
+ return Best;
+
+ if ((Bits & 0xFFFU) != 0 && (Bits & 0x1800U) == 0x1000U) {
+ const std::int64_t LastAdd =
+ -static_cast<std::int64_t>(0x800U - (Bits & 0xFFFU));
+ const std::uint64_t AdjustedBits = Bits - static_cast<std::uint64_t>(LastAdd);
+ Best = std::min(Best, baseBuildCost(std::bit_cast<std::int64_t>(AdjustedBits)) + 1U);
+ }
+
+ if (Value > 0 && Best > 2) {
+ if (const auto Candidate = leadingZeroBuildCost(Bits); Candidate && *Candidate < Best)
+ Best = *Candidate;
+ } else if (Value < 0 && Best > 3) {
+ if (const auto Candidate = leadingZeroBuildCost(~Bits);
+ Candidate && *Candidate + 1U < Best) {
+ Best = *Candidate + 1U;
+ }
+ }
+ return Best;
+}
+
+bool shouldUseRV64IntegerConstantPool(std::int64_t Value,
+ std::size_t MaximumBuildCost) noexcept {
+ if (fitsSigned(Value, 32) || estimateRV64IntegerBuildCost(Value) <= MaximumBuildCost)
+ return false;
+ const auto TwoRegisterCost = twoRegisterBuildCost(Value);
+ 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..99d3876
--- /dev/null
+++ b/backend/lib/Target/RISCV/RISCVImmediateMaterialization.h
@@ -0,0 +1,22 @@
+// 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>
+
+namespace rcc::riscv {
+
+inline constexpr std::size_t DefaultRV64IntegerBuildCost = 5;
+
+[[nodiscard]] std::size_t estimateRV64IntegerBuildCost(std::int64_t Value) noexcept;
+
+[[nodiscard]] bool
+shouldUseRV64IntegerConstantPool(
+ std::int64_t Value,
+ std::size_t MaximumBuildCost = DefaultRV64IntegerBuildCost) noexcept;
+
+} // namespace rcc::riscv
+
+#endif // RCC_LIB_TARGET_RISCV_RISCVIMMEDIATEMATERIALIZATION_H
diff --git a/backend/lib/Target/RISCV/RISCVPseudoExpansion.cpp b/backend/lib/Target/RISCV/RISCVPseudoExpansion.cpp
index ed73794..8a309e0 100644
--- a/backend/lib/Target/RISCV/RISCVPseudoExpansion.cpp
+++ b/backend/lib/Target/RISCV/RISCVPseudoExpansion.cpp
@@ -473,6 +473,44 @@ copyRegisters(const MachineInstr &Instruction) {
return true;
}
+[[nodiscard]] Expected<bool> expandBranch(MachineInstr &Instruction, MachineFunction &Function,
+ const TargetDescription &Target) {
+ const MachineOperand *TargetOperand = Instruction.operandAt(0);
+ const BasicBlockOperand *TargetBlock =
+ TargetOperand == nullptr ? nullptr : TargetOperand->getIf<BasicBlockOperand>();
+ if (TargetBlock == nullptr || TargetBlock->Block == nullptr) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V PseudoBR has no basic-block target");
+ }
+
+ auto Zero = physical(PhysicalRegisterID::X0);
+ if (!Zero)
+ return std::unexpected(Zero.error());
+ MachineIRBuilder Builder(Function, Target);
+ auto Point = Builder.setInsertPointBefore(Instruction);
+ if (!Point)
+ return std::unexpected(Point.error());
+ auto Pending = Builder.buildInstr(targetOpcode(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(*TargetBlock->Block);
+ if (!Destination)
+ return std::unexpected(Destination.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 = removeOldInstruction(Instruction);
+ if (!Removed)
+ return std::unexpected(Removed.error());
+ return true;
+}
+
class RISCVPseudoExpansionInfo final : public TargetPseudoExpansionInfo {
public:
[[nodiscard]] Expected<bool> preparePostRAPseudo(MachineInstr &Instruction,
@@ -498,6 +536,8 @@ public:
return expandCall(Instruction, Function, Target, true);
if (Instruction.opcode() == targetOpcode(InstructionOpcode::PseudoRET))
return expandReturn(Instruction, Function, Target);
+ if (Instruction.opcode() == targetOpcode(InstructionOpcode::PseudoBR))
+ return expandBranch(Instruction, Function, Target);
return false;
}
};
diff --git a/backend/lib/Target/RISCV/RISCVTarget.cpp b/backend/lib/Target/RISCV/RISCVTarget.cpp
index 8b3741a..aabce94 100644
--- a/backend/lib/Target/RISCV/RISCVTarget.cpp
+++ b/backend/lib/Target/RISCV/RISCVTarget.cpp
@@ -3,6 +3,7 @@
#include "rcc/Target/RISCV/RISCVTarget.h"
#include "rcc/CodeGen/GenericOpcodes.h"
+#include "rcc/Target/RISCV/RISCVFunctionInfo.h"
#include <array>
#include <cassert>
@@ -94,6 +95,7 @@ Expected<TargetMIRTextInfo> createMIRTextInfo() {
return std::unexpected(Registers.error());
TargetMIRTextInfoDesc Description;
appendGeneratedMIRTextInfo(Description);
+ configureFunctionInfoMIRSchema(Description);
return TargetMIRTextInfo::create(std::move(Description), *Registers);
}
@@ -114,6 +116,7 @@ Expected<TargetDescription> createTargetDescription() {
TargetMIRTextInfoDesc MIRText;
appendGeneratedMIRTextInfo(MIRText);
+ configureFunctionInfoMIRSchema(MIRText);
return TargetDescription::create(std::move(*Instructions), std::move(*Registers),
std::move(MIRText));
}
@@ -121,6 +124,7 @@ Expected<TargetDescription> createTargetDescription() {
std::shared_ptr<const RegisterMask> createCallPreservedMask() {
TargetMIRTextInfoDesc Description;
appendGeneratedMIRTextInfo(Description);
+ configureFunctionInfoMIRSchema(Description);
assert(!Description.RegisterMasks.empty());
return Description.RegisterMasks.front().Mask;
}