aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/Target/RISCVCodeGenTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'backend/unittests/Target/RISCVCodeGenTest.cpp')
-rw-r--r--backend/unittests/Target/RISCVCodeGenTest.cpp1764
1 files changed, 0 insertions, 1764 deletions
diff --git a/backend/unittests/Target/RISCVCodeGenTest.cpp b/backend/unittests/Target/RISCVCodeGenTest.cpp
deleted file mode 100644
index 7392f3b..0000000
--- a/backend/unittests/Target/RISCVCodeGenTest.cpp
+++ /dev/null
@@ -1,1764 +0,0 @@
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include "RISCV.h"
-#include "rcc/CodeGen/GenericOpcodes.h"
-#include "rcc/CodeGen/GlobalISel/InstructionSelector.h"
-#include "rcc/CodeGen/GlobalISel/LegalizerInfo.h"
-#include "rcc/CodeGen/MachineBasicBlock.h"
-#include "rcc/CodeGen/MachineFunction.h"
-#include "rcc/CodeGen/MachineIRBuilder.h"
-#include "rcc/CodeGen/MachineInstr.h"
-#include "rcc/CodeGen/MachineMemOperand.h"
-#include "rcc/CodeGen/MachineOperand.h"
-#include "rcc/Target/RISCV/RISCVSubtarget.h"
-#include "rcc/Target/RISCV/RISCVTarget.h"
-
-#include <array>
-#include <cstdint>
-#include <iostream>
-#include <memory>
-#include <span>
-#include <string>
-#include <string_view>
-#include <vector>
-
-namespace {
-
-struct RISCVGISelTestComponents final {
- rcc::riscv::RISCVSubtarget Subtarget{rcc::riscv::RISCVSubtargetFeatures::rv64gc()};
- const rcc::LegalizerInfo &Legalizer{*Subtarget.getLegalizerInfo()};
- const rcc::InstructionSelector *Selector{Subtarget.getInstructionSelector()};
-};
-
-const RISCVGISelTestComponents GISel;
-
-class TestContext final {
-public:
- void expect(bool Condition, std::string_view Message) {
- if (Condition)
- return;
- std::cerr << Message << '\n';
- ++Failures;
- }
-
- [[nodiscard]] int result() const noexcept {
- return Failures == 0 ? 0 : 1;
- }
-
-private:
- int Failures = 0;
-};
-
-struct InstructionFixture final {
- std::unique_ptr<rcc::MachineFunction> Function;
- rcc::MachineInstr *Instruction = nullptr;
- rcc::Register Result;
-};
-
-[[nodiscard]] InstructionFixture createBinary(TestContext &Context,
- const rcc::TargetDescription &Target,
- rcc::GenericOpcode Generic, std::uint32_t Bits) {
- auto Function =
- rcc::MachineFunction::create("riscv_codegen", rcc::MachineFunctionStableID::fromValue(Bits));
- Context.expect(Function.has_value(), "RISC-V codegen function creation must succeed");
- if (!Function)
- return {};
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return {};
- auto Type = rcc::LowLevelType::makeCategoryNeutralScalar(Bits);
- if (!Type)
- return {};
- auto Left = (*Function)->registerInfo().createVirtualRegister(*Type);
- auto Right = (*Function)->registerInfo().createVirtualRegister(*Type);
- auto Result = (*Function)->registerInfo().createVirtualRegister(*Type);
- if (!Left || !Right || !Result)
- return {};
- rcc::MachineIRBuilder Builder(**Function, Target);
- if (!Builder.setInsertPoint(**Block))
- return {};
- auto Pending = Builder.buildInstr(rcc::opcode(Generic));
- if (!Pending || !Pending->addDef(*Result) || !Pending->addUse(*Left) || !Pending->addUse(*Right))
- return {};
- auto Built = Pending->build();
- if (!Built)
- return {};
- return {std::move(*Function), *Built, *Result};
-}
-
-[[nodiscard]] bool constrainRegister(rcc::MachineFunction &Function, rcc::Register Reg,
- rcc::LowLevelType Type, bool Floating = false) {
- const auto Bank = rcc::RegisterBankID::fromValue(static_cast<std::uint32_t>(
- Floating ? rcc::riscv::RegisterBank::FPR : rcc::riscv::RegisterBank::GPR));
- const auto Class = rcc::RegisterClassID::fromValue(static_cast<std::uint32_t>(
- Floating ? (Type.scalarBitWidth() == 32 ? rcc::riscv::RegisterClass::FPR32
- : rcc::riscv::RegisterClass::FPR64)
- : rcc::riscv::RegisterClass::GPR));
- return Function.registerInfo().setRegisterBank(Reg, Bank).has_value() &&
- Function.registerInfo().setRegisterClass(Reg, Class).has_value();
-}
-
-[[nodiscard]] bool constrainVirtualRegisters(rcc::MachineFunction &Function,
- bool FloatingValues = false) {
- const auto Registers = Function.registerInfo().virtualRegisters();
- for (std::size_t Index = 0; Index < Registers.size(); ++Index) {
- const rcc::VirtualRegisterInfo &Info = Registers[Index];
- if (Info.IsTombstone)
- continue;
- auto Reg = rcc::Register::makeVirtual(Index);
- if (!Reg || !Info.Type ||
- !constrainRegister(Function, *Reg, *Info.Type,
- FloatingValues && Info.Type->scalarBitWidth() != 1))
- return false;
- }
- return true;
-}
-
-[[nodiscard]] bool validatesSelectedFunction(const rcc::TargetDescription &Target,
- const rcc::MachineFunction &Function) {
- for (const rcc::MachineInstr *Instruction : Function.allInstructions()) {
- if (rcc::isGenericOpcode(Instruction->opcode()) ||
- !Target.validateInstruction(*Instruction, Function.registerInfo())) {
- return false;
- }
- }
- return Target.validateRegisterInfo(Function.registerInfo()).has_value();
-}
-
-[[nodiscard]] std::unique_ptr<rcc::MachineFunction>
-createUnaryFunction(TestContext &Context, const rcc::TargetDescription &Target,
- rcc::GenericOpcode Generic, rcc::LowLevelType DestinationType,
- rcc::LowLevelType SourceType, bool FloatingDestination = false,
- bool FloatingSource = false, rcc::Register *DestinationOut = nullptr,
- rcc::Register *SourceOut = nullptr) {
- auto Function = rcc::MachineFunction::create(
- "riscv_unary",
- rcc::MachineFunctionStableID::fromValue(200 + static_cast<std::uint32_t>(Generic)));
- Context.expect(Function.has_value(), "unary test function creation must succeed");
- if (!Function)
- return nullptr;
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return nullptr;
- auto Destination = (*Function)->registerInfo().createVirtualRegister(DestinationType);
- auto Source = (*Function)->registerInfo().createVirtualRegister(SourceType);
- if (!Destination || !Source)
- return nullptr;
- if (!constrainRegister(**Function, *Destination, DestinationType, FloatingDestination) ||
- !constrainRegister(**Function, *Source, SourceType, FloatingSource))
- return nullptr;
- rcc::MachineIRBuilder Builder(**Function, Target);
- if (!Builder.setInsertPoint(**Block))
- return nullptr;
- auto Pending = Builder.buildInstr(rcc::opcode(Generic));
- if (!Pending || !Pending->addDef(*Destination) || !Pending->addUse(*Source) || !Pending->build())
- return nullptr;
- if (DestinationOut != nullptr)
- *DestinationOut = *Destination;
- if (SourceOut != nullptr)
- *SourceOut = *Source;
- return std::move(*Function);
-}
-
-[[nodiscard]] std::unique_ptr<rcc::MachineFunction>
-createCompareFunction(TestContext &Context, const rcc::TargetDescription &Target,
- rcc::GenericOpcode Generic, rcc::LowLevelType ValueType, bool Floating,
- rcc::IntegerPredicate IntegerCondition,
- rcc::FloatingPredicate FloatingCondition) {
- auto Function = rcc::MachineFunction::create(
- "riscv_compare",
- rcc::MachineFunctionStableID::fromValue(500 + static_cast<std::uint32_t>(Generic)));
- Context.expect(Function.has_value(), "comparison test function creation must succeed");
- if (!Function)
- return nullptr;
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return nullptr;
- auto ResultType = rcc::LowLevelType::makeCategoryNeutralScalar(1);
- if (!ResultType)
- return nullptr;
- auto Result = (*Function)->registerInfo().createVirtualRegister(*ResultType);
- auto Left = (*Function)->registerInfo().createVirtualRegister(ValueType);
- auto Right = (*Function)->registerInfo().createVirtualRegister(ValueType);
- if (!Result || !Left || !Right || !constrainRegister(**Function, *Result, *ResultType) ||
- !constrainRegister(**Function, *Left, ValueType, Floating) ||
- !constrainRegister(**Function, *Right, ValueType, Floating))
- return nullptr;
- rcc::MachineIRBuilder Builder(**Function, Target);
- if (!Builder.setInsertPoint(**Block))
- return nullptr;
- auto Pending = Builder.buildInstr(rcc::opcode(Generic));
- if (!Pending || !Pending->addDef(*Result))
- return nullptr;
- if (Floating) {
- if (!Pending->addFloatingPredicate(FloatingCondition))
- return nullptr;
- } else if (!Pending->addIntegerPredicate(IntegerCondition)) {
- return nullptr;
- }
- if (!Pending->addUse(*Left) || !Pending->addUse(*Right) || !Pending->build())
- return nullptr;
- return std::move(*Function);
-}
-
-[[nodiscard]] std::unique_ptr<rcc::MachineFunction>
-createFloatBinaryFunction(TestContext &Context, const rcc::TargetDescription &Target,
- rcc::GenericOpcode Generic, rcc::LowLevelType Type) {
- auto Function = rcc::MachineFunction::create(
- "riscv_float_binary",
- rcc::MachineFunctionStableID::fromValue(700 + static_cast<std::uint32_t>(Generic)));
- Context.expect(Function.has_value(), "floating binary test function creation must succeed");
- if (!Function)
- return nullptr;
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return nullptr;
- auto Left = (*Function)->registerInfo().createVirtualRegister(Type);
- auto Right = (*Function)->registerInfo().createVirtualRegister(Type);
- auto Result = (*Function)->registerInfo().createVirtualRegister(Type);
- if (!Left || !Right || !Result || !constrainRegister(**Function, *Left, Type, true) ||
- !constrainRegister(**Function, *Right, Type, true) ||
- !constrainRegister(**Function, *Result, Type, true))
- return nullptr;
- rcc::MachineIRBuilder Builder(**Function, Target);
- if (!Builder.setInsertPoint(**Block))
- return nullptr;
- auto Pending = Builder.buildInstr(rcc::opcode(Generic));
- if (!Pending || !Pending->addDef(*Result) || !Pending->addUse(*Left) ||
- !Pending->addUse(*Right) || !Pending->build())
- return nullptr;
- return std::move(*Function);
-}
-
-[[nodiscard]] std::unique_ptr<rcc::MachineFunction> createRegisterOnlyFunction(
- TestContext &Context, const rcc::TargetDescription &Target, rcc::GenericOpcode Generic,
- std::initializer_list<rcc::LowLevelType> DefinitionTypes,
- std::initializer_list<rcc::LowLevelType> UseTypes, bool FloatingValues = false) {
- auto Function = rcc::MachineFunction::create(
- "riscv_register_only",
- rcc::MachineFunctionStableID::fromValue(1000 + static_cast<std::uint32_t>(Generic)));
- Context.expect(Function.has_value(), "register-only test function creation must succeed");
- if (!Function)
- return nullptr;
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return nullptr;
- rcc::MachineIRBuilder Builder(**Function, Target);
- if (!Builder.setInsertPoint(**Block))
- return nullptr;
- auto Pending = Builder.buildInstr(rcc::opcode(Generic));
- if (!Pending)
- return nullptr;
- for (rcc::LowLevelType Type : DefinitionTypes) {
- auto Reg = (*Function)->registerInfo().createVirtualRegister(Type);
- if (!Reg || !Pending->addDef(*Reg))
- return nullptr;
- }
- for (rcc::LowLevelType Type : UseTypes) {
- auto Reg = (*Function)->registerInfo().createVirtualRegister(Type);
- if (!Reg || !Pending->addUse(*Reg))
- return nullptr;
- }
- if (!Pending->build() || !constrainVirtualRegisters(**Function, FloatingValues))
- return nullptr;
- return std::move(*Function);
-}
-
-void testClassification(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(), "RV64GC target must be available for codegen tests");
- if (!Target)
- return;
- auto Add32 = createBinary(Context, *Target, rcc::GenericOpcode::G_ADD, 32);
- auto Add64 = createBinary(Context, *Target, rcc::GenericOpcode::G_ADD, 64);
- Context.expect(Add32.Instruction != nullptr && Add64.Instruction != nullptr,
- "RISC-V integer test instructions must be constructible");
- if (Add32.Instruction == nullptr || Add64.Instruction == nullptr)
- return;
- auto Add32Action = GISel.Legalizer.classify(*Add32.Instruction, *Add32.Function, *Target);
- auto Add64Action = GISel.Legalizer.classify(*Add64.Instruction, *Add64.Function, *Target);
- Context.expect(Add32Action && *Add32Action == rcc::LegalizationAction::Legal,
- "RV64GC legalizer must accept i32 arithmetic");
- Context.expect(Add64Action && *Add64Action == rcc::LegalizationAction::Legal,
- "RV64GC legalizer must accept i64 arithmetic");
- Context.expect(
- (*GISel.Selector).canSelect(*Add32.Instruction, *Add32.Function, *Target).has_value(),
- "RV64GC selector must pre-match i32 arithmetic");
- Context.expect(
- (*GISel.Selector).canSelect(*Add64.Instruction, *Add64.Function, *Target).has_value(),
- "RV64GC selector must pre-match i64 arithmetic");
- Context.expect((*GISel.Selector).select(*Add32.Instruction, *Add32.Function, *Target).has_value(),
- "RV64GC selector must lower i32 arithmetic");
- Context.expect(Add32.Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::ADDW)),
- "i32 G_ADD must select ADDW");
- Context.expect((*GISel.Selector).select(*Add64.Instruction, *Add64.Function, *Target).has_value(),
- "RV64GC selector must lower i64 arithmetic");
- Context.expect(Add64.Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::ADD)),
- "i64 G_ADD must select ADD");
-
- auto FloatFunction = rcc::MachineFunction::create("riscv_float_constants",
- rcc::MachineFunctionStableID::fromValue(3));
- Context.expect(FloatFunction.has_value(), "floating constant function creation must succeed");
- if (!FloatFunction)
- return;
- auto FloatBlock = (*FloatFunction)->createBasicBlock();
- Context.expect(FloatBlock.has_value(), "floating constant block creation must succeed");
- if (!FloatBlock || !(*FloatFunction)->appendBlock(**FloatBlock))
- return;
- auto F32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- auto F64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- Context.expect(F32.has_value() && F64.has_value(),
- "RV64GC floating constant types must be constructible");
- if (!F32 || !F64)
- return;
- auto F32Result = (*FloatFunction)->registerInfo().createVirtualRegister(*F32);
- auto F64Result = (*FloatFunction)->registerInfo().createVirtualRegister(*F64);
- Context.expect(F32Result.has_value() && F64Result.has_value(),
- "RV64GC floating constant registers must be constructible");
- if (!F32Result || !F64Result)
- return;
- Context.expect((*FloatFunction)
- ->registerInfo()
- .setRegisterBank(*F32Result,
- rcc::RegisterBankID::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::RegisterBank::FPR)))
- .has_value() &&
- (*FloatFunction)
- ->registerInfo()
- .setRegisterClass(
- *F32Result, rcc::RegisterClassID::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::RegisterClass::FPR32)))
- .has_value(),
- "f32 destination must have an FPR32 bank and class");
- Context.expect((*FloatFunction)
- ->registerInfo()
- .setRegisterBank(*F64Result,
- rcc::RegisterBankID::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::RegisterBank::FPR)))
- .has_value() &&
- (*FloatFunction)
- ->registerInfo()
- .setRegisterClass(
- *F64Result, rcc::RegisterClassID::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::RegisterClass::FPR64)))
- .has_value(),
- "f64 destination must have an FPR64 bank and class");
- rcc::MachineIRBuilder FloatBuilder(**FloatFunction, *Target);
- if (!FloatBuilder.setInsertPoint(**FloatBlock))
- return;
- const std::array<std::uint64_t, 1> SingleWords{0x3f800000U};
- const std::array<std::uint64_t, 1> DoubleWords{0x3ff0000000000000ULL};
- auto SingleBits = rcc::APFloat::create(rcc::FloatSemantics::IEEESingle, SingleWords);
- auto DoubleBits = rcc::APFloat::create(rcc::FloatSemantics::IEEEDouble, DoubleWords);
- Context.expect(SingleBits.has_value() && DoubleBits.has_value(),
- "floating constant payloads must be constructible");
- if (!SingleBits || !DoubleBits)
- return;
- auto SinglePending = FloatBuilder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_FCONSTANT));
- auto DoublePending = FloatBuilder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_FCONSTANT));
- Context.expect(SinglePending.has_value() && DoublePending.has_value(),
- "floating constant instructions must be constructible");
- if (!SinglePending || !DoublePending)
- return;
- Context.expect(SinglePending->addDef(*F32Result).has_value() &&
- SinglePending->addFloatingImmediate(*SingleBits).has_value() &&
- SinglePending->build().has_value(),
- "f32 floating constant must be buildable");
- Context.expect(DoublePending->addDef(*F64Result).has_value() &&
- DoublePending->addFloatingImmediate(*DoubleBits).has_value() &&
- DoublePending->build().has_value(),
- "f64 floating constant must be buildable");
- if (FloatFunction->get()->firstBlock()->firstInstruction() == nullptr)
- return;
- auto Instructions = FloatFunction->get()->allInstructions();
- auto It = Instructions.begin();
- rcc::MachineInstr *F32Instruction = It == Instructions.end() ? nullptr : *It;
- if (It != Instructions.end())
- ++It;
- rcc::MachineInstr *F64Instruction = It == Instructions.end() ? nullptr : *It;
- Context.expect(F32Instruction != nullptr && F64Instruction != nullptr,
- "floating constant instructions must remain in layout order");
- if (F32Instruction == nullptr || F64Instruction == nullptr)
- return;
- auto F32Action = GISel.Legalizer.classify(*F32Instruction, **FloatFunction, *Target);
- auto F64Action = GISel.Legalizer.classify(*F64Instruction, **FloatFunction, *Target);
- Context.expect(F32Action && *F32Action == rcc::LegalizationAction::Legal,
- "RV64GC legalizer must accept f32 constants");
- Context.expect(F64Action && *F64Action == rcc::LegalizationAction::Legal,
- "RV64GC legalizer must accept f64 constants");
- auto F32Selection = (*GISel.Selector).select(*F32Instruction, **FloatFunction, *Target);
- auto F64Selection = (*GISel.Selector).select(*F64Instruction, **FloatFunction, *Target);
- Context.expect(F32Selection.has_value(), "RV64GC selector must lower f32 constants");
- Context.expect(F64Selection.has_value(), "RV64GC selector must lower f64 constants");
- bool HasFMVWX = false;
- bool HasFMVDX = false;
- for (const rcc::MachineInstr *Instruction : FloatFunction->get()->allInstructions()) {
- HasFMVWX |=
- Instruction->opcode() ==
- rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::FMV_W_X));
- HasFMVDX |=
- Instruction->opcode() ==
- rcc::Opcode::fromValue(static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::FMV_D_X));
- }
- Context.expect(HasFMVWX, "f32 constants must use FMV.W.X materialization");
- Context.expect(HasFMVDX, "f64 constants must use FMV.D.X materialization");
-}
-
-void testNarrowIntegerSelection(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- if (!Target)
- return;
- struct BinaryCase final {
- rcc::GenericOpcode Generic;
- std::uint32_t Bits;
- rcc::riscv::InstructionOpcode RequiredOpcode;
- };
- const std::array Cases{
- BinaryCase{rcc::GenericOpcode::G_ADD, 1, rcc::riscv::InstructionOpcode::ADDW},
- BinaryCase{rcc::GenericOpcode::G_UDIV, 8, rcc::riscv::InstructionOpcode::DIVUW},
- BinaryCase{rcc::GenericOpcode::G_ASHR, 16, rcc::riscv::InstructionOpcode::SRAW},
- BinaryCase{rcc::GenericOpcode::G_SMIN, 24, rcc::riscv::InstructionOpcode::SLT},
- };
- for (const BinaryCase &TestCase : Cases) {
- auto Fixture = createBinary(Context, *Target, TestCase.Generic, TestCase.Bits);
- if (!Fixture.Function || Fixture.Instruction == nullptr ||
- !constrainVirtualRegisters(*Fixture.Function)) {
- Context.expect(false, "narrow integer fixture must be constructible");
- continue;
- }
- auto Action = GISel.Legalizer.classify(*Fixture.Instruction, *Fixture.Function, *Target);
- Context.expect(Action && *Action == rcc::LegalizationAction::Legal,
- "i1-i24 scalar operations must be legal on RV64GC GPR containers");
- auto Selection = (*GISel.Selector).select(*Fixture.Instruction, *Fixture.Function, *Target);
- Context.expect(Selection.has_value(), "narrow integer operation must select");
- bool FoundRequiredOpcode = false;
- for (const rcc::MachineInstr *Instruction : Fixture.Function->allInstructions()) {
- FoundRequiredOpcode |=
- Instruction->opcode() ==
- rcc::Opcode::fromValue(static_cast<std::uint32_t>(TestCase.RequiredOpcode));
- }
- Context.expect(FoundRequiredOpcode,
- "narrow integer selection must use the required RV64 instruction");
- Context.expect(Selection && validatesSelectedFunction(*Target, *Fixture.Function),
- "narrow integer selection must satisfy target descriptors");
- }
-
- auto I7 = rcc::LowLevelType::makeCategoryNeutralScalar(7);
- auto I24 = rcc::LowLevelType::makeCategoryNeutralScalar(24);
- if (!I7 || !I24)
- return;
- auto Compare = createCompareFunction(Context, *Target, rcc::GenericOpcode::G_ICMP, *I7, false,
- rcc::IntegerPredicate::SignedLessThan,
- rcc::FloatingPredicate::AlwaysFalse);
- if (Compare && Compare->firstBlock()->firstInstruction() != nullptr) {
- rcc::MachineInstr *Instruction = Compare->firstBlock()->firstInstruction();
- auto Action = GISel.Legalizer.classify(*Instruction, *Compare, *Target);
- auto Lowered = Action ? GISel.Legalizer.lower(*Instruction, *Compare, *Target, *Action)
- : rcc::Expected<void>(std::unexpected(Action.error()));
- auto LegalAction = GISel.Legalizer.classify(*Instruction, *Compare, *Target);
- bool Selected = Lowered && LegalAction && *LegalAction == rcc::LegalizationAction::Legal &&
- constrainVirtualRegisters(*Compare);
- const auto Instructions = Compare->allInstructions();
- for (rcc::MachineInstr *Candidate : Instructions) {
- if (Selected && rcc::isGenericOpcode(Candidate->opcode()))
- Selected = (*GISel.Selector).select(*Candidate, *Compare, *Target).has_value();
- }
- Context.expect(Action && *Action == rcc::LegalizationAction::Custom && Selected,
- "signed i7 comparisons must legalize to RV64 containers and select");
- Context.expect(Selected && validatesSelectedFunction(*Target, *Compare),
- "selected i7 comparison must satisfy target descriptors");
- }
-
- auto Extension = createUnaryFunction(Context, *Target, rcc::GenericOpcode::G_SEXT, *I24, *I7);
- if (Extension && Extension->firstBlock()->firstInstruction() != nullptr) {
- rcc::MachineInstr *Instruction = Extension->firstBlock()->firstInstruction();
- auto Action = GISel.Legalizer.classify(*Instruction, *Extension, *Target);
- auto Selection = (*GISel.Selector).select(*Instruction, *Extension, *Target);
- Context.expect(Action && *Action == rcc::LegalizationAction::Legal && Selection,
- "i7-to-i24 sign extension must select through a 32-bit container");
- Context.expect(Selection && validatesSelectedFunction(*Target, *Extension),
- "selected narrow extension must satisfy target descriptors");
- }
-
- auto UnsupportedMulHigh = createBinary(Context, *Target, rcc::GenericOpcode::G_SMULH, 16);
- if (UnsupportedMulHigh.Function && UnsupportedMulHigh.Instruction != nullptr) {
- auto Action = GISel.Legalizer.classify(*UnsupportedMulHigh.Instruction,
- *UnsupportedMulHigh.Function, *Target);
- Context.expect(Action && *Action == rcc::LegalizationAction::Unsupported,
- "non-native multiply-high widths must be explicitly unsupported");
- }
-}
-
-void testScalarSelection(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(), "RV64GC target must be available for scalar selection");
- if (!Target)
- return;
-
- auto I32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- auto F32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- auto F64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- auto I1 = rcc::LowLevelType::makeCategoryNeutralScalar(1);
- Context.expect(I32 && I64 && F32 && F64 && I1, "scalar test types must be constructible");
- if (!I32 || !I64 || !F32 || !F64 || !I1)
- return;
-
- auto AnyExt = createUnaryFunction(Context, *Target, rcc::GenericOpcode::G_ANYEXT, *I64, *I32);
- auto Sext = createUnaryFunction(Context, *Target, rcc::GenericOpcode::G_SEXT, *I64, *I32);
- auto Zext = createUnaryFunction(Context, *Target, rcc::GenericOpcode::G_ZEXT, *I64, *I32);
- auto Trunc = createUnaryFunction(Context, *Target, rcc::GenericOpcode::G_TRUNC, *I32, *I64);
- for (const auto &Entry : {std::pair{AnyExt.get(), rcc::GenericOpcode::G_ANYEXT},
- std::pair{Sext.get(), rcc::GenericOpcode::G_SEXT},
- std::pair{Zext.get(), rcc::GenericOpcode::G_ZEXT},
- std::pair{Trunc.get(), rcc::GenericOpcode::G_TRUNC}}) {
- Context.expect(Entry.first != nullptr, "integer extension test instruction must exist");
- if (Entry.first == nullptr)
- continue;
- auto *Instruction = Entry.first->firstBlock()->firstInstruction();
- auto Selection = (*GISel.Selector).select(*Instruction, *Entry.first, *Target);
- const std::string_view Name = Entry.second == rcc::GenericOpcode::G_ANYEXT ? "anyext"
- : Entry.second == rcc::GenericOpcode::G_SEXT ? "sext"
- : Entry.second == rcc::GenericOpcode::G_ZEXT ? "zext"
- : "trunc";
- Context.expect(Selection.has_value(),
- std::string("RV64GC ") + std::string(Name) + " must select");
- if (!Selection)
- continue;
- if (Entry.second == rcc::GenericOpcode::G_ANYEXT || Entry.second == rcc::GenericOpcode::G_TRUNC)
- Context.expect(Instruction->opcode() == rcc::opcode(rcc::GenericOpcode::COPY),
- "anyext/trunc must select COPY");
- else
- Context.expect(!rcc::isGenericOpcode(Instruction->opcode()),
- "integer extension must not remain generic");
- }
- if (Zext) {
- Context.expect(Zext->allInstructions().size() == 2,
- "zero extension must emit a two-instruction RV64 sequence");
- if (Zext->allInstructions().size() == 2) {
- auto It = Zext->allInstructions().begin();
- Context.expect((*It)->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::SLLI)),
- "zero extension must shift left first");
- ++It;
- Context.expect((*It)->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::SRLI)),
- "zero extension must shift right logically second");
- }
- }
-
- auto FPExt =
- createUnaryFunction(Context, *Target, rcc::GenericOpcode::G_FPEXT, *F64, *F32, true, true);
- auto FPTrunc =
- createUnaryFunction(Context, *Target, rcc::GenericOpcode::G_FPTRUNC, *F32, *F64, true, true);
- auto FPToSI =
- createUnaryFunction(Context, *Target, rcc::GenericOpcode::G_FPTOSI, *I64, *F32, false, true);
- auto SIToFP =
- createUnaryFunction(Context, *Target, rcc::GenericOpcode::G_SITOFP, *F64, *I32, true, false);
- if (FPExt) {
- auto *Instruction = FPExt->firstBlock()->firstInstruction();
- Context.expect((*GISel.Selector).select(*Instruction, *FPExt, *Target).has_value(),
- "fpext must select");
- Context.expect(Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::FCVT_D_S)),
- "fpext must select FCVT.D.S");
- }
- if (FPTrunc) {
- auto *Instruction = FPTrunc->firstBlock()->firstInstruction();
- Context.expect((*GISel.Selector).select(*Instruction, *FPTrunc, *Target).has_value(),
- "fptrunc must select");
- Context.expect(Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::FCVT_S_D)),
- "fptrunc must select FCVT.S.D");
- }
- if (FPToSI) {
- auto *Instruction = FPToSI->firstBlock()->firstInstruction();
- Context.expect((*GISel.Selector).select(*Instruction, *FPToSI, *Target).has_value(),
- "fptosi must select");
- Context.expect(Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::FCVT_L_S)),
- "fptosi must select FCVT.L.S");
- }
- if (SIToFP) {
- auto *Instruction = SIToFP->firstBlock()->firstInstruction();
- Context.expect((*GISel.Selector).select(*Instruction, *SIToFP, *Target).has_value(),
- "sitofp must select");
- Context.expect(Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::FCVT_D_W)),
- "sitofp must select FCVT.D.W");
- }
-}
-
-void testComparisonsAndControl(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(), "RV64GC target must be available for comparison selection");
- if (!Target)
- return;
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- auto F32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- auto Pointer = rcc::LowLevelType::makePointer(0, 64);
- if (!I64 || !F32 || !Pointer)
- return;
-
- auto Equal =
- createCompareFunction(Context, *Target, rcc::GenericOpcode::G_ICMP, *I64, false,
- rcc::IntegerPredicate::Equal, rcc::FloatingPredicate::AlwaysFalse);
- auto LessEqual = createCompareFunction(Context, *Target, rcc::GenericOpcode::G_ICMP, *I64, false,
- rcc::IntegerPredicate::SignedLessEqual,
- rcc::FloatingPredicate::AlwaysFalse);
- if (Equal) {
- auto *Instruction = Equal->firstBlock()->firstInstruction();
- const rcc::Register *NarrowResult = Instruction->operandAt(0)->getIf<rcc::Register>();
- auto WideResult = Equal->registerInfo().createVirtualRegister(*I64);
- rcc::MachineIRBuilder Builder(*Equal, *Target);
- if (NarrowResult == nullptr || !WideResult || !constrainRegister(*Equal, *WideResult, *I64) ||
- !Builder.setInsertPoint(*Equal->firstBlock()))
- return;
- auto Extension = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_ANYEXT));
- if (!Extension || !Extension->addDef(*WideResult) || !Extension->addUse(*NarrowResult))
- return;
- auto BuiltExtension = Extension->build();
- if (!BuiltExtension)
- return;
- auto Action = GISel.Legalizer.classify(*Instruction, *Equal, *Target);
- auto Lowered = Action ? GISel.Legalizer.lower(*Instruction, *Equal, *Target, *Action)
- : rcc::Expected<void>(std::unexpected(Action.error()));
- const rcc::Register *LegalResult = Instruction->operandAt(0)->getIf<rcc::Register>();
- Context.expect(Lowered && (*BuiltExtension)->parent() == nullptr && LegalResult != nullptr &&
- *LegalResult == *WideResult,
- "RV64 integer comparison legalization must fold a single result extension");
- auto Selection = (*GISel.Selector).select(*Instruction, *Equal, *Target);
- Context.expect(Action && *Action == rcc::LegalizationAction::Custom && Lowered && Selection,
- "integer equality must legalize its result container and select");
- rcc::MachineInstr *FirstSelected = Equal->firstBlock()->firstInstruction();
- rcc::MachineInstr *SecondSelected = FirstSelected == nullptr ? nullptr : FirstSelected->next();
- Context.expect(SecondSelected != nullptr && SecondSelected->next() == nullptr,
- "integer equality must emit XOR and zero-test instructions");
- if (SecondSelected != nullptr && SecondSelected->next() == nullptr) {
- Context.expect(FirstSelected->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::XOR)),
- "integer equality must start with XOR");
- Context.expect(SecondSelected->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::SLTIU)),
- "integer equality must finish with SLTIU");
- }
- }
- if (LessEqual) {
- auto *Instruction = LessEqual->firstBlock()->firstInstruction();
- auto Action = GISel.Legalizer.classify(*Instruction, *LessEqual, *Target);
- auto Lowered = Action ? GISel.Legalizer.lower(*Instruction, *LessEqual, *Target, *Action)
- : rcc::Expected<void>(std::unexpected(Action.error()));
- Context.expect(Action && *Action == rcc::LegalizationAction::Custom && Lowered &&
- constrainVirtualRegisters(*LessEqual) &&
- (*GISel.Selector).select(*Instruction, *LessEqual, *Target),
- "signed less-equal must legalize its result container and select");
- Context.expect(LessEqual->allInstructions().size() == 2,
- "signed less-equal must emit compare and inversion");
- }
-
- auto Ordered =
- createCompareFunction(Context, *Target, rcc::GenericOpcode::G_FCMP, *F32, true,
- rcc::IntegerPredicate::Equal, rcc::FloatingPredicate::OrderedEqual);
- auto UnorderedEqual =
- createCompareFunction(Context, *Target, rcc::GenericOpcode::G_FCMP, *F32, true,
- rcc::IntegerPredicate::Equal, rcc::FloatingPredicate::UnorderedEqual);
- if (Ordered) {
- auto *Instruction = Ordered->firstBlock()->firstInstruction();
- Context.expect((*GISel.Selector).select(*Instruction, *Ordered, *Target).has_value(),
- "ordered floating equality must select");
- Context.expect(Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::FEQ_S)),
- "ordered floating equality must select FEQ.S");
- }
- if (UnorderedEqual) {
- auto *Instruction = UnorderedEqual->firstBlock()->firstInstruction();
- Context.expect((*GISel.Selector).select(*Instruction, *UnorderedEqual, *Target).has_value(),
- "unordered floating equality must select");
- Context.expect(UnorderedEqual->allInstructions().size() == 4,
- "unordered floating equality must emit ordered compare sequence");
- }
-
- auto FMin = createFloatBinaryFunction(Context, *Target, rcc::GenericOpcode::G_FMINNUM, *F32);
- if (FMin) {
- auto *Instruction = FMin->firstBlock()->firstInstruction();
- Context.expect((*GISel.Selector).select(*Instruction, *FMin, *Target).has_value(),
- "floating minnum must select");
- Context.expect(Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::FMIN_S)),
- "floating minnum must select FMIN.S");
- }
-
- auto ControlFunction =
- rcc::MachineFunction::create("riscv_control", rcc::MachineFunctionStableID::fromValue(900));
- Context.expect(ControlFunction.has_value(), "control test function creation must succeed");
- if (!ControlFunction)
- return;
- auto Block = (*ControlFunction)->createBasicBlock();
- if (!Block || !(*ControlFunction)->appendBlock(**Block))
- return;
- auto Address = (*ControlFunction)->registerInfo().createVirtualRegister(*Pointer);
- if (!Address || !constrainRegister(**ControlFunction, *Address, *Pointer))
- return;
- rcc::MachineIRBuilder Builder(**ControlFunction, *Target);
- if (!Builder.setInsertPoint(**Block))
- return;
- auto Indirect = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_BRINDIRECT));
- auto Fence = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_FENCE));
- auto Trap = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_TRAP));
- if (!Indirect || !Fence || !Trap || !Indirect->addUse(*Address) || !Indirect->build() ||
- !Fence->addImmediate(7) || !Fence->addImmediate(0) || !Fence->build() || !Trap->build())
- return;
- auto *IndirectInstruction = (*ControlFunction)->firstBlock()->firstInstruction();
- Context.expect(
- (*GISel.Selector).select(*IndirectInstruction, **ControlFunction, *Target).has_value(),
- "indirect branch must select");
- Context.expect(IndirectInstruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::JALR)),
- "indirect branch must select JALR");
- auto *FenceInstruction = IndirectInstruction->next();
- Context.expect(
- (*GISel.Selector).select(*FenceInstruction, **ControlFunction, *Target).has_value(),
- "fence must select");
- Context.expect(FenceInstruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::FENCE)),
- "fence must select FENCE");
- auto *TrapInstruction = FenceInstruction->next();
- Context.expect((*GISel.Selector).select(*TrapInstruction, **ControlFunction, *Target).has_value(),
- "trap must select");
- Context.expect(TrapInstruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::EBREAK)),
- "trap must select EBREAK");
-}
-
-void testCompositeScalarSelection(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(), "RV64GC target must be available for composite selection");
- if (!Target)
- return;
- auto I1 = rcc::LowLevelType::makeCategoryNeutralScalar(1);
- auto I32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- auto F64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- if (!I1 || !I32 || !I64 || !F64)
- return;
-
- auto DivRem = createRegisterOnlyFunction(Context, *Target, rcc::GenericOpcode::G_SDIVREM,
- {*I64, *I64}, {*I64, *I64});
- auto MultiplyHigh = createRegisterOnlyFunction(Context, *Target, rcc::GenericOpcode::G_UMULH,
- {*I32}, {*I32, *I32});
- auto Minimum = createRegisterOnlyFunction(Context, *Target, rcc::GenericOpcode::G_SMIN, {*I64},
- {*I64, *I64});
- auto Absolute =
- createRegisterOnlyFunction(Context, *Target, rcc::GenericOpcode::G_ABS, {*I32}, {*I32});
- auto IntegerSelect = createRegisterOnlyFunction(Context, *Target, rcc::GenericOpcode::G_SELECT,
- {*I64}, {*I1, *I64, *I64});
- auto FloatSelect = createRegisterOnlyFunction(Context, *Target, rcc::GenericOpcode::G_SELECT,
- {*F64}, {*I1, *F64, *F64}, true);
- auto CopySign = createRegisterOnlyFunction(Context, *Target, rcc::GenericOpcode::G_FCOPYSIGN,
- {*F64}, {*F64, *F64}, true);
- const std::array Functions{DivRem.get(), MultiplyHigh.get(), Minimum.get(), Absolute.get(),
- IntegerSelect.get(), FloatSelect.get(), CopySign.get()};
- for (rcc::MachineFunction *Function : Functions) {
- Context.expect(Function != nullptr, "composite scalar fixture must be complete");
- if (Function == nullptr)
- continue;
- rcc::MachineInstr *Instruction = Function->firstBlock()->firstInstruction();
- auto Classification = GISel.Legalizer.classify(*Instruction, *Function, *Target);
- Context.expect(Classification && *Classification == rcc::LegalizationAction::Legal,
- "composite scalar operation must be legal on RV64GC");
- auto Selection = (*GISel.Selector).select(*Instruction, *Function, *Target);
- Context.expect(Selection.has_value(), "composite scalar operation must select on RV64GC");
- Context.expect(Selection && validatesSelectedFunction(*Target, *Function),
- "composite scalar selection must satisfy all target descriptors");
- }
- if (DivRem) {
- const auto Instructions = DivRem->allInstructions();
- Context.expect(Instructions.size() == 2, "G_SDIVREM must emit separate quotient and remainder");
- if (Instructions.size() == 2) {
- auto It = Instructions.begin();
- Context.expect((*It)->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::DIV)),
- "G_SDIVREM quotient must use DIV");
- ++It;
- Context.expect((*It)->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::REM)),
- "G_SDIVREM remainder must use REM");
- }
- }
- if (MultiplyHigh) {
- Context.expect(MultiplyHigh->allInstructions().size() >= 4,
- "i32 G_UMULH must explicitly zero-extend before multiplication");
- }
- if (CopySign) {
- Context.expect(CopySign->firstBlock()->firstInstruction()->opcode() ==
- rcc::Opcode::fromValue(
- static_cast<std::uint32_t>(rcc::riscv::InstructionOpcode::FSGNJ_D)),
- "G_FCOPYSIGN must select FSGNJ.D");
- }
-}
-
-void testOverflowSelection(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- if (!Target)
- return;
- auto I1 = rcc::LowLevelType::makeCategoryNeutralScalar(1);
- auto I32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- if (!I1 || !I32 || !I64)
- return;
- const std::array Opcodes{rcc::GenericOpcode::G_UADDO, rcc::GenericOpcode::G_USUBO,
- rcc::GenericOpcode::G_SADDO, rcc::GenericOpcode::G_SSUBO,
- rcc::GenericOpcode::G_UMULO, rcc::GenericOpcode::G_SMULO};
- for (rcc::GenericOpcode Generic : Opcodes) {
- const rcc::LowLevelType Type = Generic == rcc::GenericOpcode::G_UMULO ? *I32 : *I64;
- auto Function =
- createRegisterOnlyFunction(Context, *Target, Generic, {Type, *I1}, {Type, Type});
- Context.expect(Function != nullptr, "overflow fixture must be complete");
- if (!Function)
- continue;
- rcc::MachineInstr *Instruction = Function->firstBlock()->firstInstruction();
- auto Selection = (*GISel.Selector).select(*Instruction, *Function, *Target);
- Context.expect(Selection.has_value(), "RV64GC overflow operation must select");
- Context.expect(Selection && validatesSelectedFunction(*Target, *Function),
- "overflow sequence must satisfy all target descriptors");
- Context.expect(Function->allInstructions().size() >= 2,
- "overflow operation must materialize its status result");
- }
- const std::array ExtendedOpcodes{rcc::GenericOpcode::G_UADDE, rcc::GenericOpcode::G_USUBE,
- rcc::GenericOpcode::G_SADDE, rcc::GenericOpcode::G_SSUBE};
- for (rcc::GenericOpcode Generic : ExtendedOpcodes) {
- auto Function =
- createRegisterOnlyFunction(Context, *Target, Generic, {*I64, *I1}, {*I64, *I64, *I1});
- Context.expect(Function != nullptr, "extended overflow fixture must be complete");
- if (!Function)
- continue;
- rcc::MachineInstr *Instruction = Function->firstBlock()->firstInstruction();
- auto Selection = (*GISel.Selector).select(*Instruction, *Function, *Target);
- Context.expect(Selection.has_value(), "extended RV64GC overflow operation must select");
- Context.expect(Selection && validatesSelectedFunction(*Target, *Function),
- "extended overflow sequence must satisfy all target descriptors");
- }
-}
-
-void testAtomicCompareExchangeLegalization(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- auto Function = rcc::MachineFunction::create("riscv_atomic_cmpxchg",
- rcc::MachineFunctionStableID::fromValue(1299));
- if (!Target || !Function)
- return;
- auto Block = (*Function)->createBasicBlock();
- auto I1 = rcc::LowLevelType::makeCategoryNeutralScalar(1);
- auto I8 = rcc::LowLevelType::makeCategoryNeutralScalar(8);
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- auto Pointer = rcc::LowLevelType::makePointer(0, 64);
- auto Size = rcc::MemorySize::fixed(1);
- auto Align = rcc::Alignment::create(1);
- if (!Block || !(*Function)->appendBlock(**Block) || !I1 || !I8 || !I64 || !Pointer || !Size ||
- !Align)
- return;
- auto Address = (*Function)->registerInfo().createVirtualRegister(*Pointer);
- auto Compare = (*Function)->registerInfo().createVirtualRegister(*I8);
- auto NewValue = (*Function)->registerInfo().createVirtualRegister(*I8);
- auto Result = (*Function)->registerInfo().createVirtualRegister(*I8);
- auto Extended = (*Function)->registerInfo().createVirtualRegister(*I64);
- if (!Address || !Compare || !NewValue || !Result || !Extended)
- return;
-
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return;
- auto AddressDefinition = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_IMPLICIT_DEF));
- if (!AddressDefinition || !AddressDefinition->addDef(*Address) || !AddressDefinition->build())
- return;
- const auto BuildConstant = [&](rcc::Register Destination, std::uint64_t Value) -> bool {
- auto Immediate = rcc::APInt::fromUnsigned(8, Value);
- auto Pending = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_CONSTANT));
- return Immediate && Pending && Pending->addDef(Destination) &&
- Pending->addIntegerImmediate(std::move(*Immediate)) && Pending->build();
- };
- if (!BuildConstant(*Compare, 0) || !BuildConstant(*NewValue, 1))
- return;
-
- rcc::MachineMemOperandDesc Description;
- Description.Accesses = {rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Store};
- Description.AtomicKind = rcc::AtomicOperation::CompareExchange;
- Description.SuccessOrdering = rcc::AtomicOrdering::Monotonic;
- Description.Size = *Size;
- Description.MemoryType = *I8;
- Description.Align = *Align;
- Description.Base = rcc::MemoryBase::stack();
- auto Memory = (*Function)->createMemOperand(std::move(Description));
- auto Atomic = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_ATOMIC_CMPXCHG));
- if (!Memory || !Atomic || !Atomic->addDef(*Result) || !Atomic->addUse(*Address) ||
- !Atomic->addUse(*Compare) || !Atomic->addUse(*NewValue) || !Atomic->addMemOperand(**Memory))
- return;
- auto BuiltAtomic = Atomic->build();
- auto Extension = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_ANYEXT));
- if (!BuiltAtomic || !Extension || !Extension->addDef(*Extended) || !Extension->addUse(*Result))
- return;
- auto BuiltExtension = Extension->build();
- if (!BuiltExtension)
- return;
-
- auto Action = GISel.Legalizer.classify(**BuiltAtomic, **Function, *Target);
- auto Lowered = Action ? GISel.Legalizer.lower(**BuiltAtomic, **Function, *Target, *Action)
- : rcc::Expected<void>(std::unexpected(Action.error()));
- Context.expect(Action && *Action == rcc::LegalizationAction::Custom && Lowered.has_value(),
- "narrow RV64 compare-exchange must use the widening legalizer rule");
- if (!Lowered)
- return;
-
- const auto CompareInfo = (*Function)->registerInfo().virtualRegisterInfo(*Compare);
- const auto NewValueInfo = (*Function)->registerInfo().virtualRegisterInfo(*NewValue);
- const rcc::Register *AtomicResult = (*BuiltAtomic)->operandAt(0)->getIf<rcc::Register>();
- const auto AtomicResultInfo =
- AtomicResult == nullptr
- ? rcc::Expected<std::reference_wrapper<const rcc::VirtualRegisterInfo>>(std::unexpected(
- rcc::Error(rcc::ErrorCode::InvalidArgument, "missing compare-exchange result")))
- : (*Function)->registerInfo().virtualRegisterInfo(*AtomicResult);
- auto LegalAction = GISel.Legalizer.classify(**BuiltAtomic, **Function, *Target);
- Context.expect(CompareInfo && CompareInfo->get().Type == *I64 && NewValueInfo &&
- NewValueInfo->get().Type == *I64 && AtomicResultInfo &&
- AtomicResultInfo->get().Type == *I64 &&
- (*BuiltExtension)->parent() == nullptr &&
- Target->validateInstruction(**BuiltAtomic, (*Function)->registerInfo()) &&
- LegalAction && *LegalAction == rcc::LegalizationAction::Legal,
- "compare-exchange widening must preserve the narrow MMO in a legal i64 container");
-
- auto FallbackCompare = (*Function)->registerInfo().createVirtualRegister(*I8);
- auto FallbackNewValue = (*Function)->registerInfo().createVirtualRegister(*I8);
- auto FallbackResult = (*Function)->registerInfo().createVirtualRegister(*I8);
- if (!FallbackCompare || !FallbackNewValue || !FallbackResult || !Builder.setInsertPoint(**Block))
- return;
- for (rcc::Register Definition : {*FallbackCompare, *FallbackNewValue}) {
- auto Implicit = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_IMPLICIT_DEF));
- if (!Implicit || !Implicit->addDef(Definition) || !Implicit->build())
- return;
- }
- auto Fallback = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_ATOMIC_CMPXCHG));
- if (!Fallback || !Fallback->addDef(*FallbackResult) || !Fallback->addUse(*Address) ||
- !Fallback->addUse(*FallbackCompare) || !Fallback->addUse(*FallbackNewValue) ||
- !Fallback->addMemOperand(**Memory))
- return;
- auto BuiltFallback = Fallback->build();
- if (!BuiltFallback)
- return;
- auto FallbackAction = GISel.Legalizer.classify(**BuiltFallback, **Function, *Target);
- auto FallbackLowered =
- FallbackAction ? GISel.Legalizer.lower(**BuiltFallback, **Function, *Target, *FallbackAction)
- : rcc::Expected<void>(std::unexpected(FallbackAction.error()));
- rcc::MachineInstr *Truncation = (*BuiltFallback)->next();
- const rcc::Register *WideFallbackResult = (*BuiltFallback)->operandAt(0)->getIf<rcc::Register>();
- const rcc::Register *TruncatedResult =
- Truncation == nullptr ? nullptr : Truncation->operandAt(0)->getIf<rcc::Register>();
- const rcc::Register *TruncationSource =
- Truncation == nullptr ? nullptr : Truncation->operandAt(1)->getIf<rcc::Register>();
- Context.expect(FallbackLowered && WideFallbackResult != nullptr &&
- *WideFallbackResult != *FallbackResult && Truncation != nullptr &&
- Truncation->opcode() == rcc::opcode(rcc::GenericOpcode::G_TRUNC) &&
- TruncatedResult != nullptr && *TruncatedResult == *FallbackResult &&
- TruncationSource != nullptr && *TruncationSource == *WideFallbackResult &&
- Target->validateInstruction(*Truncation, (*Function)->registerInfo()),
- "general compare-exchange widening must truncate from the new i64 result");
-
- auto OldValue = (*Function)->registerInfo().createVirtualRegister(*I64);
- auto Success = (*Function)->registerInfo().createVirtualRegister(*I1);
- if (!OldValue || !Success || !Builder.setInsertPoint(**Block))
- return;
- auto WithSuccess =
- Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_ATOMIC_CMPXCHG_WITH_SUCCESS));
- if (!WithSuccess || !WithSuccess->addDef(*OldValue) || !WithSuccess->addDef(*Success) ||
- !WithSuccess->addUse(*Address) || !WithSuccess->addUse(*Compare) ||
- !WithSuccess->addUse(*NewValue) || !WithSuccess->addMemOperand(**Memory))
- return;
- auto BuiltWithSuccess = WithSuccess->build();
- if (!BuiltWithSuccess)
- return;
- auto WithSuccessAction = GISel.Legalizer.classify(**BuiltWithSuccess, **Function, *Target);
- auto WithSuccessLowered =
- WithSuccessAction
- ? GISel.Legalizer.lower(**BuiltWithSuccess, **Function, *Target, *WithSuccessAction)
- : rcc::Expected<void>(std::unexpected(WithSuccessAction.error()));
- rcc::MachineInstr *SuccessCompare = (*BuiltWithSuccess)->previous();
- rcc::MachineInstr *LoweredAtomic =
- SuccessCompare == nullptr ? nullptr : SuccessCompare->previous();
- Context.expect(
- WithSuccessAction && *WithSuccessAction == rcc::LegalizationAction::Custom &&
- WithSuccessLowered && LoweredAtomic != nullptr && SuccessCompare != nullptr &&
- LoweredAtomic->opcode() == rcc::opcode(rcc::GenericOpcode::G_ATOMIC_CMPXCHG) &&
- SuccessCompare->opcode() == rcc::opcode(rcc::GenericOpcode::G_ICMP) &&
- (*BuiltWithSuccess)->opcode() == rcc::opcode(rcc::GenericOpcode::COPY) &&
- (*BuiltWithSuccess)->memOperands().empty() &&
- Target->validateInstruction(*LoweredAtomic, (*Function)->registerInfo()) &&
- Target->validateInstruction(*SuccessCompare, (*Function)->registerInfo()) &&
- Target->validateInstruction(**BuiltWithSuccess, (*Function)->registerInfo()),
- "compare-exchange-with-success must lower to atomic, equality test, and old-value copy");
-}
-
-void testConstantExtensionPreservesLiveSource(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- auto Function = rcc::MachineFunction::create("riscv_live_constant_extension",
- rcc::MachineFunctionStableID::fromValue(1300));
- auto I32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- if (!Target || !Function || !I32 || !I64)
- return;
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return;
- auto Source = (*Function)->registerInfo().createVirtualRegister(*I32);
- auto Extended = (*Function)->registerInfo().createVirtualRegister(*I64);
- auto PreservedUse = (*Function)->registerInfo().createVirtualRegister(*I32);
- auto Immediate = rcc::APInt::fromUnsigned(32, 7);
- if (!Source || !Extended || !PreservedUse || !Immediate)
- return;
-
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return;
- auto Constant = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_CONSTANT));
- if (!Constant || !Constant->addDef(*Source) ||
- !Constant->addIntegerImmediate(std::move(*Immediate)))
- return;
- auto BuiltConstant = Constant->build();
- auto Extension = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_ANYEXT));
- if (!BuiltConstant || !Extension || !Extension->addDef(*Extended) || !Extension->addUse(*Source))
- return;
- auto BuiltExtension = Extension->build();
- auto Copy = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::COPY));
- if (!BuiltExtension || !Copy || !Copy->addDef(*PreservedUse) || !Copy->addUse(*Source) ||
- !Copy->build())
- return;
-
- auto Action = GISel.Legalizer.classify(**BuiltExtension, **Function, *Target);
- auto Lowered = Action ? GISel.Legalizer.lower(**BuiltExtension, **Function, *Target, *Action)
- : rcc::Expected<void>(std::unexpected(Action.error()));
- Context.expect(Action && *Action == rcc::LegalizationAction::Custom && Lowered &&
- (*BuiltExtension)->opcode() == rcc::opcode(rcc::GenericOpcode::G_CONSTANT) &&
- (*BuiltConstant)->parent() != nullptr &&
- (*Function)->registerInfo().getUniqueVRegDef(*Source) ==
- (*BuiltConstant)->operandAt(0) &&
- (*Function)->registerInfo().verifyDefUseChains(),
- "constant-extension folding must preserve a source definition with live uses");
-}
-
-void testIntegerCompareReusesConstantExtension(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- auto I32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- if (!Target || !I32 || !I64)
- return;
- auto Function =
- createCompareFunction(Context, *Target, rcc::GenericOpcode::G_ICMP, *I32, false,
- rcc::IntegerPredicate::Equal, rcc::FloatingPredicate::AlwaysFalse);
- if (!Function)
- return;
- rcc::MachineInstr *Compare = Function->firstBlock()->firstInstruction();
- const rcc::Register *Right = Compare->operandAt(3)->getIf<rcc::Register>();
- auto WideRight = Function->registerInfo().createVirtualRegister(*I64);
- auto Immediate = rcc::APInt::fromUnsigned(32, 0);
- if (Right == nullptr || !WideRight || !Immediate)
- return;
-
- rcc::MachineIRBuilder Builder(*Function, *Target);
- if (!Builder.setInsertPointBefore(*Compare))
- return;
- auto Constant = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_CONSTANT));
- if (!Constant || !Constant->addDef(*Right) ||
- !Constant->addIntegerImmediate(std::move(*Immediate)))
- return;
- auto BuiltConstant = Constant->build();
- if (!BuiltConstant)
- return;
- auto Extension = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_ANYEXT));
- if (!Extension || !Extension->addDef(*WideRight) || !Extension->addUse(*Right))
- return;
- auto BuiltExtension = Extension->build();
- if (!BuiltExtension)
- return;
-
- auto Action = GISel.Legalizer.classify(*Compare, *Function, *Target);
- auto Lowered = Action ? GISel.Legalizer.lower(*Compare, *Function, *Target, *Action)
- : rcc::Expected<void>(std::unexpected(Action.error()));
- const rcc::Register *LegalRight = Compare->operandAt(3)->getIf<rcc::Register>();
- Context.expect(Action && *Action == rcc::LegalizationAction::Custom && Lowered,
- "RV64 integer comparison extension-reuse fixture must legalize");
- Context.expect(LegalRight != nullptr && *LegalRight == *WideRight,
- "RV64 integer comparison legalization must reuse an existing constant extension");
- Context.expect(Function->registerInfo().verifyDefUseChains().has_value(),
- "integer comparison extension reuse must preserve exact def-use chains");
-
- auto ExtensionAction = GISel.Legalizer.classify(**BuiltExtension, *Function, *Target);
- auto Folded = ExtensionAction
- ? GISel.Legalizer.lower(**BuiltExtension, *Function, *Target, *ExtensionAction)
- : rcc::Expected<void>(std::unexpected(ExtensionAction.error()));
- const rcc::Register *FoldedResult = (*BuiltConstant)->operandAt(0)->getIf<rcc::Register>();
- Context.expect(ExtensionAction && *ExtensionAction == rcc::LegalizationAction::Custom && Folded &&
- (*BuiltExtension)->parent() == nullptr && FoldedResult != nullptr &&
- *FoldedResult == *WideRight &&
- Function->firstBlock()->firstInstruction() == *BuiltConstant &&
- Function->registerInfo().verifyDefUseChains(),
- "single-use constant extension folding must preserve the source layout position");
-}
-
-void testNarrowAnyExtBinaryArtifacts(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- auto Function = rcc::MachineFunction::create("riscv_narrow_binary_artifacts",
- rcc::MachineFunctionStableID::fromValue(1301));
- auto I1 = rcc::LowLevelType::makeCategoryNeutralScalar(1);
- auto I32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- if (!Target || !Function || !I1 || !I32 || !I64)
- return;
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return;
- auto WideLeft = (*Function)->registerInfo().createVirtualRegister(*I64);
- auto WideRight = (*Function)->registerInfo().createVirtualRegister(*I64);
- auto NarrowLeft = (*Function)->registerInfo().createVirtualRegister(*I32);
- auto CopiedLeft = (*Function)->registerInfo().createVirtualRegister(*I32);
- auto NarrowRight = (*Function)->registerInfo().createVirtualRegister(*I1);
- auto ExtendedRight = (*Function)->registerInfo().createVirtualRegister(*I32);
- auto Product = (*Function)->registerInfo().createVirtualRegister(*I32);
- auto Result = (*Function)->registerInfo().createVirtualRegister(*I64);
- if (!WideLeft || !WideRight || !NarrowLeft || !CopiedLeft || !NarrowRight || !ExtendedRight ||
- !Product || !Result)
- return;
-
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return;
- const auto BuildUnary = [&](rcc::GenericOpcode Generic, rcc::Register Destination,
- rcc::Register Source) -> rcc::MachineInstr * {
- auto Pending = Builder.buildInstr(rcc::opcode(Generic));
- if (!Pending || !Pending->addDef(Destination) || !Pending->addUse(Source))
- return nullptr;
- auto Built = Pending->build();
- return Built ? *Built : nullptr;
- };
- rcc::MachineInstr *LeftTrunc = BuildUnary(rcc::GenericOpcode::G_TRUNC, *NarrowLeft, *WideLeft);
- rcc::MachineInstr *LeftCopy = BuildUnary(rcc::GenericOpcode::COPY, *CopiedLeft, *NarrowLeft);
- rcc::MachineInstr *RightTrunc = BuildUnary(rcc::GenericOpcode::G_TRUNC, *NarrowRight, *WideRight);
- rcc::MachineInstr *RightExtension =
- BuildUnary(rcc::GenericOpcode::G_ANYEXT, *ExtendedRight, *NarrowRight);
- auto Multiply = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_MUL));
- if (LeftTrunc == nullptr || LeftCopy == nullptr || RightTrunc == nullptr ||
- RightExtension == nullptr || !Multiply || !Multiply->addDef(*Product) ||
- !Multiply->addUse(*CopiedLeft) || !Multiply->addUse(*ExtendedRight))
- return;
- auto BuiltMultiply = Multiply->build();
- rcc::MachineInstr *OuterExtension = BuildUnary(rcc::GenericOpcode::G_ANYEXT, *Result, *Product);
- if (!BuiltMultiply || OuterExtension == nullptr)
- return;
-
- auto Action = GISel.Legalizer.classify(*OuterExtension, **Function, *Target);
- auto Lowered = Action ? GISel.Legalizer.lower(*OuterExtension, **Function, *Target, *Action)
- : rcc::Expected<void>(std::unexpected(Action.error()));
- const rcc::Register *LegalLeft = OuterExtension->operandAt(1)->getIf<rcc::Register>();
- const rcc::Register *LegalRight = OuterExtension->operandAt(2)->getIf<rcc::Register>();
- Context.expect(
- Action && *Action == rcc::LegalizationAction::Custom && Lowered &&
- OuterExtension->opcode() == rcc::opcode(rcc::GenericOpcode::G_MUL) &&
- LegalLeft != nullptr && *LegalLeft == *WideLeft && LegalRight != nullptr &&
- *LegalRight == *WideRight && LeftTrunc->parent() == nullptr &&
- LeftCopy->parent() == nullptr && RightTrunc->parent() == nullptr &&
- RightExtension->parent() == nullptr && (*BuiltMultiply)->parent() == nullptr &&
- (*Function)->registerInfo().verifyDefUseChains() &&
- Target->validateInstruction(*OuterExtension, (*Function)->registerInfo()),
- "narrow any-extension binary folding must remove copy, truncation, and extension artifacts");
-}
-
-void testMultiplyLibcallWithoutM(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- const std::vector<std::string> FeatureNames{"+a"};
- auto Features = rcc::riscv::RISCVSubtargetFeatures::fromFeatureList(FeatureNames);
- if (!Target || !Features)
- return;
- const rcc::riscv::RISCVSubtarget Subtarget(*Features);
- const rcc::LegalizerInfo *Legalizer = Subtarget.getLegalizerInfo();
- auto Multiply64 = createBinary(Context, *Target, rcc::GenericOpcode::G_MUL, 64);
- if (Multiply64.Instruction == nullptr)
- return;
-
- auto Action = Legalizer->classify(*Multiply64.Instruction, *Multiply64.Function, *Target);
- auto Lowered =
- Action ? Legalizer->lower(*Multiply64.Instruction, *Multiply64.Function, *Target, *Action)
- : rcc::Expected<void>(std::unexpected(Action.error()));
- rcc::MachineInstr *Call = nullptr;
- for (rcc::MachineInstr *Instruction : Multiply64.Function->allInstructions()) {
- if (Instruction->parent() != nullptr &&
- Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::PseudoCALL))) {
- Call = Instruction;
- break;
- }
- }
- const rcc::MachineOperand *Callee = Call == nullptr ? nullptr : Call->operandAt(0);
- const rcc::MachineOperand *MaskOperand = Call == nullptr ? nullptr : Call->operandAt(1);
- const auto *External = Callee == nullptr ? nullptr : Callee->getIf<rcc::ExternalSymbolOperand>();
- const auto *CallMask =
- MaskOperand == nullptr ? nullptr : MaskOperand->getIf<rcc::RegisterMaskOperand>();
- const auto LP64Mask = rcc::riscv::createCallPreservedMask(rcc::riscv::CallingConvention::LP64);
- Context.expect(Action && *Action == rcc::LegalizationAction::Libcall && Lowered &&
- Multiply64.Instruction->parent() == nullptr && External != nullptr &&
- External->Name == "__muldi3" && CallMask != nullptr && CallMask->Mask &&
- LP64Mask != nullptr && *CallMask->Mask == *LP64Mask &&
- Multiply64.Function->frameInfo().state().HasCalls,
- "s64 G_MUL without M must lower through the LP64 __muldi3 call path");
-
- auto Multiply32 = createBinary(Context, *Target, rcc::GenericOpcode::G_MUL, 32);
- if (Multiply32.Instruction == nullptr)
- return;
- auto NarrowAction = Legalizer->classify(*Multiply32.Instruction, *Multiply32.Function, *Target);
- auto NarrowLowered = NarrowAction ? Legalizer->lower(*Multiply32.Instruction,
- *Multiply32.Function, *Target, *NarrowAction)
- : rcc::Expected<void>(std::unexpected(NarrowAction.error()));
- rcc::MachineInstr *WideMultiply = nullptr;
- for (rcc::MachineInstr *Instruction : Multiply32.Function->allInstructions()) {
- if (Instruction->parent() != nullptr &&
- Instruction->opcode() == rcc::opcode(rcc::GenericOpcode::G_MUL)) {
- WideMultiply = Instruction;
- break;
- }
- }
- auto WideAction = WideMultiply == nullptr
- ? rcc::Expected<rcc::LegalizationAction>(std::unexpected(
- rcc::Error(rcc::ErrorCode::InternalError, "missing wide multiply")))
- : Legalizer->classify(*WideMultiply, *Multiply32.Function, *Target);
- Context.expect(NarrowAction && *NarrowAction == rcc::LegalizationAction::Custom &&
- NarrowLowered &&
- Multiply32.Instruction->opcode() == rcc::opcode(rcc::GenericOpcode::G_TRUNC) &&
- WideAction && *WideAction == rcc::LegalizationAction::Libcall,
- "narrow G_MUL without M must widen to the single s64 multiply libcall type");
-}
-
-void testAtomicSelection(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- if (!Target)
- return;
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- auto Pointer = rcc::LowLevelType::makePointer(0, 64);
- auto Size = rcc::MemorySize::fixed(8);
- auto Align = rcc::Alignment::create(8);
- if (!I64 || !Pointer || !Size || !Align)
- return;
-
- const auto MakeRMW = [&](rcc::GenericOpcode Generic, std::uint64_t ID) -> InstructionFixture {
- auto Function =
- rcc::MachineFunction::create("riscv_atomic", rcc::MachineFunctionStableID::fromValue(ID));
- if (!Function)
- return {};
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return {};
- auto Result = (*Function)->registerInfo().createVirtualRegister(*I64);
- auto Address = (*Function)->registerInfo().createVirtualRegister(*Pointer);
- auto Value = (*Function)->registerInfo().createVirtualRegister(*I64);
- if (!Result || !Address || !Value)
- return {};
- rcc::MachineMemOperandDesc Description;
- Description.Accesses = {rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Store};
- Description.AtomicKind = rcc::AtomicOperation::ReadModifyWrite;
- Description.SuccessOrdering = rcc::AtomicOrdering::SequentiallyConsistent;
- Description.Size = *Size;
- Description.Align = *Align;
- Description.Base = rcc::MemoryBase::stack();
- Description.Offset = 32;
- auto Memory = (*Function)->createMemOperand(std::move(Description));
- if (!Memory)
- return {};
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return {};
- auto Pending = Builder.buildInstr(rcc::opcode(Generic));
- if (!Pending || !Pending->addDef(*Result) || !Pending->addUse(*Address) ||
- !Pending->addUse(*Value) || !Pending->addMemOperand(**Memory)) {
- return {};
- }
- auto Built = Pending->build();
- if (!Built || !constrainVirtualRegisters(**Function))
- return {};
- return {std::move(*Function), *Built, *Result};
- };
-
- auto Add = MakeRMW(rcc::GenericOpcode::G_ATOMICRMW_ADD, 1300);
- auto Sub = MakeRMW(rcc::GenericOpcode::G_ATOMICRMW_SUB, 1301);
- auto Nand = MakeRMW(rcc::GenericOpcode::G_ATOMICRMW_NAND, 1302);
- Context.expect(Add.Instruction != nullptr && Sub.Instruction != nullptr &&
- Nand.Instruction != nullptr,
- "atomic RMW fixtures must be complete");
- if (Add.Instruction != nullptr) {
- auto Selection = (*GISel.Selector).select(*Add.Instruction, *Add.Function, *Target);
- Context.expect(Selection.has_value() && Add.Instruction->opcode() ==
- rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::AMOADD_D)),
- "G_ATOMICRMW_ADD must select AMOADD.D");
- Context.expect(Selection && validatesSelectedFunction(*Target, *Add.Function),
- "selected AMOADD.D must satisfy its descriptor and MMO contract");
- }
- if (Sub.Instruction != nullptr) {
- auto Selection = (*GISel.Selector).select(*Sub.Instruction, *Sub.Function, *Target);
- Context.expect(Selection.has_value() && Sub.Function->allInstructions().size() == 2,
- "G_ATOMICRMW_SUB must negate its operand before AMOADD.D");
- Context.expect(Selection && validatesSelectedFunction(*Target, *Sub.Function),
- "selected atomic subtraction must satisfy target descriptors");
- }
- if (Nand.Instruction != nullptr) {
- auto Classification = GISel.Legalizer.classify(*Nand.Instruction, *Nand.Function, *Target);
- Context.expect(
- Classification && *Classification == rcc::LegalizationAction::Unsupported,
- "an atomic operation without a single-instruction RV64GC mapping must be explicit");
- auto Match = (*GISel.Selector).canSelect(*Nand.Instruction, *Nand.Function, *Target);
- Context.expect(!Match && Match.error().code() == rcc::ErrorCode::Unsupported,
- "unsupported atomic RMW must fail before mutating MachineIR");
- Context.expect(!Match && Match.error().message().find("G_ATOMICRMW_NAND") != std::string::npos,
- "unsupported atomic diagnostics must name the rejected opcode");
- }
-
- const auto MakeAtomicAccess = [&](bool Load, std::uint64_t ID) -> InstructionFixture {
- auto Function = rcc::MachineFunction::create(Load ? "riscv_atomic_load" : "riscv_atomic_store",
- rcc::MachineFunctionStableID::fromValue(ID));
- if (!Function)
- return {};
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return {};
- auto Value = (*Function)->registerInfo().createVirtualRegister(*I64);
- auto Address = (*Function)->registerInfo().createVirtualRegister(*Pointer);
- if (!Value || !Address)
- return {};
- rcc::MachineMemOperandDesc Description;
- Description.Accesses = {Load ? rcc::MemoryAccessFlag::Load : rcc::MemoryAccessFlag::Store};
- Description.AtomicKind = Load ? rcc::AtomicOperation::Load : rcc::AtomicOperation::Store;
- Description.SuccessOrdering =
- Load ? rcc::AtomicOrdering::Acquire : rcc::AtomicOrdering::Release;
- Description.Size = *Size;
- Description.Align = *Align;
- Description.Base = rcc::MemoryBase::stack();
- auto Memory = (*Function)->createMemOperand(std::move(Description));
- if (!Memory)
- return {};
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return {};
- auto Pending = Builder.buildInstr(
- rcc::opcode(Load ? rcc::GenericOpcode::G_LOAD : rcc::GenericOpcode::G_STORE));
- if (!Pending)
- return {};
- if (Load) {
- if (!Pending->addDef(*Value) || !Pending->addUse(*Address))
- return {};
- } else if (!Pending->addUse(*Value) || !Pending->addUse(*Address)) {
- return {};
- }
- if (!Pending->addMemOperand(**Memory))
- return {};
- auto Built = Pending->build();
- if (!Built || !constrainVirtualRegisters(**Function))
- return {};
- return {std::move(*Function), *Built, *Value};
- };
- auto AtomicLoad = MakeAtomicAccess(true, 1303);
- auto AtomicStore = MakeAtomicAccess(false, 1304);
- if (AtomicLoad.Instruction != nullptr) {
- auto Selection =
- (*GISel.Selector).select(*AtomicLoad.Instruction, *AtomicLoad.Function, *Target);
- Context.expect(Selection && AtomicLoad.Instruction->opcode() ==
- rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::LR_D)),
- "atomic G_LOAD must select LR.D");
- Context.expect(Selection && validatesSelectedFunction(*Target, *AtomicLoad.Function),
- "selected atomic load must satisfy target descriptors");
- }
- if (AtomicStore.Instruction != nullptr) {
- auto Selection =
- (*GISel.Selector).select(*AtomicStore.Instruction, *AtomicStore.Function, *Target);
- Context.expect(Selection && AtomicStore.Instruction->opcode() ==
- rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::AMOSWAP_D)),
- "atomic G_STORE must select AMOSWAP.D with a discarded old value");
- Context.expect(Selection && validatesSelectedFunction(*Target, *AtomicStore.Function),
- "selected atomic store must satisfy target descriptors");
- }
-}
-
-void testUnsupportedTypes(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(), "RV64GC target must be available for type diagnostics");
- if (!Target)
- return;
- auto WideInteger = createBinary(Context, *Target, rcc::GenericOpcode::G_ADD, 128);
- if (WideInteger.Function && WideInteger.Instruction != nullptr) {
- auto Selection =
- (*GISel.Selector).canSelect(*WideInteger.Instruction, *WideInteger.Function, *Target);
- Context.expect(!Selection && Selection.error().code() == rcc::ErrorCode::Unsupported,
- "i128 arithmetic must produce an Unsupported diagnostic");
- Context.expect(!Selection && Selection.error().message().find("s128") != std::string::npos,
- "unsupported type diagnostics must name the rejected LowLevelType");
- }
- auto Function = rcc::MachineFunction::create("unsupported_type",
- rcc::MachineFunctionStableID::fromValue(100));
- if (!Function)
- return;
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return;
- auto F64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- if (!F64)
- return;
- auto Result = (*Function)->registerInfo().createVirtualRegister(*F64);
- auto Source = (*Function)->registerInfo().createVirtualRegister(*F64);
- if (!Result || !Source)
- return;
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return;
- auto Pending = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_FEXP));
- if (!Pending || !Pending->addDef(*Result) || !Pending->addUse(*Source))
- return;
- if (!Pending->build())
- return;
- const rcc::MachineInstr &Instruction = **Function->get()->allInstructions().begin();
- auto Action = GISel.Legalizer.classify(Instruction, **Function, *Target);
- Context.expect(Action && *Action == rcc::LegalizationAction::Unsupported,
- "unsupported floating operations must remain explicitly diagnosed");
- auto Selection = (*GISel.Selector).canSelect(Instruction, **Function, *Target);
- Context.expect(!Selection && Selection.error().code() == rcc::ErrorCode::Unsupported,
- "selector pre-matching must reject unsupported floating operations");
- Context.expect(!Selection && Selection.error().message().find("G_FEXP") != std::string::npos,
- "unsupported selection diagnostics must name the rejected opcode");
-}
-
-void testVACopyLegalization(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(), "RV64GC target must be available for va_copy legalization");
- if (!Target)
- return;
- Context.expect(Target->mirText().intrinsic("llvm.va_copy") ==
- rcc::intrinsicID(rcc::GenericIntrinsic::VACopy),
- "RV64GC MIR text must expose the generic va_copy intrinsic identity");
-
- auto Function =
- rcc::MachineFunction::create("riscv_vacopy", rcc::MachineFunctionStableID::fromValue(102));
- if (!Function)
- return;
- auto Block = (*Function)->createBasicBlock();
- auto Pointer = rcc::LowLevelType::makePointer(0, 64);
- if (!Block || !Pointer || !(*Function)->appendBlock(**Block))
- return;
- auto Destination = (*Function)->registerInfo().createVirtualRegister(*Pointer);
- auto Source = (*Function)->registerInfo().createVirtualRegister(*Pointer);
- if (!Destination || !Source)
- return;
-
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return;
- auto Pending = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_INTRINSIC_W_SIDE_EFFECTS));
- if (!Pending || !Pending->addIntrinsic(rcc::intrinsicID(rcc::GenericIntrinsic::VACopy)) ||
- !Pending->addUse(*Destination) || !Pending->addUse(*Source)) {
- return;
- }
- auto Built = Pending->build();
- if (!Built)
- return;
- auto Action = GISel.Legalizer.classify(**Built, **Function, *Target);
- Context.expect(Action && *Action == rcc::LegalizationAction::Custom,
- "RV64GC must custom-legalize va_copy");
- if (!Action)
- return;
- auto Lowered = GISel.Legalizer.lower(**Built, **Function, *Target, *Action);
- Context.expect(Lowered.has_value(), "RV64GC va_copy legalization must succeed");
- if (!Lowered)
- return;
-
- rcc::MachineInstr *Load = (*Function)->firstBlock()->firstInstruction();
- rcc::MachineInstr *Store = Load == nullptr ? nullptr : Load->next();
- const rcc::Register *Loaded = Load == nullptr || Load->operandAt(0) == nullptr
- ? nullptr
- : Load->operandAt(0)->getIf<rcc::Register>();
- const rcc::Register *LoadAddress = Load == nullptr || Load->operandAt(1) == nullptr
- ? nullptr
- : Load->operandAt(1)->getIf<rcc::Register>();
- const rcc::Register *Stored = Store == nullptr || Store->operandAt(0) == nullptr
- ? nullptr
- : Store->operandAt(0)->getIf<rcc::Register>();
- const rcc::Register *StoreAddress = Store == nullptr || Store->operandAt(1) == nullptr
- ? nullptr
- : Store->operandAt(1)->getIf<rcc::Register>();
- Context.expect(
- (*Built)->parent() == nullptr && Load != nullptr && Store != nullptr &&
- Store->next() == nullptr && Load->opcode() == rcc::opcode(rcc::GenericOpcode::G_LOAD) &&
- Store->opcode() == rcc::opcode(rcc::GenericOpcode::G_STORE) && Loaded != nullptr &&
- Stored != nullptr && *Loaded == *Stored && LoadAddress != nullptr &&
- *LoadAddress == *Source && StoreAddress != nullptr && *StoreAddress == *Destination,
- "va_copy must become a pointer load followed by a store to the destination list");
- const auto LoadMemory =
- Load == nullptr ? std::span<const rcc::MachineMemOperand *const>{} : Load->memOperands();
- const auto StoreMemory =
- Store == nullptr ? std::span<const rcc::MachineMemOperand *const>{} : Store->memOperands();
- Context.expect(
- LoadMemory.size() == 1 && StoreMemory.size() == 1 &&
- LoadMemory.front()->description().Accesses.has(rcc::MemoryAccessFlag::Load) &&
- StoreMemory.front()->description().Accesses.has(rcc::MemoryAccessFlag::Store) &&
- LoadMemory.front()->description().Size.minimumBytes() == 8 &&
- StoreMemory.front()->description().Size.minimumBytes() == 8 &&
- LoadMemory.front()->description().MemoryType == *Pointer &&
- StoreMemory.front()->description().MemoryType == *Pointer,
- "va_copy expansion must attach complete pointer-sized load/store memory operands");
-}
-
-void testVAArgLegalization(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(), "RV64GC target must be available for va_arg legalization");
- if (!Target)
- return;
- auto Function =
- rcc::MachineFunction::create("riscv_vaarg", rcc::MachineFunctionStableID::fromValue(103));
- if (!Function)
- return;
- auto Block = (*Function)->createBasicBlock();
- auto Pointer = rcc::LowLevelType::makePointer(0, 64);
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- if (!Block || !Pointer || !I64 || !(*Function)->appendBlock(**Block))
- return;
- auto Destination = (*Function)->registerInfo().createVirtualRegister(*I64);
- auto ListPointer = (*Function)->registerInfo().createVirtualRegister(*Pointer);
- auto Sink = (*Function)->registerInfo().createVirtualRegister(*I64);
- if (!Destination || !ListPointer || !Sink)
- return;
-
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return;
- auto Pending = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_VAARG));
- if (!Pending || !Pending->addDef(*Destination) || !Pending->addUse(*ListPointer) ||
- !Pending->addImmediate(4)) {
- return;
- }
- auto Built = Pending->build();
- if (!Built)
- return;
- auto Freeze = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_FREEZE));
- if (!Freeze || !Freeze->addDef(*Sink) || !Freeze->addUse(*Destination) || !Freeze->build())
- return;
-
- Context.expect(Target->validateInstruction(**Built, (*Function)->registerInfo()).has_value(),
- "canonical G_VAARG without MMOs must satisfy the generic descriptor");
- auto Action = GISel.Legalizer.classify(**Built, **Function, *Target);
- Context.expect(Action && *Action == rcc::LegalizationAction::Custom,
- "RV64GC must custom-legalize G_VAARG");
- if (!Action)
- return;
- auto Lowered = GISel.Legalizer.lower(**Built, **Function, *Target, *Action);
- Context.expect(Lowered.has_value(), "RV64GC va_arg legalization must succeed");
- if (!Lowered)
- return;
-
- std::vector<rcc::MachineInstr *> Instructions;
- for (rcc::MachineInstr *Instruction = (*Function)->firstBlock()->firstInstruction();
- Instruction != nullptr; Instruction = Instruction->next())
- Instructions.push_back(Instruction);
- constexpr std::array ExpectedOpcodes = {
- rcc::GenericOpcode::G_LOAD, rcc::GenericOpcode::G_CONSTANT,
- rcc::GenericOpcode::G_PTR_ADD, rcc::GenericOpcode::G_CONSTANT,
- rcc::GenericOpcode::G_PTRMASK, rcc::GenericOpcode::G_CONSTANT,
- rcc::GenericOpcode::G_PTR_ADD, rcc::GenericOpcode::G_STORE,
- rcc::GenericOpcode::G_LOAD, rcc::GenericOpcode::G_FREEZE,
- };
- bool OpcodeSequenceMatches = Instructions.size() == ExpectedOpcodes.size();
- for (std::size_t Index = 0; OpcodeSequenceMatches && Index < Instructions.size(); ++Index)
- OpcodeSequenceMatches = Instructions[Index]->opcode() == rcc::opcode(ExpectedOpcodes[Index]);
- Context.expect((*Built)->parent() == nullptr && OpcodeSequenceMatches,
- "va_arg must expand into aligned list access and a live result load");
- if (!OpcodeSequenceMatches)
- return;
-
- const auto *Bias = Instructions[1]->operandAt(1)->getIf<rcc::IntegerImmediateOperand>();
- const auto *Mask = Instructions[3]->operandAt(1)->getIf<rcc::IntegerImmediateOperand>();
- const auto *Increment = Instructions[5]->operandAt(1)->getIf<rcc::IntegerImmediateOperand>();
- Context.expect(Bias != nullptr && Bias->Value.toSignedDecimal() == "3" && Mask != nullptr &&
- Mask->Value.toSignedDecimal() == "-4" && Increment != nullptr &&
- Increment->Value.toSignedDecimal() == "8",
- "va_arg alignment and allocation constants must follow the RV64 ABI");
-
- const auto ListLoadMemory = Instructions[0]->memOperands();
- const auto ListStoreMemory = Instructions[7]->memOperands();
- const auto ValueLoadMemory = Instructions[8]->memOperands();
- Context.expect(ListLoadMemory.size() == 1 && ListStoreMemory.size() == 1 &&
- ValueLoadMemory.size() == 1 && ListLoadMemory.front()->isLoad() &&
- !ListLoadMemory.front()->isStore() && ListStoreMemory.front()->isStore() &&
- !ListStoreMemory.front()->isLoad() && ValueLoadMemory.front()->isLoad() &&
- !ValueLoadMemory.front()->isStore() &&
- ListLoadMemory.front()->description().MemoryType == *Pointer &&
- ListStoreMemory.front()->description().MemoryType == *Pointer &&
- ValueLoadMemory.front()->description().MemoryType == *I64,
- "va_arg expansion must attach typed MMOs to every concrete memory access");
-}
-
-void testFrameIndexLegalization(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(),
- "RV64GC target must be available for frame-index legalization");
- if (!Target)
- return;
- auto Function = rcc::MachineFunction::create("riscv_frame_index",
- rcc::MachineFunctionStableID::fromValue(104));
- if (!Function)
- return;
- auto Block = (*Function)->createBasicBlock();
- auto Pointer = rcc::LowLevelType::makePointer(0, 64);
- auto OtherAddressSpace = rcc::LowLevelType::makePointer(1, 64);
- rcc::StackObjectDesc StackSlot;
- StackSlot.Size = 8;
- StackSlot.Align = *rcc::Alignment::create(8);
- auto Frame = (*Function)->frameInfo().createObject(StackSlot);
- if (!Block || !Pointer || !OtherAddressSpace || !Frame || !(*Function)->appendBlock(**Block))
- return;
- auto Result = (*Function)->registerInfo().createVirtualRegister(*Pointer);
- auto UnsupportedResult = (*Function)->registerInfo().createVirtualRegister(*OtherAddressSpace);
- if (!Result || !UnsupportedResult)
- return;
-
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return;
- auto Legal = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_FRAME_INDEX));
- if (!Legal || !Legal->addDef(*Result) || !Legal->addFrameIndex(*Frame))
- return;
- auto BuiltLegal = Legal->build();
- auto Unsupported = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_FRAME_INDEX));
- if (!BuiltLegal || !Unsupported || !Unsupported->addDef(*UnsupportedResult) ||
- !Unsupported->addFrameIndex(*Frame)) {
- return;
- }
- auto BuiltUnsupported = Unsupported->build();
- if (!BuiltUnsupported)
- return;
-
- auto LegalAction = GISel.Legalizer.classify(**BuiltLegal, **Function, *Target);
- auto UnsupportedAction = GISel.Legalizer.classify(**BuiltUnsupported, **Function, *Target);
- Context.expect(LegalAction && *LegalAction == rcc::LegalizationAction::Legal,
- "RV64GC must legalize G_FRAME_INDEX for p0");
- Context.expect(UnsupportedAction && *UnsupportedAction == rcc::LegalizationAction::Unsupported,
- "RV64GC must reject G_FRAME_INDEX outside p0");
-}
-
-void testPointerArithmeticLegalization(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(), "RV64GC target must be available for pointer legalization");
- if (!Target)
- return;
- auto Pointer = rcc::LowLevelType::makePointer(0, 64);
- auto S64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- auto S32 = rcc::LowLevelType::makeCategoryNeutralScalar(32);
- if (!Pointer || !S64 || !S32)
- return;
- auto Legal = createRegisterOnlyFunction(Context, *Target, rcc::GenericOpcode::G_PTR_ADD,
- {*Pointer}, {*Pointer, *S64});
- auto Unsupported = createRegisterOnlyFunction(Context, *Target, rcc::GenericOpcode::G_PTR_ADD,
- {*Pointer}, {*Pointer, *S32});
- if (!Legal || !Unsupported)
- return;
- auto LegalAction =
- GISel.Legalizer.classify(*Legal->firstBlock()->firstInstruction(), *Legal, *Target);
- auto UnsupportedAction = GISel.Legalizer.classify(*Unsupported->firstBlock()->firstInstruction(),
- *Unsupported, *Target);
- Context.expect(LegalAction && *LegalAction == rcc::LegalizationAction::Legal,
- "RV64GC must legalize G_PTR_ADD for p0 and s64");
- Context.expect(UnsupportedAction && *UnsupportedAction == rcc::LegalizationAction::Unsupported,
- "RV64GC must reject G_PTR_ADD with a non-XLen offset");
-}
-
-void testMemoryOffsetSelection(TestContext &Context) {
- auto Target = rcc::riscv::createTargetDescription();
- Context.expect(Target.has_value(), "RV64GC target must be available for memory selection");
- if (!Target)
- return;
- auto Function = rcc::MachineFunction::create("riscv_memory_offset",
- rcc::MachineFunctionStableID::fromValue(101));
- if (!Function)
- return;
- auto Block = (*Function)->createBasicBlock();
- if (!Block || !(*Function)->appendBlock(**Block))
- return;
- auto I64 = rcc::LowLevelType::makeCategoryNeutralScalar(64);
- auto Pointer = rcc::LowLevelType::makePointer(0, 64);
- if (!I64 || !Pointer)
- return;
- auto Result = (*Function)->registerInfo().createVirtualRegister(*I64);
- auto Address = (*Function)->registerInfo().createVirtualRegister(*Pointer);
- if (!Result || !Address)
- return;
- rcc::MachineIRBuilder Builder(**Function, *Target);
- if (!Builder.setInsertPoint(**Block))
- return;
- auto Pending = Builder.buildInstr(rcc::opcode(rcc::GenericOpcode::G_LOAD));
- if (!Pending || !Pending->addDef(*Result) || !Pending->addUse(*Address))
- return;
- auto Size = rcc::MemorySize::fixed(8);
- auto Align = rcc::Alignment::create(8);
- if (!Size || !Align)
- return;
- rcc::MachineMemOperandDesc Description;
- Description.Accesses = {rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Volatile};
- Description.Size = *Size;
- Description.Align = *Align;
- Description.Base = rcc::MemoryBase::stack();
- Description.Offset = 24;
- auto Memory = (*Function)->createMemOperand(std::move(Description));
- if (!Memory || !Pending->addMemOperand(**Memory) || !Pending->build() ||
- !constrainVirtualRegisters(**Function))
- return;
- rcc::MachineInstr *Instruction = (*Function)->firstBlock()->firstInstruction();
- Context.expect(Instruction != nullptr, "generic load must remain attached to its block");
- if (Instruction == nullptr)
- return;
- auto Action = GISel.Legalizer.classify(*Instruction, **Function, *Target);
- Context.expect(Action && *Action == rcc::LegalizationAction::Legal,
- "RV64GC legalizer must accept volatile scalar loads");
- auto Selection = (*GISel.Selector).select(*Instruction, **Function, *Target);
- Context.expect(Selection.has_value(), "RV64GC selector must select a volatile scalar load");
- if (!Selection)
- return;
- Context.expect(Instruction->opcode() == rcc::Opcode::fromValue(static_cast<std::uint32_t>(
- rcc::riscv::InstructionOpcode::LD)),
- "i64 load with an offset must select LD");
- const rcc::MachineOperand *OffsetOperand = Instruction->operandAt(2);
- const std::int64_t *Offset =
- OffsetOperand == nullptr ? nullptr : OffsetOperand->getIf<std::int64_t>();
- Context.expect(Offset != nullptr && *Offset == 0,
- "selected load must not reinterpret MMO provenance as an address immediate");
- Context.expect(Instruction->memOperands().size() == 1 &&
- Instruction->memOperands().front()->description().Offset == 24,
- "selected load must preserve its MMO provenance offset");
- Context.expect(validatesSelectedFunction(*Target, **Function),
- "selected volatile load must satisfy the target descriptor");
-}
-
-} // namespace
-
-int main() {
- TestContext Context;
- testClassification(Context);
- testNarrowIntegerSelection(Context);
- testScalarSelection(Context);
- testComparisonsAndControl(Context);
- testCompositeScalarSelection(Context);
- testOverflowSelection(Context);
- testAtomicCompareExchangeLegalization(Context);
- testConstantExtensionPreservesLiveSource(Context);
- testIntegerCompareReusesConstantExtension(Context);
- testNarrowAnyExtBinaryArtifacts(Context);
- testMultiplyLibcallWithoutM(Context);
- testAtomicSelection(Context);
- testUnsupportedTypes(Context);
- testVACopyLegalization(Context);
- testVAArgLegalization(Context);
- testFrameIndexLegalization(Context);
- testPointerArithmeticLegalization(Context);
- testMemoryOffsetSelection(Context);
- return Context.result();
-}