aboutsummaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/lib/CodeGen/GenericOpcodes.cpp26
-rw-r--r--backend/lib/Target/RISCV/RISCVCodeGen.cpp191
-rw-r--r--backend/unittests/CodeGen/GenericOpcodesTest.cpp20
-rw-r--r--backend/unittests/Target/RISCVCodeGenTest.cpp89
4 files changed, 288 insertions, 38 deletions
diff --git a/backend/lib/CodeGen/GenericOpcodes.cpp b/backend/lib/CodeGen/GenericOpcodes.cpp
index c6967d5..3c2cee1 100644
--- a/backend/lib/CodeGen/GenericOpcodes.cpp
+++ b/backend/lib/CodeGen/GenericOpcodes.cpp
@@ -948,27 +948,9 @@ private:
}
return VerifyAddress(0, *MemoryOperands.front());
case GO::G_VAARG: {
- if (MemoryOperands.size() != 2)
- return semanticError("G_VAARG requires separate value-load and list-update MMOs");
- const MachineMemOperand *ValueLoad = nullptr;
- const MachineMemOperand *ListStore = nullptr;
- for (const MachineMemOperand *Memory : MemoryOperands) {
- if (Memory->isLoad() == Memory->isStore()) {
- return semanticError("G_VAARG MMOs must each describe exactly one access direction");
- }
- if (Memory->isLoad())
- ValueLoad = Memory;
- else
- ListStore = Memory;
- if (auto Address = VerifyAddress(1, *Memory); !Address)
- return Address;
- }
- if (ValueLoad == nullptr || ListStore == nullptr)
- return semanticError("G_VAARG MMOs must cover both the value load and list update");
- auto ResultType = TypeAt(0);
- if (!ResultType)
- return std::unexpected(ResultType.error());
- return VerifySize(*ResultType, *ValueLoad, SizeRelation::Equal);
+ return MemoryOperands.empty()
+ ? Expected<void>{}
+ : semanticError("G_VAARG must not carry MMOs before legalization");
}
case GO::G_MEMCPY:
case GO::G_MEMCPY_INLINE:
@@ -1314,7 +1296,7 @@ void configureGenericOpcodes(DescriptorRegistry &Registry) {
Registry.configure({GO::G_VASTART}, {ScalarPointerUse0}, 0, {InstructionFlag::MayStore}, false, 0,
true);
Registry.configure({GO::G_VAARG}, {AnyDef0, ScalarPointerUse1, immediate()}, 1,
- {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, true);
+ {InstructionFlag::MayLoad, InstructionFlag::MayStore}, false, 0, false);
Registry.configure({GO::G_SEXT_INREG}, {IntDef0, IntUse0, immediate()}, 1);
Registry.configure(
{GO::G_SHL, GO::G_LSHR, GO::G_ASHR, GO::G_ROTR, GO::G_ROTL, GO::G_USHLSAT, GO::G_SSHLSAT},
diff --git a/backend/lib/Target/RISCV/RISCVCodeGen.cpp b/backend/lib/Target/RISCV/RISCVCodeGen.cpp
index 1129f89..f9bd8a5 100644
--- a/backend/lib/Target/RISCV/RISCVCodeGen.cpp
+++ b/backend/lib/Target/RISCV/RISCVCodeGen.cpp
@@ -2975,6 +2975,185 @@ buildIncomingConversion(MachineFunction &Function, const TargetDescription &Targ
return true;
}
+struct RV64ScalarLayout final {
+ std::uint64_t StoreSize = 0;
+ std::uint64_t AllocationSize = 0;
+ std::uint64_t ABIAlignment = 0;
+};
+
+[[nodiscard]] std::optional<RV64ScalarLayout> rv64ScalarLayout(LLT Type) noexcept {
+ if (Type.isPointer()) {
+ return Type.addressSpace() == 0 && Type.scalarBitWidth() == 64
+ ? std::optional<RV64ScalarLayout>{{8, 8, 8}}
+ : std::nullopt;
+ }
+ if ((!isSupportedIntegerType(Type) && !isRVFloatType(Type)) || Type.scalarBitWidth() == 0) {
+ return std::nullopt;
+ }
+ const std::uint64_t StoreBytes = (Type.scalarBitWidth() + 7U) / 8U;
+ const std::uint64_t AllocationBytes = std::bit_ceil(StoreBytes);
+ return RV64ScalarLayout{StoreBytes, AllocationBytes, std::min<std::uint64_t>(AllocationBytes, 8)};
+}
+
+[[nodiscard]] Expected<const MachineMemOperand *>
+createMemoryAccess(MachineFunction &Function, MemoryAccessFlag Access, LLT Type,
+ std::uint64_t SizeBytes, std::uint64_t AlignmentBytes) {
+ auto Size = MemorySize::fixed(SizeBytes);
+ auto Align = Alignment::create(AlignmentBytes);
+ if (!Size || !Align)
+ return makeUnexpected(ErrorCode::InternalError, "RISC-V memory facts are invalid");
+ MachineMemOperandDesc Description;
+ Description.Accesses = {Access};
+ Description.Size = *Size;
+ Description.MemoryType = Type;
+ Description.Align = *Align;
+ return Function.createMemOperand(std::move(Description));
+}
+
+struct VAArgPlan final {
+ Register Destination;
+ Register ListPointer;
+ LLT DestinationType;
+ LLT PointerType;
+ std::uint64_t Alignment = 0;
+ RV64ScalarLayout ValueLayout;
+};
+
+[[nodiscard]] std::optional<VAArgPlan> vaargPlan(const MachineInstr &Instruction,
+ const MachineFunction &Function) {
+ if (Instruction.opcode() != opcode(GenericOpcode::G_VAARG) || Instruction.operandCount() != 3 ||
+ !Instruction.memOperands().empty()) {
+ return std::nullopt;
+ }
+ const MachineOperand *DestinationOperand = Instruction.operandAt(0);
+ const MachineOperand *ListOperand = Instruction.operandAt(1);
+ const MachineOperand *AlignmentOperand = Instruction.operandAt(2);
+ const Register *Destination =
+ DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>();
+ const Register *ListPointer = ListOperand == nullptr ? nullptr : ListOperand->getIf<Register>();
+ const std::optional<LLT> DestinationType = resultType(Function, Instruction);
+ const std::optional<LLT> PointerType = operandType(Function, Instruction, 1);
+ const auto AlignmentValue = AlignmentOperand == nullptr
+ ? Expected<std::int64_t>(std::unexpected(
+ Error(ErrorCode::InvalidArgument, "missing alignment")))
+ : signedImmediate(*AlignmentOperand);
+ const std::optional<RV64ScalarLayout> ValueLayout =
+ DestinationType ? rv64ScalarLayout(*DestinationType) : std::nullopt;
+ if (Destination == nullptr || ListPointer == nullptr || !Destination->isVirtual() ||
+ !ListPointer->isVirtual() || !DestinationOperand->isDef() || !ListOperand->isUse() ||
+ !DestinationType || !PointerType || !PointerType->isPointer() ||
+ PointerType->addressSpace() != 0 || PointerType->scalarBitWidth() != 64 || !AlignmentValue ||
+ *AlignmentValue <= 0 || !std::has_single_bit(static_cast<std::uint64_t>(*AlignmentValue)) ||
+ !ValueLayout) {
+ return std::nullopt;
+ }
+
+ return VAArgPlan{*Destination,
+ *ListPointer,
+ *DestinationType,
+ *PointerType,
+ static_cast<std::uint64_t>(*AlignmentValue),
+ *ValueLayout};
+}
+
+[[nodiscard]] Expected<void> lowerVAArg(MachineInstr &Instruction, MachineFunction &Function,
+ const TargetDescription &Target) {
+ const auto Plan = vaargPlan(Instruction, Function);
+ if (!Plan) {
+ return makeUnexpected(ErrorCode::VerificationError,
+ "RISC-V va_arg legalization requires a scalar result, p0 list, and "
+ "power-of-two alignment");
+ }
+
+ auto ListLoadMemory =
+ createMemoryAccess(Function, MemoryAccessFlag::Load, Plan->PointerType, 8, 8);
+ if (!ListLoadMemory)
+ return std::unexpected(ListLoadMemory.error());
+ auto ListStoreMemory =
+ createMemoryAccess(Function, MemoryAccessFlag::Store, Plan->PointerType, 8, 8);
+ if (!ListStoreMemory)
+ return std::unexpected(ListStoreMemory.error());
+
+ auto CurrentList = Function.registerInfo().createVirtualRegister(Plan->PointerType);
+ if (!CurrentList)
+ return std::unexpected(CurrentList.error());
+ MachineIRBuilder Builder(Function, Target);
+ if (auto Point = Builder.setInsertPointBefore(Instruction); !Point)
+ return std::unexpected(Point.error());
+ auto ListLoad = Builder.buildInstr(opcode(GenericOpcode::G_LOAD));
+ if (!ListLoad)
+ return std::unexpected(ListLoad.error());
+ if (auto Added = ListLoad->addDef(*CurrentList); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = ListLoad->addUse(Plan->ListPointer); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = ListLoad->addMemOperand(**ListLoadMemory); !Added)
+ return std::unexpected(Added.error());
+ if (auto Built = ListLoad->build(); !Built)
+ return std::unexpected(Built.error());
+
+ Register ValueAddress = *CurrentList;
+ if (Plan->Alignment > 1) {
+ auto AlignmentOffset =
+ buildIntegerConstantBefore(Builder, Instruction, 64, Plan->Alignment - 1U);
+ if (!AlignmentOffset)
+ return std::unexpected(AlignmentOffset.error());
+ auto BiasedAddress = buildBinaryBefore(Builder, Instruction, GenericOpcode::G_PTR_ADD,
+ Plan->PointerType, ValueAddress, *AlignmentOffset);
+ if (!BiasedAddress)
+ return std::unexpected(BiasedAddress.error());
+ auto AlignmentMask =
+ buildIntegerConstantBefore(Builder, Instruction, 64, ~(Plan->Alignment - std::uint64_t{1}));
+ if (!AlignmentMask)
+ return std::unexpected(AlignmentMask.error());
+ auto AlignedAddress = buildBinaryBefore(Builder, Instruction, GenericOpcode::G_PTRMASK,
+ Plan->PointerType, *BiasedAddress, *AlignmentMask);
+ if (!AlignedAddress)
+ return std::unexpected(AlignedAddress.error());
+ ValueAddress = *AlignedAddress;
+ }
+
+ auto Increment =
+ buildIntegerConstantBefore(Builder, Instruction, 64, Plan->ValueLayout.AllocationSize);
+ if (!Increment)
+ return std::unexpected(Increment.error());
+ auto NextList = buildBinaryBefore(Builder, Instruction, GenericOpcode::G_PTR_ADD,
+ Plan->PointerType, ValueAddress, *Increment);
+ if (!NextList)
+ return std::unexpected(NextList.error());
+ auto ListStore = Builder.buildInstr(opcode(GenericOpcode::G_STORE));
+ if (!ListStore)
+ return std::unexpected(ListStore.error());
+ if (auto Added = ListStore->addUse(*NextList); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = ListStore->addUse(Plan->ListPointer); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = ListStore->addMemOperand(**ListStoreMemory); !Added)
+ return std::unexpected(Added.error());
+ if (auto Built = ListStore->build(); !Built)
+ return std::unexpected(Built.error());
+
+ if (!Function.registerInfo().useEmpty(Plan->Destination)) {
+ auto ValueLoadMemory =
+ createMemoryAccess(Function, MemoryAccessFlag::Load, Plan->DestinationType,
+ Plan->ValueLayout.StoreSize, Plan->ValueLayout.ABIAlignment);
+ if (!ValueLoadMemory)
+ return std::unexpected(ValueLoadMemory.error());
+ auto ValueLoad = Builder.buildInstr(opcode(GenericOpcode::G_LOAD));
+ if (!ValueLoad)
+ return std::unexpected(ValueLoad.error());
+ if (auto Added = ValueLoad->addDef(Plan->Destination); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = ValueLoad->addUse(ValueAddress); !Added)
+ return std::unexpected(Added.error());
+ if (auto Added = ValueLoad->addMemOperand(**ValueLoadMemory); !Added)
+ return std::unexpected(Added.error());
+ if (auto Built = ValueLoad->build(); !Built)
+ return std::unexpected(Built.error());
+ }
+ return eraseInstruction(Instruction);
+}
+
struct VACopyPlan final {
Register Destination;
Register Source;
@@ -3091,15 +3270,17 @@ public:
case GenericOpcode::G_INTRINSIC_W_SIDE_EFFECTS:
return vacopyPlan(Instruction, Function) ? LegalizationAction::Custom
: LegalizationAction::Unsupported;
+ case GenericOpcode::G_VAARG:
+ return vaargPlan(Instruction, Function) ? LegalizationAction::Custom
+ : LegalizationAction::Unsupported;
case GenericOpcode::G_BR:
return LegalizationAction::Legal;
case GenericOpcode::G_BRCOND: {
const std::optional<LLT> Condition = operandType(Function, Instruction, 0);
if (!Condition || !isScalarGPRType(*Condition))
return LegalizationAction::Unsupported;
- return canLowerNarrowBranchCondition(Instruction, Function)
- ? LegalizationAction::Custom
- : LegalizationAction::Legal;
+ return canLowerNarrowBranchCondition(Instruction, Function) ? LegalizationAction::Custom
+ : LegalizationAction::Legal;
}
case GenericOpcode::G_PHI: {
if (!Type)
@@ -3720,6 +3901,10 @@ public:
Instruction.opcode() == opcode(GenericOpcode::G_INTRINSIC_W_SIDE_EFFECTS)) {
return lowerVACopy(Instruction, Function, Target);
}
+ if (Action == LegalizationAction::Custom &&
+ Instruction.opcode() == opcode(GenericOpcode::G_VAARG)) {
+ return lowerVAArg(Instruction, Function, Target);
+ }
if (Instruction.opcode() == opcode(GenericOpcode::G_MUL) && !Features.has(RISCVExtension::M)) {
if (Action == LegalizationAction::Custom)
return widenNarrowMultiply(Instruction, Function, Target);
diff --git a/backend/unittests/CodeGen/GenericOpcodesTest.cpp b/backend/unittests/CodeGen/GenericOpcodesTest.cpp
index c9ed8d5..4527004 100644
--- a/backend/unittests/CodeGen/GenericOpcodesTest.cpp
+++ b/backend/unittests/CodeGen/GenericOpcodesTest.cpp
@@ -649,21 +649,12 @@ void testInstructionMemorySemantics(TestContext &Context) {
VarArgOperands.push_back(rcc::MachineOperand::immediate(8));
rcc::MachineInstr &VarArg =
createInstruction(**Function, rcc::GenericOpcode::G_VAARG, std::move(VarArgOperands));
+ Context.expect(Set->validateInstruction(VarArg, (*Function)->registerInfo()).has_value(),
+ "G_VAARG must accept LLVM's canonical form without MMOs");
addMemOperand(**Function, VarArg, memoryDescription({rcc::MemoryAccessFlag::Load}, 8));
addMemOperand(**Function, VarArg, memoryDescription({rcc::MemoryAccessFlag::Store}, 8));
- Context.expect(Set->validateInstruction(VarArg, (*Function)->registerInfo()).has_value(),
- "G_VAARG must accept separate value-load and list-update MMOs");
-
- std::vector<rcc::MachineOperand> CombinedVarArgOperands;
- CombinedVarArgOperands.push_back(registerOperand(Dst64, true));
- CombinedVarArgOperands.push_back(registerOperand(Address0, false));
- CombinedVarArgOperands.push_back(rcc::MachineOperand::immediate(8));
- rcc::MachineInstr &CombinedVarArg =
- createInstruction(**Function, rcc::GenericOpcode::G_VAARG, std::move(CombinedVarArgOperands));
- addMemOperand(**Function, CombinedVarArg,
- memoryDescription({rcc::MemoryAccessFlag::Load, rcc::MemoryAccessFlag::Store}, 8));
- Context.expect(!Set->validateInstruction(CombinedVarArg, (*Function)->registerInfo()),
- "G_VAARG must reject a combined read-write MMO in place of two accesses");
+ Context.expect(!Set->validateInstruction(VarArg, (*Function)->registerInfo()),
+ "G_VAARG must reject MMOs before legalization");
std::vector<rcc::MachineOperand> MemoryTransferOperands;
MemoryTransferOperands.push_back(registerOperand(Address0, false));
@@ -706,6 +697,7 @@ void testSemanticFlags(TestContext &Context) {
const auto *Load = Find(rcc::GenericOpcode::G_LOAD);
const auto *Atomic = Find(rcc::GenericOpcode::G_ATOMIC_CMPXCHG);
+ const auto *VarArg = Find(rcc::GenericOpcode::G_VAARG);
const auto *Branch = Find(rcc::GenericOpcode::G_BRINDIRECT);
const auto *Strict = Find(rcc::GenericOpcode::G_STRICT_FMA);
const auto *InlineAsm = Find(rcc::GenericOpcode::INLINEASM);
@@ -714,6 +706,8 @@ void testSemanticFlags(TestContext &Context) {
Load->RequiresMemoryOperands && Atomic != nullptr &&
Atomic->Flags.has(rcc::InstructionFlag::MayLoad) &&
Atomic->Flags.has(rcc::InstructionFlag::MayStore) && Atomic->RequiresMemoryOperands &&
+ VarArg != nullptr && VarArg->Flags.has(rcc::InstructionFlag::MayLoad) &&
+ VarArg->Flags.has(rcc::InstructionFlag::MayStore) && !VarArg->RequiresMemoryOperands &&
Branch != nullptr && Branch->Flags.has(rcc::InstructionFlag::Terminator) &&
Branch->Flags.has(rcc::InstructionFlag::IndirectBranch) &&
Branch->Flags.has(rcc::InstructionFlag::Barrier) && Strict != nullptr &&
diff --git a/backend/unittests/Target/RISCVCodeGenTest.cpp b/backend/unittests/Target/RISCVCodeGenTest.cpp
index 4f675a4..97728ae 100644
--- a/backend/unittests/Target/RISCVCodeGenTest.cpp
+++ b/backend/unittests/Target/RISCVCodeGenTest.cpp
@@ -1538,6 +1538,94 @@ void testVACopyLegalization(TestContext &Context) {
"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::LLT::makePointer(0, 64);
+ auto I64 = rcc::LLT::makeInteger(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 = rcc::riscv::legalizerInfo().classify(**Built, **Function, *Target);
+ Context.expect(Action && *Action == rcc::LegalizationAction::Custom,
+ "RV64GC must custom-legalize G_VAARG");
+ if (!Action)
+ return;
+ auto Lowered = rcc::riscv::legalizerInfo().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 testMemoryOffsetSelection(TestContext &Context) {
auto Target = rcc::riscv::createTargetDescription();
Context.expect(Target.has_value(), "RV64GC target must be available for memory selection");
@@ -1622,6 +1710,7 @@ int main() {
testAtomicSelection(Context);
testUnsupportedTypes(Context);
testVACopyLegalization(Context);
+ testVAArgLegalization(Context);
testMemoryOffsetSelection(Context);
return Context.result();
}