diff options
Diffstat (limited to 'backend/lib/CodeGen/GlobalISel/LegalizerHelper.cpp')
| -rw-r--r-- | backend/lib/CodeGen/GlobalISel/LegalizerHelper.cpp | 7120 |
1 files changed, 7120 insertions, 0 deletions
diff --git a/backend/lib/CodeGen/GlobalISel/LegalizerHelper.cpp b/backend/lib/CodeGen/GlobalISel/LegalizerHelper.cpp new file mode 100644 index 0000000..8bf6e80 --- /dev/null +++ b/backend/lib/CodeGen/GlobalISel/LegalizerHelper.cpp @@ -0,0 +1,7120 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "rcc/CodeGen/GlobalISel/LegalizerHelper.h" + +#include "rcc/CodeGen/GlobalISel/GISelChangeObserver.h" +#include "rcc/CodeGen/GlobalISel/Utils.h" +#include "rcc/CodeGen/MachineBasicBlock.h" +#include "rcc/CodeGen/MachineFunction.h" +#include "rcc/CodeGen/MachineInstr.h" +#include "rcc/CodeGen/MachineMemOperand.h" +#include "rcc/CodeGen/MachineOperand.h" +#include "rcc/CodeGen/MachineRegisterInfo.h" +#include "rcc/CodeGen/TargetCallLowering.h" + +#include <algorithm> +#include <bit> +#include <cstddef> +#include <cstdint> +#include <initializer_list> +#include <limits> +#include <numeric> +#include <optional> +#include <span> +#include <string> +#include <string_view> +#include <utility> +#include <vector> + +namespace rcc { +namespace { + +struct ScalarLayout final { + std::uint64_t StoreSize = 0; + std::uint64_t AllocationSize = 0; + std::uint64_t ABIAlignment = 0; +}; + +[[nodiscard]] Expected<std::int64_t> signedImmediate(const MachineOperand &Operand) { + if (const std::int64_t *Value = Operand.getIf<std::int64_t>()) + return *Value; + const auto *Integer = Operand.getIf<IntegerImmediateOperand>(); + if (Integer == nullptr || Integer->Value.bitWidth() == 0 || Integer->Value.bitWidth() > 64) { + return makeUnexpected(ErrorCode::VerificationError, + "legalizer requires an immediate no wider than 64 bits"); + } + std::uint64_t Raw = Integer->Value.words().empty() ? 0 : Integer->Value.words().front(); + const std::uint32_t Width = Integer->Value.bitWidth(); + if (Width < 64 && (Raw & (std::uint64_t{1} << (Width - 1U))) != 0) + Raw |= ~std::uint64_t{0} << Width; + return std::bit_cast<std::int64_t>(Raw); +} + +[[nodiscard]] std::optional<ScalarLayout> scalarLayout(LowLevelType Type) noexcept { + if (!Type.isScalarValue() || Type.scalarBitWidth() == 0) + return std::nullopt; + const std::uint64_t StoreSize = (Type.scalarBitWidth() + 7U) / 8U; + const std::uint64_t AllocationSize = std::bit_ceil(StoreSize); + return ScalarLayout{StoreSize, AllocationSize, std::min<std::uint64_t>(AllocationSize, 8)}; +} + +[[nodiscard]] Expected<const MachineMemOperand *> createMemoryAccess(MachineFunction &Function, + MemoryAccessFlag Access, + LowLevelType Type, + const ScalarLayout &Layout) { + auto Size = MemorySize::fixed(Layout.StoreSize); + auto Align = Alignment::create(Layout.ABIAlignment); + if (!Size || !Align) + return makeUnexpected(ErrorCode::InternalError, + "legalizer cannot represent scalar memory layout"); + MachineMemOperandDesc Description; + Description.Accesses = {Access}; + Description.Size = *Size; + Description.MemoryType = Type; + Description.Align = *Align; + return Function.createMemOperand(std::move(Description)); +} + +[[nodiscard]] Expected<Register> resultRegister(MachineInstr &Instruction) { + const MachineOperand *Definition = Instruction.operandAt(0); + const Register *Result = Definition == nullptr ? nullptr : Definition->getIf<Register>(); + if (Result == nullptr || !Definition->isDef()) { + return makeUnexpected(ErrorCode::InternalError, + "machine IR builder returned an instruction without a register result"); + } + return *Result; +} + +[[nodiscard]] Expected<void> setInsertPointAtFirstTerminator(MachineIRBuilder &Builder, + MachineBasicBlock &Block) { + for (MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = + Builder.target().instructionDescriptor(Instruction->opcode()); + if (Descriptor == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "PHI predecessor contains an unknown instruction"); + if (Descriptor->Flags.has(InstructionFlag::Terminator)) + return Builder.setInsertPointBefore(*Instruction); + } + return Builder.setInsertPoint(Block); +} + +[[nodiscard]] Expected<void> setInsertPointAfterPHIs(MachineIRBuilder &Builder, + MachineBasicBlock &Block) { + for (MachineInstr *Instruction = Block.firstInstruction(); Instruction != nullptr; + Instruction = Instruction->next()) { + const InstructionDescriptor *Descriptor = + Builder.target().instructionDescriptor(Instruction->opcode()); + if (Descriptor == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "PHI block contains an unknown instruction"); + if (!Descriptor->Flags.has(InstructionFlag::Phi) && + !Descriptor->Flags.has(InstructionFlag::Meta)) { + return Builder.setInsertPointBefore(*Instruction); + } + } + return Builder.setInsertPoint(Block); +} + +[[nodiscard]] Expected<Register> buildConstantRegister(MachineIRBuilder &Builder, LowLevelType Type, + APInt Value) { + auto Built = Builder.buildConstant(Type, std::move(Value)); + if (!Built) + return std::unexpected(Built.error()); + return resultRegister(**Built); +} + +[[nodiscard]] Expected<Register> buildConstantRegister(MachineIRBuilder &Builder, LowLevelType Type, + std::int64_t Value) { + auto Immediate = APInt::fromSigned(Type.scalarBitWidth(), Value); + if (!Immediate) + return std::unexpected(Immediate.error()); + return buildConstantRegister(Builder, Type, std::move(*Immediate)); +} + +[[nodiscard]] Expected<Register> buildResultInstruction(MachineIRBuilder &Builder, + GenericOpcode Generic, LowLevelType Type, + std::initializer_list<Register> Sources) { + auto Built = Builder.buildInstr(opcode(Generic), {Type}, Sources); + if (!Built) + return std::unexpected(Built.error()); + return resultRegister(**Built); +} + +[[nodiscard]] Expected<Register> +buildFlaggedBinaryInstruction(MachineIRBuilder &Builder, GenericOpcode Generic, LowLevelType Type, + Register Left, Register Right, MachineInstrFlags Flags) { + auto Destination = Builder.function().registerInfo().createVirtualRegister(Type); + if (!Destination) + return std::unexpected(Destination.error()); + auto Pending = Builder.buildInstr(opcode(Generic)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Added = Pending->addDef(*Destination); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(Left); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(Right); !Added) + return std::unexpected(Added.error()); + if (auto Set = Pending->setFlags(Flags); !Set) + return std::unexpected(Set.error()); + if (auto Built = Pending->build(); !Built) + return std::unexpected(Built.error()); + return *Destination; +} + +[[nodiscard]] Expected<std::int64_t> addMemoryOffset(std::int64_t Offset, + std::uint64_t ByteOffset) { + if (ByteOffset > static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max())) { + return makeUnexpected(ErrorCode::Unsupported, + "memory split offset exceeds the representable machine memory range"); + } + const auto SignedByteOffset = static_cast<std::int64_t>(ByteOffset); + if (Offset > std::numeric_limits<std::int64_t>::max() - SignedByteOffset) { + return makeUnexpected(ErrorCode::Unsupported, + "memory split offset exceeds the representable machine memory range"); + } + return Offset + SignedByteOffset; +} + +[[nodiscard]] Expected<const MachineMemOperand *> +deriveMemoryOperand(MachineFunction &Function, const MachineMemOperand &Original, + LowLevelType MemoryType, std::uint64_t ByteOffset) { + if (!MemoryType.isScalarValue() || MemoryType.minimumBitWidth() == 0 || + MemoryType.minimumBitWidth() % 8U != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "memory splitting requires a byte-sized scalar memory type"); + } + + const std::uint64_t ByteSize = MemoryType.minimumBitWidth() / 8U; + auto Size = MemorySize::fixed(ByteSize); + if (!Size) + return std::unexpected(Size.error()); + + MachineMemOperandDesc Description = Original.description(); + auto Offset = addMemoryOffset(Description.Offset, ByteOffset); + if (!Offset) + return std::unexpected(Offset.error()); + Description.Offset = *Offset; + if (Description.OffsetRange) { + auto Lower = addMemoryOffset(Description.OffsetRange->Lower, ByteOffset); + auto Upper = addMemoryOffset(Description.OffsetRange->Upper, ByteOffset); + if (!Lower) + return std::unexpected(Lower.error()); + if (!Upper) + return std::unexpected(Upper.error()); + Description.OffsetRange = MemoryOffsetRange{*Lower, *Upper}; + } + + const std::optional<Alignment> BaseAlignment = Description.BaseAlign; + std::uint64_t EffectiveAlignment = BaseAlignment.value_or(Description.Align).bytes(); + if (Description.Offset != 0) { + const auto OffsetBits = static_cast<std::uint64_t>(Description.Offset); + const std::uint64_t OffsetAlignment = std::uint64_t{1} << std::countr_zero(OffsetBits); + EffectiveAlignment = std::min(EffectiveAlignment, OffsetAlignment); + } + auto AlignmentValue = Alignment::create(EffectiveAlignment); + if (!AlignmentValue) + return std::unexpected(AlignmentValue.error()); + + Description.Size = *Size; + Description.MemoryType = MemoryType; + Description.Align = *AlignmentValue; + Description.BaseAlign = BaseAlignment; + return Function.createMemOperand(std::move(Description)); +} + +[[nodiscard]] Expected<Register> +buildObjectPointerOffset(MachineIRBuilder &Builder, Register Pointer, std::uint64_t ByteOffset) { + if (ByteOffset == 0) + return Pointer; + + auto PointerType = Builder.function().registerInfo().getType(Pointer); + if (!PointerType) + return std::unexpected(PointerType.error()); + if (!PointerType->isPointer()) { + return makeUnexpected(ErrorCode::VerificationError, + "memory splitting requires a pointer address operand"); + } + const std::uint32_t PointerBits = PointerType->scalarBitWidth(); + if (PointerBits < 64U && (ByteOffset >> PointerBits) != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "memory split offset does not fit the pointer index type"); + } + auto OffsetType = LowLevelType::makeCategoryNeutralScalar(PointerBits); + if (!OffsetType) + return std::unexpected(OffsetType.error()); + auto OffsetValue = APInt::fromUnsigned(PointerBits, ByteOffset); + if (!OffsetValue) + return std::unexpected(OffsetValue.error()); + auto Offset = buildConstantRegister(Builder, *OffsetType, std::move(*OffsetValue)); + if (!Offset) + return std::unexpected(Offset.error()); + return buildFlaggedBinaryInstruction( + Builder, GenericOpcode::G_PTR_ADD, *PointerType, Pointer, *Offset, + MachineInstrFlags{MachineInstrFlag::NoUnsignedWrap, MachineInstrFlag::InBounds}); +} + +[[nodiscard]] Expected<APInt> repeatedByte(std::uint32_t BitWidth, std::uint8_t Byte) { + if (BitWidth == 0 || BitWidth > APInt::MaxBitWidth || BitWidth % 8U != 0) + return makeUnexpected(ErrorCode::InvalidArgument, + "repeated-byte integer has an invalid bit width"); + constexpr std::uint64_t ByteSplat = 0x0101010101010101ULL; + std::vector<std::uint64_t> Words((static_cast<std::size_t>(BitWidth) + 63U) / 64U, + ByteSplat * Byte); + const std::uint32_t HighBits = BitWidth % 64U; + if (HighBits != 0) + Words.back() &= (std::uint64_t{1} << HighBits) - 1U; + return APInt::create(BitWidth, Words); +} + +[[nodiscard]] Expected<APInt> oneBitSet(std::uint32_t BitWidth, std::uint32_t Bit) { + if (BitWidth == 0 || BitWidth > APInt::MaxBitWidth || Bit >= BitWidth) + return makeUnexpected(ErrorCode::InvalidArgument, + "single-bit integer has an invalid bit position"); + std::vector<std::uint64_t> Words((static_cast<std::size_t>(BitWidth) + 63U) / 64U, 0); + Words[Bit / 64U] = std::uint64_t{1} << (Bit % 64U); + return APInt::create(BitWidth, Words); +} + +[[nodiscard]] Expected<Register> buildICmpRegister(MachineIRBuilder &Builder, + IntegerPredicate Predicate, + LowLevelType ResultType, Register Left, + Register Right) { + auto Built = Builder.buildICmp(Predicate, ResultType, Left, Right); + if (!Built) + return std::unexpected(Built.error()); + return resultRegister(**Built); +} + +[[nodiscard]] IntegerPredicate minMaxToCompare(GenericOpcode Generic) { + switch (Generic) { + case GenericOpcode::G_SMIN: + return IntegerPredicate::SignedLessThan; + case GenericOpcode::G_SMAX: + return IntegerPredicate::SignedGreaterThan; + case GenericOpcode::G_UMIN: + return IntegerPredicate::UnsignedLessThan; + case GenericOpcode::G_UMAX: + return IntegerPredicate::UnsignedGreaterThan; + default: + std::unreachable(); + } +} + +[[nodiscard]] bool isEqualityPredicate(IntegerPredicate Predicate) noexcept { + return Predicate == IntegerPredicate::Equal || Predicate == IntegerPredicate::NotEqual; +} + +[[nodiscard]] Expected<IntegerPredicate> unsignedPredicate(IntegerPredicate Predicate) { + switch (Predicate) { + case IntegerPredicate::Equal: + case IntegerPredicate::NotEqual: + case IntegerPredicate::UnsignedGreaterThan: + case IntegerPredicate::UnsignedGreaterEqual: + case IntegerPredicate::UnsignedLessThan: + case IntegerPredicate::UnsignedLessEqual: + return Predicate; + case IntegerPredicate::SignedGreaterThan: + return IntegerPredicate::UnsignedGreaterThan; + case IntegerPredicate::SignedGreaterEqual: + return IntegerPredicate::UnsignedGreaterEqual; + case IntegerPredicate::SignedLessThan: + return IntegerPredicate::UnsignedLessThan; + case IntegerPredicate::SignedLessEqual: + return IntegerPredicate::UnsignedLessEqual; + case IntegerPredicate::Count: + return makeUnexpected(ErrorCode::VerificationError, + "integer comparison has an invalid predicate"); + } + return makeUnexpected(ErrorCode::VerificationError, + "integer comparison has an invalid predicate"); +} + +[[nodiscard]] Expected<LowLevelType> booleanTypeFor(LowLevelType Type) { + auto BooleanElementType = LowLevelType::makeCategoryNeutralScalar(1); + if (!BooleanElementType) + return std::unexpected(BooleanElementType.error()); + if (!Type.isVector()) + return *BooleanElementType; + if (Type.isScalableVector()) + return LowLevelType::makeScalableVector(Type.minimumElementCount(), *BooleanElementType); + return LowLevelType::makeFixedVector(Type.minimumElementCount(), *BooleanElementType); +} + +[[nodiscard]] Expected<Register> buildConstantBefore(MachineIRBuilder &Builder, + MachineInstr &Anchor, LowLevelType Type, + std::uint64_t Value) { + auto Immediate = APInt::fromUnsigned(Type.scalarBitWidth(), Value); + if (!Immediate) + return std::unexpected(Immediate.error()); + if (auto Point = Builder.setInsertPointBefore(Anchor); !Point) + return std::unexpected(Point.error()); + return buildConstantRegister(Builder, Type, std::move(*Immediate)); +} + +[[nodiscard]] Expected<Register> buildBinaryBefore(MachineIRBuilder &Builder, MachineInstr &Anchor, + GenericOpcode Generic, LowLevelType Type, + Register Left, Register Right) { + auto Result = Builder.function().registerInfo().createVirtualRegister(Type); + if (!Result) + return std::unexpected(Result.error()); + if (auto Point = Builder.setInsertPointBefore(Anchor); !Point) + return std::unexpected(Point.error()); + auto Pending = Builder.buildInstr(opcode(Generic)); + if (!Pending) + return std::unexpected(Pending.error()); + if (auto Added = Pending->addDef(*Result); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(Left); !Added) + return std::unexpected(Added.error()); + if (auto Added = Pending->addUse(Right); !Added) + return std::unexpected(Added.error()); + auto Built = Pending->build(); + if (!Built) + return std::unexpected(Built.error()); + return *Result; +} + +[[nodiscard]] Expected<std::string> integerLibcallName(GenericOpcode Generic, + std::uint32_t BitWidth) { + std::string_view Prefix; + switch (Generic) { + case GenericOpcode::G_MUL: + Prefix = "__mul"; + break; + case GenericOpcode::G_SDIV: + Prefix = "__div"; + break; + case GenericOpcode::G_UDIV: + Prefix = "__udiv"; + break; + case GenericOpcode::G_SREM: + Prefix = "__mod"; + break; + case GenericOpcode::G_UREM: + Prefix = "__umod"; + break; + default: + return makeUnexpected(ErrorCode::Unsupported, + "integer libcall has no runtime function mapping"); + } + + std::string_view Suffix; + switch (BitWidth) { + case 32: + Suffix = "si3"; + break; + case 64: + Suffix = "di3"; + break; + case 128: + Suffix = "ti3"; + break; + default: + return makeUnexpected(ErrorCode::Unsupported, + "integer libcall requires a 32, 64, or 128-bit scalar"); + } + return std::string(Prefix) + std::string(Suffix); +} + +[[nodiscard]] ABIExtensionKind integerLibcallExtension(GenericOpcode Generic, + std::uint32_t BitWidth) noexcept { + if (BitWidth >= 64) + return ABIExtensionKind::None; + if (Generic == GenericOpcode::G_SDIV || Generic == GenericOpcode::G_SREM) + return ABIExtensionKind::SignExtend; + if (Generic == GenericOpcode::G_UDIV || Generic == GenericOpcode::G_UREM) + return ABIExtensionKind::ZeroExtend; + return ABIExtensionKind::AnyExtend; +} + +[[nodiscard]] Expected<std::string_view> integerLibcallTypeCode(std::uint32_t BitWidth) { + switch (BitWidth) { + case 32: + return "si"; + case 64: + return "di"; + case 128: + return "ti"; + default: + return makeUnexpected(ErrorCode::Unsupported, + "conversion libcall requires a 32, 64, or 128-bit integer"); + } +} + +[[nodiscard]] Expected<std::string_view> floatingLibcallTypeCode(std::uint32_t BitWidth) { + switch (BitWidth) { + case 32: + return "sf"; + case 64: + return "df"; + case 128: + return "tf"; + default: + return makeUnexpected(ErrorCode::Unsupported, + "conversion libcall requires a 32, 64, or 128-bit float"); + } +} + +[[nodiscard]] Expected<std::string> floatingBinaryLibcallName(GenericOpcode Generic, + std::uint32_t BitWidth) { + if (Generic != GenericOpcode::G_FREM) { + return makeUnexpected(ErrorCode::Unsupported, + "floating libcall has no runtime function mapping"); + } + switch (BitWidth) { + case 32: + return "fmodf"; + case 64: + return "fmod"; + case 128: + return "fmodl"; + default: + return makeUnexpected(ErrorCode::Unsupported, + "floating libcall requires a 32, 64, or 128-bit scalar"); + } +} + +[[nodiscard]] Expected<std::string> conversionLibcallName(GenericOpcode Generic, + std::uint32_t DestinationWidth, + std::uint32_t SourceWidth) { + const bool DestinationIsFloat = + Generic == GenericOpcode::G_SITOFP || Generic == GenericOpcode::G_UITOFP || + Generic == GenericOpcode::G_FPEXT || Generic == GenericOpcode::G_FPTRUNC; + const bool SourceIsFloat = + Generic == GenericOpcode::G_FPTOSI || Generic == GenericOpcode::G_FPTOUI || + Generic == GenericOpcode::G_FPEXT || Generic == GenericOpcode::G_FPTRUNC; + auto DestinationCode = DestinationIsFloat ? floatingLibcallTypeCode(DestinationWidth) + : integerLibcallTypeCode(DestinationWidth); + auto SourceCode = + SourceIsFloat ? floatingLibcallTypeCode(SourceWidth) : integerLibcallTypeCode(SourceWidth); + if (!DestinationCode) + return std::unexpected(DestinationCode.error()); + if (!SourceCode) + return std::unexpected(SourceCode.error()); + + std::string Name; + switch (Generic) { + case GenericOpcode::G_FPEXT: + Name = "__extend"; + break; + case GenericOpcode::G_FPTRUNC: + Name = "__trunc"; + break; + case GenericOpcode::G_FPTOSI: + Name = "__fix"; + break; + case GenericOpcode::G_FPTOUI: + Name = "__fixuns"; + break; + case GenericOpcode::G_SITOFP: + Name = "__float"; + break; + case GenericOpcode::G_UITOFP: + Name = "__floatun"; + break; + default: + return makeUnexpected(ErrorCode::Unsupported, "opcode has no conversion libcall mapping"); + } + Name += *SourceCode; + Name += *DestinationCode; + if (Generic == GenericOpcode::G_FPEXT || Generic == GenericOpcode::G_FPTRUNC) + Name += '2'; + return Name; +} + +[[nodiscard]] Expected<ABIValue> +libcallABIValue(Register Value, LowLevelType Type, ABIValueCategory Category, + std::uint32_t OriginalValue, std::uint16_t FragmentIndex, + std::uint16_t FragmentCount, std::uint32_t FragmentOffsetBytes, + Alignment OriginalAlignment, ABIExtensionKind Extension) { + auto OneByte = Alignment::create(1); + if (!OneByte) + return std::unexpected(OneByte.error()); + return ABIValue{ + .Value = Value, + .Type = Type, + .Category = Category, + .OriginalValue = OriginalValue, + .FragmentIndex = FragmentIndex, + .FragmentCount = FragmentCount, + .FragmentOffsetBytes = FragmentOffsetBytes, + .OriginalAlignment = OriginalAlignment, + .Extension = Extension, + .Flags = {}, + .ByValSizeBytes = 0, + .ByValAlignment = *OneByte, + }; +} + +} // namespace + +LegalizerHelper::LegalizerHelper(MachineFunction &Function, const LegalizerInfo &Info, + GISelChangeObserver &Observer, MachineIRBuilder &MIRBuilder, + const LibcallLoweringInfo *Libcalls) + : MIRBuilder(MIRBuilder), Observer(Observer), Function(Function), MRI(Function.registerInfo()), + Info(Info), Libcalls(Libcalls) {} + +Expected<void> LegalizerHelper::widenScalarMergeValues(MachineInstr &Instruction, + std::size_t TypeIndex, + LowLevelType WideType) { + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::Unsupported, + "G_MERGE_VALUES widening requires type index one"); + if (Instruction.operandCount() < 3) + return makeUnexpected(ErrorCode::VerificationError, + "G_MERGE_VALUES requires a destination and sources"); + + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + const Register *FirstSource = Instruction.operandAt(1)->getIf<Register>(); + if (Destination == nullptr || FirstSource == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_MERGE_VALUES requires register operands"); + auto DestinationType = MRI.getType(*Destination); + auto SourceType = MRI.getType(*FirstSource); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (!SourceType) + return std::unexpected(SourceType.error()); + if (DestinationType->isVector()) + return makeUnexpected(ErrorCode::Unsupported, + "vector G_MERGE_VALUES widening is not supported"); + + const std::uint64_t DestinationSize = DestinationType->minimumBitWidth(); + const std::uint64_t SourceSize = SourceType->minimumBitWidth(); + const std::uint64_t WideSize = WideType.minimumBitWidth(); + const std::uint64_t MergeCount = (DestinationSize + WideSize - 1U) / WideSize; + const std::size_t SourceCount = Instruction.operandCount() - 1U; + const std::uint64_t PartSize = DestinationSize / SourceCount; + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + if (WideSize >= DestinationSize) { + auto BuiltResult = MIRBuilder.buildZExt(WideType, *FirstSource); + if (!BuiltResult) + return std::unexpected(BuiltResult.error()); + auto Result = resultRegister(**BuiltResult); + if (!Result) + return std::unexpected(Result.error()); + Register ResultRegister = *Result; + + for (std::size_t Index = 2; Index != Instruction.operandCount(); ++Index) { + const Register *Source = Instruction.operandAt(Index)->getIf<Register>(); + if (Source == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_MERGE_VALUES requires register sources"); + auto ActualSourceType = MRI.getType(*Source); + if (!ActualSourceType) + return std::unexpected(ActualSourceType.error()); + if (*ActualSourceType != *SourceType || ActualSourceType->minimumBitWidth() != PartSize) + return makeUnexpected(ErrorCode::VerificationError, + "G_MERGE_VALUES source types are inconsistent"); + + auto BuiltExtendedInput = MIRBuilder.buildZExt(WideType, *Source); + if (!BuiltExtendedInput) + return std::unexpected(BuiltExtendedInput.error()); + auto ExtendedInput = resultRegister(**BuiltExtendedInput); + if (!ExtendedInput) + return std::unexpected(ExtendedInput.error()); + auto ShiftAmount = buildConstantRegister(MIRBuilder, WideType, + static_cast<std::int64_t>((Index - 1U) * PartSize)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto BuiltShiftedInput = MIRBuilder.buildShl(WideType, *ExtendedInput, *ShiftAmount); + if (!BuiltShiftedInput) + return std::unexpected(BuiltShiftedInput.error()); + auto ShiftedInput = resultRegister(**BuiltShiftedInput); + if (!ShiftedInput) + return std::unexpected(ShiftedInput.error()); + + Register NextResult = *Destination; + if (Index + 1U != Instruction.operandCount() || WideType != *DestinationType) { + auto NewResult = MRI.createVirtualRegister(WideType); + if (!NewResult) + return std::unexpected(NewResult.error()); + NextResult = *NewResult; + } + if (auto Built = MIRBuilder.buildOr(NextResult, ResultRegister, *ShiftedInput); !Built) + return std::unexpected(Built.error()); + ResultRegister = NextResult; + } + + if (WideSize > DestinationSize) { + if (auto Built = MIRBuilder.buildTrunc(*Destination, ResultRegister); !Built) + return std::unexpected(Built.error()); + } else if (DestinationType->isPointer()) { + if (auto Built = MIRBuilder.buildIntToPtr(*Destination, ResultRegister); !Built) + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); + } + + auto GCDType = LowLevelType::makeCategoryNeutralScalar(std::gcd(SourceSize, WideSize)); + auto WideDestinationType = LowLevelType::makeCategoryNeutralScalar(MergeCount * WideSize); + if (!GCDType) + return std::unexpected(GCDType.error()); + if (!WideDestinationType) + return std::unexpected(WideDestinationType.error()); + + std::vector<Register> UnmergedRegisters; + for (std::size_t Index = 1; Index != Instruction.operandCount(); ++Index) { + const Register *Source = Instruction.operandAt(Index)->getIf<Register>(); + if (Source == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_MERGE_VALUES requires register sources"); + if (GCDType->minimumBitWidth() == SourceSize) { + UnmergedRegisters.push_back(*Source); + continue; + } + auto Extracted = extractGCDType(UnmergedRegisters, *GCDType, *Source); + if (!Extracted) + return std::unexpected(Extracted.error()); + } + + const std::uint64_t RequiredPartCount = MergeCount * WideSize; + if (UnmergedRegisters.size() != RequiredPartCount) { + auto BuiltUndef = MIRBuilder.buildUndef(*GCDType); + if (!BuiltUndef) + return std::unexpected(BuiltUndef.error()); + auto UndefRegister = resultRegister(**BuiltUndef); + if (!UndefRegister) + return std::unexpected(UndefRegister.error()); + while (UnmergedRegisters.size() != RequiredPartCount) + UnmergedRegisters.push_back(*UndefRegister); + } + + const std::size_t PartsPerGCD = static_cast<std::size_t>(WideSize / GCDType->minimumBitWidth()); + std::vector<Register> NewMergeRegisters; + NewMergeRegisters.reserve(static_cast<std::size_t>(MergeCount)); + for (std::size_t Index = 0; Index != MergeCount; ++Index) { + std::vector<Register> Parts( + UnmergedRegisters.begin() + static_cast<std::ptrdiff_t>(Index * PartsPerGCD), + UnmergedRegisters.begin() + static_cast<std::ptrdiff_t>((Index + 1U) * PartsPerGCD)); + auto BuiltMerge = MIRBuilder.buildMergeLikeInstr(WideType, Parts); + if (!BuiltMerge) + return std::unexpected(BuiltMerge.error()); + auto MergeRegister = resultRegister(**BuiltMerge); + if (!MergeRegister) + return std::unexpected(MergeRegister.error()); + NewMergeRegisters.push_back(*MergeRegister); + } + + if (DestinationSize == WideDestinationType->minimumBitWidth()) { + if (auto Built = MIRBuilder.buildMergeLikeInstr(*Destination, NewMergeRegisters); !Built) + return std::unexpected(Built.error()); + } else { + auto BuiltFinalMerge = MIRBuilder.buildMergeLikeInstr(*WideDestinationType, NewMergeRegisters); + if (!BuiltFinalMerge) + return std::unexpected(BuiltFinalMerge.error()); + auto FinalMerge = resultRegister(**BuiltFinalMerge); + if (!FinalMerge) + return std::unexpected(FinalMerge.error()); + if (auto Built = MIRBuilder.buildTrunc(*Destination, *FinalMerge); !Built) + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::widenScalarUnmergeValues(MachineInstr &Instruction, + std::size_t TypeIndex, + LowLevelType WideType) { + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::Unsupported, + "G_UNMERGE_VALUES widening requires type index zero"); + if (Instruction.operandCount() < 2) + return makeUnexpected(ErrorCode::VerificationError, + "G_UNMERGE_VALUES requires destinations and a source"); + + const std::size_t DestinationCount = Instruction.operandCount() - 1U; + const Register *Source = Instruction.operandAt(DestinationCount)->getIf<Register>(); + const Register *FirstDestination = Instruction.operandAt(0)->getIf<Register>(); + if (Source == nullptr || FirstDestination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_UNMERGE_VALUES requires register operands"); + auto SourceType = MRI.getType(*Source); + auto DestinationType = MRI.getType(*FirstDestination); + if (!SourceType) + return std::unexpected(SourceType.error()); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (SourceType->isVector() || !DestinationType->isCategoryNeutralScalar()) + return makeUnexpected(ErrorCode::Unsupported, + "G_UNMERGE_VALUES widening requires scalar types"); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + Register SourceRegister = *Source; + if (WideType.minimumBitWidth() >= SourceType->minimumBitWidth()) { + if (SourceType->isPointer()) { + auto IntegerSourceType = + LowLevelType::makeCategoryNeutralScalar(SourceType->minimumBitWidth()); + if (!IntegerSourceType) + return std::unexpected(IntegerSourceType.error()); + auto IntegerSource = MRI.createVirtualRegister(*IntegerSourceType); + if (!IntegerSource) + return std::unexpected(IntegerSource.error()); + if (auto Built = MIRBuilder.buildPtrToInt(*IntegerSource, SourceRegister); !Built) + return std::unexpected(Built.error()); + *SourceType = *IntegerSourceType; + SourceRegister = *IntegerSource; + } + + if (WideType.minimumBitWidth() > SourceType->minimumBitWidth()) { + auto WideSource = MRI.createVirtualRegister(WideType); + if (!WideSource) + return std::unexpected(WideSource.error()); + if (auto Built = MIRBuilder.buildAnyExt(*WideSource, SourceRegister); !Built) + return std::unexpected(Built.error()); + *SourceType = WideType; + SourceRegister = *WideSource; + } + + const std::uint64_t DestinationSize = DestinationType->minimumBitWidth(); + if (auto Built = MIRBuilder.buildTrunc(*FirstDestination, SourceRegister); !Built) + return std::unexpected(Built.error()); + for (std::size_t Index = 1; Index != DestinationCount; ++Index) { + const Register *Destination = Instruction.operandAt(Index)->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_UNMERGE_VALUES requires register destinations"); + auto ShiftedSource = MRI.createVirtualRegister(*SourceType); + if (!ShiftedSource) + return std::unexpected(ShiftedSource.error()); + auto ShiftAmount = buildConstantRegister(MIRBuilder, *SourceType, + static_cast<std::int64_t>(DestinationSize * Index)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + if (auto Built = MIRBuilder.buildLShr(*ShiftedSource, SourceRegister, *ShiftAmount); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildTrunc(*Destination, *ShiftedSource); !Built) + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); + } + + auto LCMType = getLCMType(*SourceType, WideType); + if (!LCMType) + return std::unexpected(LCMType.error()); + Register WideSource = SourceRegister; + if (LCMType->minimumBitWidth() != SourceType->minimumBitWidth()) { + if (SourceType->isPointer()) + return makeUnexpected(ErrorCode::Unsupported, + "widening a pointer G_UNMERGE_VALUES source is not implemented"); + auto ExtendedSource = MRI.createVirtualRegister(*LCMType); + if (!ExtendedSource) + return std::unexpected(ExtendedSource.error()); + if (auto Built = MIRBuilder.buildAnyExt(*ExtendedSource, WideSource); !Built) + return std::unexpected(Built.error()); + WideSource = *ExtendedSource; + } + + const std::size_t UnmergeCount = + static_cast<std::size_t>(LCMType->minimumBitWidth() / WideType.minimumBitWidth()); + std::vector<Register> UnmergeRegisters; + UnmergeRegisters.reserve(UnmergeCount); + for (std::size_t Index = 0; Index != UnmergeCount; ++Index) { + auto Part = MRI.createVirtualRegister(WideType); + if (!Part) + return std::unexpected(Part.error()); + UnmergeRegisters.push_back(*Part); + } + if (auto Built = MIRBuilder.buildUnmergeValues(UnmergeRegisters, WideSource); !Built) + return std::unexpected(Built.error()); + + auto GCDType = getGCDType(WideType, *DestinationType); + if (!GCDType) + return std::unexpected(GCDType.error()); + const std::size_t PartsPerRemerge = + static_cast<std::size_t>(DestinationType->minimumBitWidth() / GCDType->minimumBitWidth()); + + if (PartsPerRemerge == 1) { + const std::size_t PartsPerUnmerge = + static_cast<std::size_t>(WideType.minimumBitWidth() / DestinationType->minimumBitWidth()); + for (std::size_t Index = 0; Index != UnmergeRegisters.size(); ++Index) { + std::vector<Register> Destinations; + Destinations.reserve(PartsPerUnmerge); + for (std::size_t PartIndex = 0; PartIndex != PartsPerUnmerge; ++PartIndex) { + const std::size_t DestinationIndex = Index * PartsPerUnmerge + PartIndex; + if (DestinationIndex < DestinationCount) { + const Register *Destination = Instruction.operandAt(DestinationIndex)->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_UNMERGE_VALUES requires register destinations"); + Destinations.push_back(*Destination); + } else { + auto DeadDestination = MRI.createVirtualRegister(*DestinationType); + if (!DeadDestination) + return std::unexpected(DeadDestination.error()); + Destinations.push_back(*DeadDestination); + } + } + if (auto Built = MIRBuilder.buildUnmergeValues(Destinations, UnmergeRegisters[Index]); !Built) + return std::unexpected(Built.error()); + } + } else { + std::vector<Register> Parts; + for (Register UnmergeRegister : UnmergeRegisters) { + auto Extracted = extractGCDType(Parts, *GCDType, UnmergeRegister); + if (!Extracted) + return std::unexpected(Extracted.error()); + } + for (std::size_t Index = 0; Index != DestinationCount; ++Index) { + const Register *Destination = Instruction.operandAt(Index)->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_UNMERGE_VALUES requires register destinations"); + std::vector<Register> RemergeParts( + Parts.begin() + static_cast<std::ptrdiff_t>(Index * PartsPerRemerge), + Parts.begin() + static_cast<std::ptrdiff_t>((Index + 1U) * PartsPerRemerge)); + if (auto Built = MIRBuilder.buildMergeLikeInstr(*Destination, RemergeParts); !Built) + return std::unexpected(Built.error()); + } + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::insertParts(Register Destination, LowLevelType ResultType, + LowLevelType PartType, + const std::vector<Register> &PartRegisters, + LowLevelType LeftoverType, + const std::vector<Register> &LeftoverRegisters) { + if (!LeftoverType.isValid()) { + if (!LeftoverRegisters.empty()) + return makeUnexpected(ErrorCode::InvalidArgument, + "leftover registers require a leftover type"); + return MIRBuilder.buildMergeLikeInstr(Destination, PartRegisters).transform([](MachineInstr *) { + }); + } + + if (ResultType.isVector()) { + if (LeftoverRegisters.size() != 1) + return makeUnexpected(ErrorCode::InvalidArgument, + "mixed subvector merge requires one leftover register"); + std::vector<Register> AllRegisters = PartRegisters; + AllRegisters.insert(AllRegisters.end(), LeftoverRegisters.begin(), LeftoverRegisters.end()); + return MIRBuilder.buildMergeLikeInstr(Destination, AllRegisters).transform([](MachineInstr *) { + }); + } + + std::vector<Register> GCDRegisters; + auto FirstGCD = getGCDType(ResultType, LeftoverType); + if (!FirstGCD) + return std::unexpected(FirstGCD.error()); + auto GCDType = getGCDType(*FirstGCD, PartType); + if (!GCDType) + return std::unexpected(GCDType.error()); + for (Register Part : PartRegisters) { + auto Extracted = extractGCDType(GCDRegisters, *GCDType, Part); + if (!Extracted) + return std::unexpected(Extracted.error()); + } + for (Register Part : LeftoverRegisters) { + auto Extracted = extractGCDType(GCDRegisters, *GCDType, Part); + if (!Extracted) + return std::unexpected(Extracted.error()); + } + auto LCMType = buildLCMMergePieces(ResultType, LeftoverType, *GCDType, GCDRegisters); + if (!LCMType) + return std::unexpected(LCMType.error()); + return buildWidenedRemergeToDst(Destination, *LCMType, GCDRegisters); +} + +Expected<void> LegalizerHelper::extractGCDType(std::vector<Register> &Parts, LowLevelType GCDType, + Register Source) { + auto SourceType = MRI.getType(Source); + if (!SourceType) + return std::unexpected(SourceType.error()); + if (*SourceType == GCDType) { + Parts.push_back(Source); + return {}; + } + const std::uint64_t SourceSize = SourceType->minimumBitWidth(); + const std::uint64_t GCDSize = GCDType.minimumBitWidth(); + if (GCDSize == 0 || SourceSize % GCDSize != 0) + return makeUnexpected(ErrorCode::InvalidArgument, + "GCD type does not evenly divide the source type"); + return extractParts(Source, GCDType, static_cast<int>(SourceSize / GCDSize), Parts, MIRBuilder, + MRI); +} + +Expected<LowLevelType> LegalizerHelper::extractGCDType(std::vector<Register> &Parts, + LowLevelType DestinationType, + LowLevelType NarrowType, Register Source) { + auto SourceType = MRI.getType(Source); + if (!SourceType) + return std::unexpected(SourceType.error()); + auto FirstGCD = getGCDType(*SourceType, NarrowType); + if (!FirstGCD) + return std::unexpected(FirstGCD.error()); + auto GCDType = getGCDType(*FirstGCD, DestinationType); + if (!GCDType) + return std::unexpected(GCDType.error()); + auto Extracted = extractGCDType(Parts, *GCDType, Source); + if (!Extracted) + return std::unexpected(Extracted.error()); + return *GCDType; +} + +Expected<LowLevelType> LegalizerHelper::buildLCMMergePieces(LowLevelType DestinationType, + LowLevelType NarrowType, + LowLevelType GCDType, + std::vector<Register> &Registers, + GenericOpcode PadStrategy) { + auto LCMType = getLCMType(DestinationType, NarrowType); + if (!LCMType) + return std::unexpected(LCMType.error()); + const int PartCount = static_cast<int>(LCMType->minimumBitWidth() / NarrowType.minimumBitWidth()); + const int SubPartCount = + static_cast<int>(NarrowType.minimumBitWidth() / GCDType.minimumBitWidth()); + const int OriginalSourceCount = static_cast<int>(Registers.size()); + + Register PadRegister; + if (OriginalSourceCount < PartCount * SubPartCount) { + if (PadStrategy == GenericOpcode::G_ZEXT) { + auto Pad = buildConstantRegister(MIRBuilder, GCDType, 0); + if (!Pad) + return std::unexpected(Pad.error()); + PadRegister = *Pad; + } else if (PadStrategy == GenericOpcode::G_ANYEXT) { + auto Built = MIRBuilder.buildUndef(GCDType); + if (!Built) + return std::unexpected(Built.error()); + auto Pad = resultRegister(**Built); + if (!Pad) + return std::unexpected(Pad.error()); + PadRegister = *Pad; + } else if (PadStrategy == GenericOpcode::G_SEXT) { + if (Registers.empty()) + return makeUnexpected(ErrorCode::InternalError, + "sign-extension padding requires a source part"); + auto ShiftType = LowLevelType::makeCategoryNeutralScalar(64); + if (!ShiftType) + return std::unexpected(ShiftType.error()); + auto ShiftAmount = buildConstantRegister( + MIRBuilder, *ShiftType, static_cast<std::int64_t>(GCDType.minimumBitWidth() - 1U)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto Pad = buildResultInstruction(MIRBuilder, GenericOpcode::G_ASHR, GCDType, + {Registers.back(), *ShiftAmount}); + if (!Pad) + return std::unexpected(Pad.error()); + PadRegister = *Pad; + } else { + return makeUnexpected(ErrorCode::InternalError, + "LCM merge received an invalid extension strategy"); + } + } + + std::vector<Register> Remerge(static_cast<std::size_t>(PartCount)); + std::vector<Register> SubMerge(static_cast<std::size_t>(SubPartCount)); + Register AllPadRegister; + for (int PartIndex = 0; PartIndex != PartCount; ++PartIndex) { + bool AllMergePartsArePadding = true; + for (int SubPartIndex = 0; SubPartIndex != SubPartCount; ++SubPartIndex) { + const int Index = PartIndex * SubPartCount + SubPartIndex; + if (Index >= OriginalSourceCount) { + SubMerge[static_cast<std::size_t>(SubPartIndex)] = PadRegister; + continue; + } + SubMerge[static_cast<std::size_t>(SubPartIndex)] = Registers[static_cast<std::size_t>(Index)]; + AllMergePartsArePadding = false; + } + + if (AllMergePartsArePadding && AllPadRegister.isNoRegister()) { + if (PadStrategy == GenericOpcode::G_ZEXT) { + auto Pad = buildConstantRegister(MIRBuilder, NarrowType, 0); + if (!Pad) + return std::unexpected(Pad.error()); + AllPadRegister = *Pad; + } else if (PadStrategy == GenericOpcode::G_ANYEXT) { + auto Built = MIRBuilder.buildUndef(NarrowType); + if (!Built) + return std::unexpected(Built.error()); + auto Pad = resultRegister(**Built); + if (!Pad) + return std::unexpected(Pad.error()); + AllPadRegister = *Pad; + } + } + if (!AllPadRegister.isNoRegister()) { + Remerge[static_cast<std::size_t>(PartIndex)] = AllPadRegister; + continue; + } + if (SubPartCount == 1) { + Remerge[static_cast<std::size_t>(PartIndex)] = SubMerge.front(); + } else { + auto Built = MIRBuilder.buildMergeLikeInstr(NarrowType, SubMerge); + if (!Built) + return std::unexpected(Built.error()); + auto RemergeRegister = resultRegister(**Built); + if (!RemergeRegister) + return std::unexpected(RemergeRegister.error()); + Remerge[static_cast<std::size_t>(PartIndex)] = *RemergeRegister; + } + if (AllMergePartsArePadding && AllPadRegister.isNoRegister()) + AllPadRegister = Remerge[static_cast<std::size_t>(PartIndex)]; + } + Registers = std::move(Remerge); + return *LCMType; +} + +Expected<void> +LegalizerHelper::buildWidenedRemergeToDst(Register Destination, LowLevelType LCMType, + const std::vector<Register> &RemergeRegisters) { + auto DestinationType = MRI.getType(Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (*DestinationType == LCMType) + return MIRBuilder.buildMergeLikeInstr(Destination, RemergeRegisters) + .transform([](MachineInstr *) {}); + + auto Remerge = MRI.createVirtualRegister(LCMType); + if (!Remerge) + return std::unexpected(Remerge.error()); + auto BuiltMerge = MIRBuilder.buildMergeLikeInstr(*Remerge, RemergeRegisters); + if (!BuiltMerge) + return std::unexpected(BuiltMerge.error()); + if (!DestinationType->isVector() && !LCMType.isVector()) + return MIRBuilder.buildTrunc(Destination, *Remerge).transform([](MachineInstr *) {}); + + if (LCMType.isVector()) { + const std::size_t DefinitionCount = + static_cast<std::size_t>(LCMType.minimumBitWidth() / DestinationType->minimumBitWidth()); + std::vector<Register> Definitions; + Definitions.reserve(DefinitionCount); + Definitions.push_back(Destination); + for (std::size_t Index = 1; Index != DefinitionCount; ++Index) { + auto Definition = MRI.createVirtualRegister(*DestinationType); + if (!Definition) + return std::unexpected(Definition.error()); + Definitions.push_back(*Definition); + } + return MIRBuilder.buildUnmergeValues(Definitions, *Remerge).transform([](MachineInstr *) {}); + } + return makeUnexpected(ErrorCode::Unsupported, + "widened remerge has an unsupported type combination"); +} + +Expected<void> LegalizerHelper::multiplyRegisters(std::vector<Register> &Destinations, + const std::vector<Register> &FirstSources, + const std::vector<Register> &SecondSources, + LowLevelType NarrowType) { + if (FirstSources.empty() || FirstSources.size() != SecondSources.size() || + (Destinations.size() != FirstSources.size() && + Destinations.size() != FirstSources.size() * 2U)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "multi-precision multiplication has incompatible register parts"); + } + auto BooleanType = LowLevelType::makeCategoryNeutralScalar(1); + if (!BooleanType) + return std::unexpected(BooleanType.error()); + + const auto BuildUAddo = [&](Register Left, + Register Right) -> Expected<std::pair<Register, Register>> { + auto Sum = MRI.createVirtualRegister(NarrowType); + auto Carry = MRI.createVirtualRegister(*BooleanType); + if (!Sum) + return std::unexpected(Sum.error()); + if (!Carry) + return std::unexpected(Carry.error()); + auto Built = + MIRBuilder.buildInstr(opcode(GenericOpcode::G_UADDO), {*Sum, *Carry}, {Left, Right}); + if (!Built) + return std::unexpected(Built.error()); + return std::pair{*Sum, *Carry}; + }; + + const std::size_t SourcePartCount = FirstSources.size(); + const std::size_t DestinationPartCount = Destinations.size(); + auto Product = buildResultInstruction(MIRBuilder, GenericOpcode::G_MUL, NarrowType, + {FirstSources.front(), SecondSources.front()}); + if (!Product) + return std::unexpected(Product.error()); + Destinations.front() = *Product; + + Register PreviousCarrySum; + std::vector<Register> Factors; + for (std::size_t DestinationIndex = 1; DestinationIndex != DestinationPartCount; + ++DestinationIndex) { + // Collect high halves from the preceding product diagonal. + const std::size_t HighStart = + DestinationIndex < SourcePartCount ? 0 : DestinationIndex - SourcePartCount; + const std::size_t HighEnd = std::min(DestinationIndex - 1U, SourcePartCount - 1U); + for (std::size_t Index = HighStart; Index <= HighEnd; ++Index) { + auto HighProduct = buildResultInstruction( + MIRBuilder, GenericOpcode::G_UMULH, NarrowType, + {FirstSources[DestinationIndex - 1U - Index], SecondSources[Index]}); + if (!HighProduct) + return std::unexpected(HighProduct.error()); + Factors.push_back(*HighProduct); + } + + // Visit the current low-half diagonal from its low first-source part. + const std::size_t LowStart = + DestinationIndex + 1U < SourcePartCount ? 0 : DestinationIndex - SourcePartCount + 1U; + const std::size_t LowEnd = std::min(DestinationIndex, SourcePartCount - 1U); + for (std::size_t ReverseIndex = LowEnd + 1U; ReverseIndex != LowStart; --ReverseIndex) { + const std::size_t Index = ReverseIndex - 1U; + auto LowProduct = + buildResultInstruction(MIRBuilder, GenericOpcode::G_MUL, NarrowType, + {FirstSources[DestinationIndex - Index], SecondSources[Index]}); + if (!LowProduct) + return std::unexpected(LowProduct.error()); + Factors.push_back(*LowProduct); + } + if (DestinationIndex != 1U) + Factors.push_back(PreviousCarrySum); + if (Factors.size() < 2) + return makeUnexpected(ErrorCode::InternalError, + "multi-precision multiplication has too few factors"); + + Register FactorSum; + Register CarrySum; + if (DestinationIndex + 1U != DestinationPartCount) { + auto SumAndCarry = BuildUAddo(Factors[0], Factors[1]); + if (!SumAndCarry) + return std::unexpected(SumAndCarry.error()); + FactorSum = SumAndCarry->first; + auto ExtendedCarry = MRI.createVirtualRegister(NarrowType); + if (!ExtendedCarry) + return std::unexpected(ExtendedCarry.error()); + if (auto Built = MIRBuilder.buildZExt(*ExtendedCarry, SumAndCarry->second); !Built) + return std::unexpected(Built.error()); + CarrySum = *ExtendedCarry; + + for (std::size_t Index = 2; Index != Factors.size(); ++Index) { + SumAndCarry = BuildUAddo(FactorSum, Factors[Index]); + if (!SumAndCarry) + return std::unexpected(SumAndCarry.error()); + FactorSum = SumAndCarry->first; + auto ExtendedCarryPart = MRI.createVirtualRegister(NarrowType); + if (!ExtendedCarryPart) + return std::unexpected(ExtendedCarryPart.error()); + if (auto Built = MIRBuilder.buildZExt(*ExtendedCarryPart, SumAndCarry->second); !Built) + return std::unexpected(Built.error()); + auto NextCarrySum = buildResultInstruction(MIRBuilder, GenericOpcode::G_ADD, NarrowType, + {CarrySum, *ExtendedCarryPart}); + if (!NextCarrySum) + return std::unexpected(NextCarrySum.error()); + CarrySum = *NextCarrySum; + } + } else { + auto Sum = buildResultInstruction(MIRBuilder, GenericOpcode::G_ADD, NarrowType, + {Factors[0], Factors[1]}); + if (!Sum) + return std::unexpected(Sum.error()); + FactorSum = *Sum; + for (std::size_t Index = 2; Index != Factors.size(); ++Index) { + Sum = buildResultInstruction(MIRBuilder, GenericOpcode::G_ADD, NarrowType, + {FactorSum, Factors[Index]}); + if (!Sum) + return std::unexpected(Sum.error()); + FactorSum = *Sum; + } + } + + PreviousCarrySum = CarrySum; + Destinations[DestinationIndex] = FactorSum; + Factors.clear(); + } + return {}; +} + +Expected<void> LegalizerHelper::widenScalarSrc(MachineInstr &Instruction, LowLevelType WideType, + std::size_t OperandIndex, + GenericOpcode ExtensionOpcode) { + MachineOperand *Operand = Instruction.operandAt(OperandIndex); + const Register *Source = Operand == nullptr ? nullptr : Operand->getIf<Register>(); + if (Source == nullptr || !Source->isVirtual() || !Operand->isUse()) + return makeUnexpected(ErrorCode::VerificationError, + "scalar widening source is not a virtual-register use"); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto Extension = MIRBuilder.buildInstr(opcode(ExtensionOpcode), {WideType}, {*Source}); + if (!Extension) + return std::unexpected(Extension.error()); + auto WideSource = resultRegister(**Extension); + if (!WideSource) + return std::unexpected(WideSource.error()); + return Operand->setRegister(*WideSource); +} + +Expected<void> LegalizerHelper::narrowScalarSrc(MachineInstr &Instruction, LowLevelType NarrowType, + std::size_t OperandIndex) { + MachineOperand *Operand = Instruction.operandAt(OperandIndex); + const Register *Source = Operand == nullptr ? nullptr : Operand->getIf<Register>(); + if (Source == nullptr || !Source->isVirtual() || !Operand->isUse()) + return makeUnexpected(ErrorCode::VerificationError, + "scalar narrowing source is not a virtual-register use"); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto Truncation = MIRBuilder.buildTrunc(NarrowType, *Source); + if (!Truncation) + return std::unexpected(Truncation.error()); + auto NarrowSource = resultRegister(**Truncation); + if (!NarrowSource) + return std::unexpected(NarrowSource.error()); + return Operand->setRegister(*NarrowSource); +} + +Expected<void> LegalizerHelper::narrowScalarDst(MachineInstr &Instruction, LowLevelType NarrowType, + std::size_t OperandIndex, + GenericOpcode ExtensionOpcode) { + MachineOperand *Operand = Instruction.operandAt(OperandIndex); + const Register *Destination = Operand == nullptr ? nullptr : Operand->getIf<Register>(); + if (Destination == nullptr || !Destination->isVirtual() || !Operand->isDef()) + return makeUnexpected(ErrorCode::VerificationError, + "scalar narrowing destination is not a virtual-register definition"); + + const Register OriginalDestination = *Destination; + auto NarrowDestination = MRI.createVirtualRegister(NarrowType); + if (!NarrowDestination) + return std::unexpected(NarrowDestination.error()); + if (auto Point = MIRBuilder.setInsertPointAfter(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Extension = MIRBuilder.buildInstr(opcode(ExtensionOpcode), {OriginalDestination}, + {*NarrowDestination}); + !Extension) { + return std::unexpected(Extension.error()); + } + return Operand->setRegister(*NarrowDestination); +} + +Expected<void> LegalizerHelper::widenScalarDst(MachineInstr &Instruction, LowLevelType WideType, + std::size_t OperandIndex, + GenericOpcode TruncOpcode) { + MachineOperand *Operand = Instruction.operandAt(OperandIndex); + const Register *Destination = Operand == nullptr ? nullptr : Operand->getIf<Register>(); + if (Destination == nullptr || !Destination->isVirtual() || !Operand->isDef()) + return makeUnexpected(ErrorCode::VerificationError, + "scalar widening destination is not a virtual-register definition"); + + auto WideDestination = MRI.createVirtualRegister(WideType); + if (!WideDestination) + return std::unexpected(WideDestination.error()); + if (auto Point = MIRBuilder.setInsertPointAfter(Instruction); !Point) + return std::unexpected(Point.error()); + auto Truncation = MIRBuilder.buildInstr(opcode(TruncOpcode)); + if (!Truncation) + return std::unexpected(Truncation.error()); + if (auto Added = Truncation->addDef(*Destination); !Added) + return std::unexpected(Added.error()); + if (auto Added = Truncation->addUse(*WideDestination); !Added) + return std::unexpected(Added.error()); + if (auto Built = Truncation->build(); !Built) + return std::unexpected(Built.error()); + return Operand->setRegister(*WideDestination); +} + +Expected<void> LegalizerHelper::widenScalarAddSubOverflow(MachineInstr &Instruction, + std::size_t TypeIndex, + LowLevelType WideType) { + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + GenericOpcode ArithmeticOpcode; + GenericOpcode ExtensionOpcode; + bool HasCarryIn = false; + switch (Generic) { + case GenericOpcode::G_SADDO: + ArithmeticOpcode = GenericOpcode::G_ADD; + ExtensionOpcode = GenericOpcode::G_SEXT; + break; + case GenericOpcode::G_SSUBO: + ArithmeticOpcode = GenericOpcode::G_SUB; + ExtensionOpcode = GenericOpcode::G_SEXT; + break; + case GenericOpcode::G_UADDO: + ArithmeticOpcode = GenericOpcode::G_ADD; + ExtensionOpcode = GenericOpcode::G_ZEXT; + break; + case GenericOpcode::G_USUBO: + ArithmeticOpcode = GenericOpcode::G_SUB; + ExtensionOpcode = GenericOpcode::G_ZEXT; + break; + case GenericOpcode::G_SADDE: + ArithmeticOpcode = GenericOpcode::G_UADDE; + ExtensionOpcode = GenericOpcode::G_SEXT; + HasCarryIn = true; + break; + case GenericOpcode::G_SSUBE: + ArithmeticOpcode = GenericOpcode::G_USUBE; + ExtensionOpcode = GenericOpcode::G_SEXT; + HasCarryIn = true; + break; + case GenericOpcode::G_UADDE: + ArithmeticOpcode = GenericOpcode::G_UADDE; + ExtensionOpcode = GenericOpcode::G_ZEXT; + HasCarryIn = true; + break; + case GenericOpcode::G_USUBE: + ArithmeticOpcode = GenericOpcode::G_USUBE; + ExtensionOpcode = GenericOpcode::G_ZEXT; + HasCarryIn = true; + break; + default: + return makeUnexpected(ErrorCode::InternalError, + "unexpected opcode during add/sub overflow widening"); + } + + if (TypeIndex == 1) { + if (HasCarryIn) { + if (auto Result = widenScalarSrc(Instruction, WideType, 4, + MIRBuilder.getBoolExtOp(WideType.isVector(), false)); + !Result) { + return Result; + } + } + return widenScalarDst(Instruction, WideType, 1); + } + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "add/sub overflow widening requires type index zero or one"); + if (Instruction.operandCount() != (HasCarryIn ? 5U : 4U)) + return makeUnexpected(ErrorCode::VerificationError, + "add/sub overflow widening has an invalid operand count"); + + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *OverflowOperand = Instruction.operandAt(1); + const MachineOperand *LeftOperand = Instruction.operandAt(2); + const MachineOperand *RightOperand = Instruction.operandAt(3); + const MachineOperand *CarryInOperand = HasCarryIn ? Instruction.operandAt(4) : nullptr; + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Overflow = + OverflowOperand == nullptr ? nullptr : OverflowOperand->getIf<Register>(); + const Register *Left = LeftOperand == nullptr ? nullptr : LeftOperand->getIf<Register>(); + const Register *Right = RightOperand == nullptr ? nullptr : RightOperand->getIf<Register>(); + const Register *CarryIn = CarryInOperand == nullptr ? nullptr : CarryInOperand->getIf<Register>(); + if (Destination == nullptr || Overflow == nullptr || Left == nullptr || Right == nullptr || + (HasCarryIn && CarryIn == nullptr)) { + return makeUnexpected(ErrorCode::VerificationError, + "add/sub overflow widening requires register operands"); + } + + auto OriginalType = MRI.getType(*Destination); + auto OverflowType = MRI.getType(*Overflow); + if (!OriginalType) + return std::unexpected(OriginalType.error()); + if (!OverflowType) + return std::unexpected(OverflowType.error()); + + auto WideLeft = MRI.createVirtualRegister(WideType); + auto WideRight = MRI.createVirtualRegister(WideType); + auto WideResult = MRI.createVirtualRegister(WideType); + auto TruncatedResult = MRI.createVirtualRegister(*OriginalType); + auto ExtendedResult = MRI.createVirtualRegister(WideType); + if (!WideLeft) + return std::unexpected(WideLeft.error()); + if (!WideRight) + return std::unexpected(WideRight.error()); + if (!WideResult) + return std::unexpected(WideResult.error()); + if (!TruncatedResult) + return std::unexpected(TruncatedResult.error()); + if (!ExtendedResult) + return std::unexpected(ExtendedResult.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(ExtensionOpcode), {*WideLeft}, {*Left}); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(ExtensionOpcode), {*WideRight}, {*Right}); !Built) + return std::unexpected(Built.error()); + if (HasCarryIn) { + if (auto Built = MIRBuilder.buildInstr(opcode(ArithmeticOpcode), {*WideResult, *OverflowType}, + {*WideLeft, *WideRight, *CarryIn}); + !Built) { + return std::unexpected(Built.error()); + } + } else if (auto Built = MIRBuilder.buildInstr(opcode(ArithmeticOpcode), {*WideResult}, + {*WideLeft, *WideRight}); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildTrunc(*TruncatedResult, *WideResult); !Built) + return std::unexpected(Built.error()); + if (auto Built = + MIRBuilder.buildInstr(opcode(ExtensionOpcode), {*ExtendedResult}, {*TruncatedResult}); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::NotEqual, *Overflow, *WideResult, *ExtendedResult); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildTrunc(*Destination, *WideResult); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::widenScalarMulo(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType WideType) { + if (TypeIndex == 1) + return widenScalarDst(Instruction, WideType, 1); + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "multiply-overflow widening requires type index zero or one"); + if (Instruction.operandCount() != 4) + return makeUnexpected(ErrorCode::VerificationError, + "multiply-overflow widening requires four register operands"); + + const bool IsSigned = Instruction.opcode() == opcode(GenericOpcode::G_SMULO); + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + const Register *OriginalOverflow = Instruction.operandAt(1)->getIf<Register>(); + const Register *Left = Instruction.operandAt(2)->getIf<Register>(); + const Register *Right = Instruction.operandAt(3)->getIf<Register>(); + if (Destination == nullptr || OriginalOverflow == nullptr || Left == nullptr || Right == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "multiply-overflow widening requires register operands"); + + auto SourceType = MRI.getType(*Left); + auto OverflowType = MRI.getType(*OriginalOverflow); + if (!SourceType) + return std::unexpected(SourceType.error()); + if (!OverflowType) + return std::unexpected(OverflowType.error()); + const std::uint32_t SourceBitWidth = SourceType->scalarBitWidth(); + if (SourceBitWidth == 0 || !WideType.isCategoryNeutralScalar()) + return makeUnexpected(ErrorCode::VerificationError, + "multiply-overflow widening requires scalar types"); + + const GenericOpcode ExtensionOpcode = IsSigned ? GenericOpcode::G_SEXT : GenericOpcode::G_ZEXT; + auto WideLeft = buildResultInstruction(MIRBuilder, ExtensionOpcode, WideType, {*Left}); + auto WideRight = buildResultInstruction(MIRBuilder, ExtensionOpcode, WideType, {*Right}); + if (!WideLeft) + return std::unexpected(WideLeft.error()); + if (!WideRight) + return std::unexpected(WideRight.error()); + + const bool WideMulCanOverflow = static_cast<std::uint64_t>(WideType.scalarBitWidth()) < + 2U * static_cast<std::uint64_t>(SourceBitWidth); + Register Mul; + Register WideOverflow; + if (WideMulCanOverflow) { + auto Built = + MIRBuilder.buildInstr(opcode(IsSigned ? GenericOpcode::G_SMULO : GenericOpcode::G_UMULO), + {WideType, *OverflowType}, {*WideLeft, *WideRight}); + if (!Built) + return std::unexpected(Built.error()); + const MachineOperand *ResultOperand = (*Built)->operandAt(0); + const MachineOperand *OverflowOperand = (*Built)->operandAt(1); + const Register *Result = ResultOperand == nullptr ? nullptr : ResultOperand->getIf<Register>(); + const Register *Overflow = + OverflowOperand == nullptr ? nullptr : OverflowOperand->getIf<Register>(); + if (Result == nullptr || Overflow == nullptr) + return makeUnexpected(ErrorCode::InternalError, + "multiply-overflow widening produced invalid results"); + Mul = *Result; + WideOverflow = *Overflow; + } else { + auto Built = + buildResultInstruction(MIRBuilder, GenericOpcode::G_MUL, WideType, {*WideLeft, *WideRight}); + if (!Built) + return std::unexpected(Built.error()); + Mul = *Built; + } + + if (auto Built = MIRBuilder.buildTrunc(*Destination, Mul); !Built) + return std::unexpected(Built.error()); + + Register ExtendedResult; + if (IsSigned) { + auto Built = MIRBuilder.buildSExtInReg(WideType, Mul, SourceBitWidth); + if (!Built) + return std::unexpected(Built.error()); + auto Result = resultRegister(**Built); + if (!Result) + return std::unexpected(Result.error()); + ExtendedResult = *Result; + } else { + auto Mask = APInt::getLowBitsSet(WideType.scalarBitWidth(), SourceBitWidth); + if (!Mask) + return std::unexpected(Mask.error()); + auto MaskRegister = buildConstantRegister(MIRBuilder, WideType, std::move(*Mask)); + if (!MaskRegister) + return std::unexpected(MaskRegister.error()); + auto Built = + buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, WideType, {Mul, *MaskRegister}); + if (!Built) + return std::unexpected(Built.error()); + ExtendedResult = *Built; + } + + if (WideMulCanOverflow) { + auto Overflow = MRI.createVirtualRegister(*OverflowType); + if (!Overflow) + return std::unexpected(Overflow.error()); + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::NotEqual, *Overflow, Mul, ExtendedResult); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildOr(*OriginalOverflow, WideOverflow, *Overflow); !Built) + return std::unexpected(Built.error()); + } else if (auto Built = MIRBuilder.buildICmp(IntegerPredicate::NotEqual, *OriginalOverflow, Mul, + ExtendedResult); + !Built) { + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::widenScalar(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType WideType) { + auto Query = LegalizerInfo::buildQuery(Instruction, Function, MIRBuilder.target()); + if (!Query) + return std::unexpected(Query.error()); + if (!WideType.isCategoryNeutralScalar() || TypeIndex >= Query->Types.size() || + !Query->Types[TypeIndex].isCategoryNeutralScalar() || + WideType.scalarBitWidth() <= Query->Types[TypeIndex].scalarBitWidth()) { + return makeUnexpected(ErrorCode::VerificationError, + "scalar widening has an invalid type index or target type"); + } + + const auto WidenSource = [&](std::size_t OperandIndex, GenericOpcode ExtensionOpcode) { + return widenScalarSrc(Instruction, WideType, OperandIndex, ExtensionOpcode); + }; + const auto WidenDestination = [&](std::size_t OperandIndex = 0, + GenericOpcode TruncOpcode = GenericOpcode::G_TRUNC) { + return widenScalarDst(Instruction, WideType, OperandIndex, TruncOpcode); + }; + const auto Apply = [](Expected<void> Result) -> Expected<void> { + if (!Result) + return std::unexpected(Result.error()); + return {}; + }; + + Observer.changingInstr(Instruction); + switch (static_cast<GenericOpcode>(Instruction.opcode().value())) { + case GenericOpcode::G_MERGE_VALUES: + return widenScalarMergeValues(Instruction, TypeIndex, WideType); + case GenericOpcode::G_UNMERGE_VALUES: + return widenScalarUnmergeValues(Instruction, TypeIndex, WideType); + case GenericOpcode::G_ATOMICRMW_XCHG: + case GenericOpcode::G_ATOMICRMW_ADD: + case GenericOpcode::G_ATOMICRMW_SUB: + case GenericOpcode::G_ATOMICRMW_AND: + case GenericOpcode::G_ATOMICRMW_OR: + case GenericOpcode::G_ATOMICRMW_XOR: + case GenericOpcode::G_ATOMICRMW_MIN: + case GenericOpcode::G_ATOMICRMW_MAX: + case GenericOpcode::G_ATOMICRMW_UMIN: + case GenericOpcode::G_ATOMICRMW_UMAX: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "atomic RMW value widening has an invalid type index"); + if (auto Result = Apply(WidenSource(2, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + case GenericOpcode::G_ATOMIC_CMPXCHG: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "compare-exchange widening has an invalid type index"); + if (auto Result = Apply(WidenSource(2, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenSource(3, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + case GenericOpcode::G_ATOMIC_CMPXCHG_WITH_SUCCESS: + if (TypeIndex == 0) { + if (auto Result = Apply(WidenSource(3, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenSource(4, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + } + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::VerificationError, + "compare-exchange success widening has an invalid type index"); + if (auto Result = Apply(WidenDestination(1)); !Result) + return Result; + break; + + case GenericOpcode::G_CONSTANT: { + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "constant widening has an invalid type index"); + MachineOperand *SourceOperand = Instruction.operandAt(1); + const auto *Immediate = + SourceOperand == nullptr ? nullptr : SourceOperand->getIf<IntegerImmediateOperand>(); + if (Immediate == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_CONSTANT widening requires an integer immediate"); + const GenericOpcode ExtensionOpcode = + Info.getExtOpcodeForWideningConstant(Query->Types[TypeIndex]); + if (ExtensionOpcode != GenericOpcode::G_SEXT && ExtensionOpcode != GenericOpcode::G_ZEXT && + ExtensionOpcode != GenericOpcode::G_ANYEXT) { + return makeUnexpected(ErrorCode::InternalError, + "constant widening selected an invalid extension opcode"); + } + auto Extended = ExtensionOpcode == GenericOpcode::G_SEXT + ? Immediate->Value.sext(WideType.scalarBitWidth()) + : Immediate->Value.zext(WideType.scalarBitWidth()); + if (!Extended) + return std::unexpected(Extended.error()); + if (auto Replaced = SourceOperand->setIntegerImmediate(std::move(*Extended)); !Replaced) + return std::unexpected(Replaced.error()); + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + } + + case GenericOpcode::G_IMPLICIT_DEF: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "implicit definition widening has an invalid type index"); + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + case GenericOpcode::G_BRCOND: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "G_BRCOND widening requires type index zero"); + if (auto Result = Apply(WidenSource(0, MIRBuilder.getBoolExtOp(false, false))); !Result) + return Result; + break; + + case GenericOpcode::G_CTTZ: + case GenericOpcode::G_CTTZ_ZERO_POISON: + case GenericOpcode::G_CTLZ: + case GenericOpcode::G_CTLZ_ZERO_POISON: + case GenericOpcode::G_CTLS: + case GenericOpcode::G_CTPOP: { + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (TypeIndex == 0) { + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + } + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::VerificationError, + "bit-count widening has an invalid type index"); + + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *SourceOperand = Instruction.operandAt(1); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Destination == nullptr || Source == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "bit-count widening requires register operands"); + auto SourceType = MRI.getType(*Source); + if (!SourceType) + return std::unexpected(SourceType.error()); + + GenericOpcode ExtensionOpcode = GenericOpcode::G_ZEXT; + switch (Generic) { + case GenericOpcode::G_CTTZ: + case GenericOpcode::G_CTTZ_ZERO_POISON: + case GenericOpcode::G_CTLZ_ZERO_POISON: + ExtensionOpcode = GenericOpcode::G_ANYEXT; + break; + case GenericOpcode::G_CTLS: + ExtensionOpcode = GenericOpcode::G_SEXT; + break; + default: + break; + } + + auto WideSource = buildResultInstruction(MIRBuilder, ExtensionOpcode, WideType, {*Source}); + if (!WideSource) + return std::unexpected(WideSource.error()); + GenericOpcode WideOpcode = Generic; + if (Generic == GenericOpcode::G_CTTZ) { + auto TopBitValue = oneBitSet(WideType.scalarBitWidth(), SourceType->scalarBitWidth()); + if (!TopBitValue) + return std::unexpected(TopBitValue.error()); + auto TopBit = buildConstantRegister(MIRBuilder, WideType, std::move(*TopBitValue)); + if (!TopBit) + return std::unexpected(TopBit.error()); + auto NonZeroSource = + buildResultInstruction(MIRBuilder, GenericOpcode::G_OR, WideType, {*WideSource, *TopBit}); + if (!NonZeroSource) + return std::unexpected(NonZeroSource.error()); + WideSource = *NonZeroSource; + WideOpcode = GenericOpcode::G_CTTZ_ZERO_POISON; + } + + const std::uint32_t SizeDifference = WideType.scalarBitWidth() - SourceType->scalarBitWidth(); + if (Generic == GenericOpcode::G_CTLZ_ZERO_POISON) { + auto ShiftAmount = + buildConstantRegister(MIRBuilder, WideType, static_cast<std::int64_t>(SizeDifference)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto Shifted = buildResultInstruction(MIRBuilder, GenericOpcode::G_SHL, WideType, + {*WideSource, *ShiftAmount}); + if (!Shifted) + return std::unexpected(Shifted.error()); + WideSource = *Shifted; + } + + auto Result = buildResultInstruction(MIRBuilder, WideOpcode, WideType, {*WideSource}); + if (!Result) + return std::unexpected(Result.error()); + if (Generic == GenericOpcode::G_CTLZ || Generic == GenericOpcode::G_CTLS) { + auto Difference = + buildConstantRegister(MIRBuilder, WideType, static_cast<std::int64_t>(SizeDifference)); + if (!Difference) + return std::unexpected(Difference.error()); + if (Generic == GenericOpcode::G_CTLZ) { + Result = buildFlaggedBinaryInstruction(MIRBuilder, GenericOpcode::G_SUB, WideType, *Result, + *Difference, + MachineInstrFlags{MachineInstrFlag::NoUnsignedWrap}); + } else { + Result = buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, WideType, + {*Result, *Difference}); + } + if (!Result) + return std::unexpected(Result.error()); + } + + if (auto Built = MIRBuilder.buildZExtOrTrunc(*Destination, *Result); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); + } + + case GenericOpcode::G_BSWAP: { + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "byte-swap widening has an invalid type index"); + MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "byte-swap widening requires a register destination"); + const Register OriginalDestination = *Destination; + auto OriginalType = MRI.getType(OriginalDestination); + if (!OriginalType) + return std::unexpected(OriginalType.error()); + auto WideDestination = MRI.createVirtualRegister(WideType); + auto Shifted = MRI.createVirtualRegister(WideType); + if (!WideDestination) + return std::unexpected(WideDestination.error()); + if (!Shifted) + return std::unexpected(Shifted.error()); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Replaced = DestinationOperand->setRegister(*WideDestination); !Replaced) + return std::unexpected(Replaced.error()); + + if (auto Point = MIRBuilder.setInsertPointAfter(Instruction); !Point) + return std::unexpected(Point.error()); + const std::uint32_t Difference = WideType.scalarBitWidth() - OriginalType->scalarBitWidth(); + auto ShiftAmount = buildConstantRegister(MIRBuilder, WideType, Difference); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + if (auto Built = MIRBuilder.buildLShr(*Shifted, *WideDestination, *ShiftAmount); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildTrunc(OriginalDestination, *Shifted); !Built) + return std::unexpected(Built.error()); + break; + } + + case GenericOpcode::G_LOAD: + case GenericOpcode::G_SEXTLOAD: + case GenericOpcode::G_ZEXTLOAD: + case GenericOpcode::G_FPEXTLOAD: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "load widening has an invalid type index"); + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + case GenericOpcode::G_STORE: { + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "store widening has an invalid type index"); + const Register *Source = Instruction.operandAt(0)->getIf<Register>(); + if (Source == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "store widening requires a register source"); + auto SourceType = MRI.getType(*Source); + if (!SourceType) + return std::unexpected(SourceType.error()); + if (!SourceType->isCategoryNeutralScalar()) + return makeUnexpected(ErrorCode::Unsupported, "non-scalar store widening is not implemented"); + const GenericOpcode ExtensionOpcode = + SourceType->scalarBitWidth() == 1 ? GenericOpcode::G_ZEXT : GenericOpcode::G_ANYEXT; + if (auto Result = Apply(WidenSource(0, ExtensionOpcode)); !Result) + return Result; + break; + } + + case GenericOpcode::G_FPTRUNCSTORE: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "floating truncating store widening has an invalid type index"); + if (auto Result = Apply(WidenSource(0, GenericOpcode::G_FPEXT)); !Result) + return Result; + break; + + case GenericOpcode::G_SADDO: + case GenericOpcode::G_SSUBO: + case GenericOpcode::G_UADDO: + case GenericOpcode::G_USUBO: + case GenericOpcode::G_SADDE: + case GenericOpcode::G_SSUBE: + case GenericOpcode::G_UADDE: + case GenericOpcode::G_USUBE: + return widenScalarAddSubOverflow(Instruction, TypeIndex, WideType); + + case GenericOpcode::G_SMULO: + case GenericOpcode::G_UMULO: + if (TypeIndex != 1) + return widenScalarMulo(Instruction, TypeIndex, WideType); + if (auto Result = widenScalarMulo(Instruction, TypeIndex, WideType); !Result) + return Result; + break; + + case GenericOpcode::G_ADD: + case GenericOpcode::G_SUB: + case GenericOpcode::G_MUL: + case GenericOpcode::G_AND: + case GenericOpcode::G_OR: + case GenericOpcode::G_XOR: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "binary operation widening has an invalid type index"); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenSource(2, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + case GenericOpcode::G_SDIV: + case GenericOpcode::G_SREM: + case GenericOpcode::G_SMIN: + case GenericOpcode::G_SMAX: + case GenericOpcode::G_ABDS: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "signed operation widening has an invalid type index"); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_SEXT)); !Result) + return Result; + if (auto Result = Apply(WidenSource(2, GenericOpcode::G_SEXT)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + case GenericOpcode::G_UDIV: + case GenericOpcode::G_UREM: + case GenericOpcode::G_UMIN: + case GenericOpcode::G_UMAX: + case GenericOpcode::G_ABDU: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "unsigned operation widening has an invalid type index"); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_ZEXT)); !Result) + return Result; + if (auto Result = Apply(WidenSource(2, GenericOpcode::G_ZEXT)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + case GenericOpcode::G_SHL: + case GenericOpcode::G_LSHR: + case GenericOpcode::G_ASHR: + if (TypeIndex == 0) { + GenericOpcode ExtensionOpcode = GenericOpcode::G_ANYEXT; + if (Instruction.opcode() == opcode(GenericOpcode::G_LSHR)) + ExtensionOpcode = GenericOpcode::G_ZEXT; + else if (Instruction.opcode() == opcode(GenericOpcode::G_ASHR)) + ExtensionOpcode = GenericOpcode::G_SEXT; + if (auto Result = Apply(WidenSource(1, ExtensionOpcode)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + } + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::VerificationError, + "shift widening has an invalid type index"); + if (auto Result = Apply(WidenSource(2, GenericOpcode::G_ZEXT)); !Result) + return Result; + break; + + case GenericOpcode::G_ROTL: + case GenericOpcode::G_ROTR: + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::Unsupported, "rotate value widening is not defined"); + if (auto Result = Apply(WidenSource(2, GenericOpcode::G_ZEXT)); !Result) + return Result; + break; + + case GenericOpcode::G_FREEZE: + case GenericOpcode::G_CONSTANT_FOLD_BARRIER: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "unary operation widening has an invalid type index"); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + case GenericOpcode::G_ABS: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "absolute-value widening has an invalid type index"); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_SEXT)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + case GenericOpcode::G_ICMP: + if (TypeIndex == 0) { + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + } + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::VerificationError, + "integer comparison widening has an invalid type index"); + { + const MachineOperand *PredicateMachineOperand = Instruction.operandAt(1); + const auto *Predicate = PredicateMachineOperand == nullptr + ? nullptr + : PredicateMachineOperand->getIf<PredicateOperand>(); + const IntegerPredicate *Integer = + Predicate == nullptr ? nullptr : Predicate->integerPredicate(); + if (Integer == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_ICMP widening requires an integer predicate"); + + bool IsSigned = false; + switch (*Integer) { + case IntegerPredicate::SignedGreaterThan: + case IntegerPredicate::SignedGreaterEqual: + case IntegerPredicate::SignedLessThan: + case IntegerPredicate::SignedLessEqual: + IsSigned = true; + break; + case IntegerPredicate::Equal: + case IntegerPredicate::NotEqual: + case IntegerPredicate::UnsignedGreaterThan: + case IntegerPredicate::UnsignedGreaterEqual: + case IntegerPredicate::UnsignedLessThan: + case IntegerPredicate::UnsignedLessEqual: + break; + case IntegerPredicate::Count: + return makeUnexpected(ErrorCode::VerificationError, + "G_ICMP widening received an invalid integer predicate"); + } + const GenericOpcode ExtensionOpcode = + IsSigned || Info.isSExtCheaperThanZExt(Query->Types[TypeIndex], WideType) + ? GenericOpcode::G_SEXT + : GenericOpcode::G_ZEXT; + if (auto Result = Apply(WidenSource(2, ExtensionOpcode)); !Result) + return Result; + if (auto Result = Apply(WidenSource(3, ExtensionOpcode)); !Result) + return Result; + } + break; + + case GenericOpcode::G_FCMP: + if (TypeIndex == 0) { + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + } + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::VerificationError, + "floating comparison widening has an invalid type index"); + if (auto Result = Apply(WidenSource(2, GenericOpcode::G_FPEXT)); !Result) + return Result; + if (auto Result = Apply(WidenSource(3, GenericOpcode::G_FPEXT)); !Result) + return Result; + break; + + case GenericOpcode::G_SELECT: + if (TypeIndex == 0) { + if (auto Result = Apply(WidenSource(2, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenSource(3, GenericOpcode::G_ANYEXT)); !Result) + return Result; + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + } + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::VerificationError, + "select widening has an invalid type index"); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_ZEXT)); !Result) + return Result; + break; + + case GenericOpcode::G_PHI: { + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "G_PHI widening requires type index zero"); + if (Instruction.operandCount() < 3 || Instruction.operandCount() % 2U == 0) + return makeUnexpected(ErrorCode::VerificationError, + "G_PHI widening requires incoming value/block pairs"); + + for (std::size_t Index = 1; Index < Instruction.operandCount(); Index += 2) { + MachineOperand *ValueOperand = Instruction.operandAt(Index); + const MachineOperand *BlockOperand = Instruction.operandAt(Index + 1U); + const Register *Value = ValueOperand == nullptr ? nullptr : ValueOperand->getIf<Register>(); + const auto *Incoming = + BlockOperand == nullptr ? nullptr : BlockOperand->getIf<BasicBlockOperand>(); + if (Value == nullptr || Incoming == nullptr || Incoming->Block == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_PHI widening has an invalid incoming pair"); + if (auto Point = setInsertPointAtFirstTerminator(MIRBuilder, *Incoming->Block); !Point) + return std::unexpected(Point.error()); + auto Extension = MIRBuilder.buildInstr(opcode(GenericOpcode::G_ANYEXT), {WideType}, {*Value}); + if (!Extension) + return std::unexpected(Extension.error()); + auto WideValue = resultRegister(**Extension); + if (!WideValue) + return std::unexpected(WideValue.error()); + if (auto Changed = ValueOperand->setRegister(*WideValue); !Changed) + return std::unexpected(Changed.error()); + } + + MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + MachineBasicBlock *Block = Instruction.parent(); + if (Destination == nullptr || Block == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_PHI widening has an invalid destination"); + const Register OriginalDestination = *Destination; + auto WideDestination = MRI.createVirtualRegister(WideType); + if (!WideDestination) + return std::unexpected(WideDestination.error()); + if (auto Changed = DestinationOperand->setRegister(*WideDestination); !Changed) + return std::unexpected(Changed.error()); + if (auto Point = setInsertPointAfterPHIs(MIRBuilder, *Block); !Point) + return std::unexpected(Point.error()); + if (auto Truncation = MIRBuilder.buildTrunc(OriginalDestination, *WideDestination); + !Truncation) { + return std::unexpected(Truncation.error()); + } + break; + } + + case GenericOpcode::G_FADD: + case GenericOpcode::G_FMUL: + case GenericOpcode::G_FSUB: + case GenericOpcode::G_FMA: + case GenericOpcode::G_FMAD: + case GenericOpcode::G_FNEG: + case GenericOpcode::G_FABS: + case GenericOpcode::G_FCANONICALIZE: + case GenericOpcode::G_FMINNUM: + case GenericOpcode::G_FMAXNUM: + case GenericOpcode::G_FMINNUM_IEEE: + case GenericOpcode::G_FMAXNUM_IEEE: + case GenericOpcode::G_FMINIMUM: + case GenericOpcode::G_FMAXIMUM: + case GenericOpcode::G_FMINIMUMNUM: + case GenericOpcode::G_FMAXIMUMNUM: + case GenericOpcode::G_FDIV: + case GenericOpcode::G_FREM: + case GenericOpcode::G_FCEIL: + case GenericOpcode::G_FFLOOR: + case GenericOpcode::G_FCOS: + case GenericOpcode::G_FSIN: + case GenericOpcode::G_FTAN: + case GenericOpcode::G_FACOS: + case GenericOpcode::G_FASIN: + case GenericOpcode::G_FATAN: + case GenericOpcode::G_FATAN2: + case GenericOpcode::G_FCOSH: + case GenericOpcode::G_FSINH: + case GenericOpcode::G_FTANH: + case GenericOpcode::G_FLOG10: + case GenericOpcode::G_FLOG: + case GenericOpcode::G_FLOG2: + case GenericOpcode::G_FRINT: + case GenericOpcode::G_FNEARBYINT: + case GenericOpcode::G_FSQRT: + case GenericOpcode::G_FEXP: + case GenericOpcode::G_FEXP2: + case GenericOpcode::G_FEXP10: + case GenericOpcode::G_FPOW: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::VerificationError, + "floating operation widening has an invalid type index"); + for (std::size_t OperandIndex = 1; OperandIndex < Instruction.operandCount(); ++OperandIndex) { + if (auto Result = Apply(WidenSource(OperandIndex, GenericOpcode::G_FPEXT)); !Result) + return Result; + } + if (auto Result = Apply(WidenDestination(0, GenericOpcode::G_FPTRUNC)); !Result) + return Result; + break; + + case GenericOpcode::G_FPEXT: + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::Unsupported, + "floating-point extension destination widening is not defined"); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_FPEXT)); !Result) + return Result; + break; + + case GenericOpcode::G_FPTOSI: + case GenericOpcode::G_FPTOUI: + case GenericOpcode::G_INTRINSIC_LRINT: + case GenericOpcode::G_INTRINSIC_LLRINT: + case GenericOpcode::G_IS_FPCLASS: + if (TypeIndex == 0) { + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + } + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::VerificationError, + "floating-to-integer widening has an invalid type index"); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_FPEXT)); !Result) + return Result; + break; + + case GenericOpcode::G_SITOFP: + case GenericOpcode::G_UITOFP: + if (TypeIndex == 0) { + if (auto Result = Apply(WidenDestination(0, GenericOpcode::G_FPTRUNC)); !Result) + return Result; + break; + } + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::VerificationError, + "integer-to-floating widening has an invalid type index"); + if (auto Result = Apply(WidenSource(1, Instruction.opcode() == opcode(GenericOpcode::G_SITOFP) + ? GenericOpcode::G_SEXT + : GenericOpcode::G_ZEXT)); + !Result) + return Result; + break; + + case GenericOpcode::G_INTTOPTR: + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::Unsupported, + "G_INTTOPTR destination widening is not defined"); + if (auto Result = Apply(WidenSource(1, GenericOpcode::G_ZEXT)); !Result) + return Result; + break; + + case GenericOpcode::G_PTRTOINT: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::Unsupported, "G_PTRTOINT source widening is not defined"); + if (auto Result = Apply(WidenDestination()); !Result) + return Result; + break; + + default: + return makeUnexpected(ErrorCode::Unsupported, + "scalar widening is not implemented for this opcode"); + } + + Observer.changedInstr(Instruction); + return {}; +} + +Expected<void> LegalizerHelper::narrowScalarAddSub(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType NarrowType) { + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::Unsupported, "add/sub narrowing requires type index zero"); + + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "add/sub narrowing requires a register destination"); + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (DestinationType->isVector()) + return makeUnexpected(ErrorCode::Unsupported, "vector add/sub narrowing is not implemented"); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + GenericOpcode FirstOpcode; + GenericOpcode MiddleOpcode; + GenericOpcode FinalOpcode; + switch (static_cast<GenericOpcode>(Instruction.opcode().value())) { + case GenericOpcode::G_SADDO: + case GenericOpcode::G_SADDE: + case GenericOpcode::G_UADDO: + case GenericOpcode::G_UADDE: + case GenericOpcode::G_ADD: + FirstOpcode = GenericOpcode::G_UADDO; + MiddleOpcode = GenericOpcode::G_UADDE; + FinalOpcode = Instruction.opcode() == opcode(GenericOpcode::G_SADDO) || + Instruction.opcode() == opcode(GenericOpcode::G_SADDE) + ? GenericOpcode::G_SADDE + : GenericOpcode::G_UADDE; + break; + case GenericOpcode::G_SSUBO: + case GenericOpcode::G_SSUBE: + case GenericOpcode::G_USUBO: + case GenericOpcode::G_USUBE: + case GenericOpcode::G_SUB: + FirstOpcode = GenericOpcode::G_USUBO; + MiddleOpcode = GenericOpcode::G_USUBE; + FinalOpcode = Instruction.opcode() == opcode(GenericOpcode::G_SSUBO) || + Instruction.opcode() == opcode(GenericOpcode::G_SSUBE) + ? GenericOpcode::G_SSUBE + : GenericOpcode::G_USUBE; + break; + default: + return makeUnexpected(ErrorCode::InternalError, + "unexpected add/sub opcode during scalar narrowing"); + } + + const InstructionDescriptor *Descriptor = + MIRBuilder.target().instructionDescriptor(Instruction.opcode()); + if (Descriptor == nullptr) + return makeUnexpected(ErrorCode::InternalError, + "add/sub narrowing has no instruction descriptor"); + const std::size_t DefinitionCount = Descriptor->NumExplicitDefs; + const MachineOperand *FirstSourceOperand = Instruction.operandAt(DefinitionCount); + const MachineOperand *SecondSourceOperand = Instruction.operandAt(DefinitionCount + 1); + const Register *FirstSource = + FirstSourceOperand == nullptr ? nullptr : FirstSourceOperand->getIf<Register>(); + const Register *SecondSource = + SecondSourceOperand == nullptr ? nullptr : SecondSourceOperand->getIf<Register>(); + if (FirstSource == nullptr || SecondSource == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "add/sub narrowing requires two register sources"); + + Register CarryDestination; + Register CarryIn; + if (DefinitionCount == 2) { + const MachineOperand *CarryOperand = Instruction.operandAt(1); + const Register *Carry = CarryOperand == nullptr ? nullptr : CarryOperand->getIf<Register>(); + if (Carry == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "add/sub narrowing has an invalid carry destination"); + CarryDestination = *Carry; + } + if (Instruction.operandCount() == DefinitionCount + 3) { + const MachineOperand *CarryOperand = Instruction.operandAt(DefinitionCount + 2); + const Register *Carry = CarryOperand == nullptr ? nullptr : CarryOperand->getIf<Register>(); + if (Carry == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "add/sub narrowing has an invalid carry input"); + CarryIn = *Carry; + } + + LowLevelType LeftoverType; + LowLevelType DummyType; + std::vector<Register> FirstParts; + std::vector<Register> SecondParts; + std::vector<Register> FirstLeftovers; + std::vector<Register> SecondLeftovers; + std::vector<Register> DestinationParts; + auto FirstExtracted = extractParts(*FirstSource, *DestinationType, NarrowType, LeftoverType, + FirstParts, FirstLeftovers, MIRBuilder, MRI); + if (!FirstExtracted) + return std::unexpected(FirstExtracted.error()); + auto SecondExtracted = extractParts(*SecondSource, *DestinationType, NarrowType, DummyType, + SecondParts, SecondLeftovers, MIRBuilder, MRI); + if (!SecondExtracted) + return std::unexpected(SecondExtracted.error()); + + const std::size_t NarrowPartCount = FirstParts.size(); + FirstParts.insert(FirstParts.end(), FirstLeftovers.begin(), FirstLeftovers.end()); + SecondParts.insert(SecondParts.end(), SecondLeftovers.begin(), SecondLeftovers.end()); + if (FirstParts.size() != SecondParts.size()) + return makeUnexpected(ErrorCode::InternalError, + "add/sub source splits have different part counts"); + DestinationParts.reserve(FirstParts.size()); + + auto BooleanType = LowLevelType::makeCategoryNeutralScalar(1); + if (!BooleanType) + return std::unexpected(BooleanType.error()); + for (std::size_t Index = 0; Index != FirstParts.size(); ++Index) { + auto PartType = MRI.getType(FirstParts[Index]); + if (!PartType) + return std::unexpected(PartType.error()); + auto PartDestination = MRI.createVirtualRegister(*PartType); + if (!PartDestination) + return std::unexpected(PartDestination.error()); + + Register CarryOut; + if (Index + 1 == FirstParts.size() && !CarryDestination.isNoRegister()) { + CarryOut = CarryDestination; + } else { + auto NewCarry = MRI.createVirtualRegister(*BooleanType); + if (!NewCarry) + return std::unexpected(NewCarry.error()); + CarryOut = *NewCarry; + } + + Expected<MachineInstr *> Built; + if (CarryIn.isNoRegister()) { + Built = MIRBuilder.buildInstr(opcode(FirstOpcode), {*PartDestination, CarryOut}, + {FirstParts[Index], SecondParts[Index]}); + } else if (Index + 1 == FirstParts.size()) { + Built = MIRBuilder.buildInstr(opcode(FinalOpcode), {*PartDestination, CarryOut}, + {FirstParts[Index], SecondParts[Index], CarryIn}); + } else { + Built = MIRBuilder.buildInstr(opcode(MiddleOpcode), {*PartDestination, CarryOut}, + {FirstParts[Index], SecondParts[Index], CarryIn}); + } + if (!Built) + return std::unexpected(Built.error()); + DestinationParts.push_back(*PartDestination); + CarryIn = CarryOut; + } + + std::vector<Register> MainDestinations(DestinationParts.begin(), + DestinationParts.begin() + + static_cast<std::ptrdiff_t>(NarrowPartCount)); + std::vector<Register> LeftoverDestinations(DestinationParts.begin() + + static_cast<std::ptrdiff_t>(NarrowPartCount), + DestinationParts.end()); + auto Inserted = insertParts(*Destination, *DestinationType, NarrowType, MainDestinations, + LeftoverType, LeftoverDestinations); + if (!Inserted) + return std::unexpected(Inserted.error()); + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::narrowScalarMul(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType NarrowType) { + if (TypeIndex != 0 || Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::Unsupported, + "multiply narrowing requires type index zero and two sources"); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *FirstSourceOperand = Instruction.operandAt(1); + const MachineOperand *SecondSourceOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *FirstSource = + FirstSourceOperand == nullptr ? nullptr : FirstSourceOperand->getIf<Register>(); + const Register *SecondSource = + SecondSourceOperand == nullptr ? nullptr : SecondSourceOperand->getIf<Register>(); + if (Destination == nullptr || FirstSource == nullptr || SecondSource == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "multiply narrowing requires register operands"); + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (DestinationType->isVector() || !NarrowType.isCategoryNeutralScalar()) + return makeUnexpected(ErrorCode::Unsupported, + "multiply narrowing requires scalar integer types"); + + const std::uint64_t DestinationSize = DestinationType->minimumBitWidth(); + const std::uint64_t NarrowSize = NarrowType.minimumBitWidth(); + if (NarrowSize == 0 || NarrowSize >= DestinationSize || DestinationSize % NarrowSize != 0) + return makeUnexpected(ErrorCode::Unsupported, + "multiply narrowing requires an even smaller scalar type"); + const std::size_t PartCount = static_cast<std::size_t>(DestinationSize / NarrowSize); + const bool IsMultiplyHigh = Instruction.opcode() == opcode(GenericOpcode::G_UMULH); + const std::size_t TemporaryPartCount = PartCount * (IsMultiplyHigh ? 2U : 1U); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + std::vector<Register> FirstParts; + std::vector<Register> SecondParts; + std::vector<Register> TemporaryDestinations(TemporaryPartCount); + if (auto Extracted = extractParts(*FirstSource, NarrowType, static_cast<int>(PartCount), + FirstParts, MIRBuilder, MRI); + !Extracted) { + return std::unexpected(Extracted.error()); + } + if (auto Extracted = extractParts(*SecondSource, NarrowType, static_cast<int>(PartCount), + SecondParts, MIRBuilder, MRI); + !Extracted) { + return std::unexpected(Extracted.error()); + } + if (auto Multiplied = + multiplyRegisters(TemporaryDestinations, FirstParts, SecondParts, NarrowType); + !Multiplied) { + return std::unexpected(Multiplied.error()); + } + + std::vector<Register> ResultParts(TemporaryDestinations.end() - + static_cast<std::ptrdiff_t>(PartCount), + TemporaryDestinations.end()); + if (auto Built = MIRBuilder.buildMergeLikeInstr(*Destination, ResultParts); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::narrowScalarExt(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType NarrowType) { + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::Unsupported, "extension narrowing requires type index zero"); + + const auto Destination = Instruction.operandAt(0)->registerValue(); + const auto Source = Instruction.operandAt(1)->registerValue(); + if (!Destination || !Source) + return makeUnexpected(ErrorCode::VerificationError, + "extension narrowing requires register operands"); + + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (DestinationType->isVector()) + return makeUnexpected(ErrorCode::Unsupported, "vector extension narrowing is not implemented"); + + std::vector<Register> Parts; + auto GCDType = extractGCDType(Parts, *DestinationType, NarrowType, *Source); + if (!GCDType) + return std::unexpected(GCDType.error()); + auto LCMType = buildLCMMergePieces(*DestinationType, NarrowType, *GCDType, Parts, + static_cast<GenericOpcode>(Instruction.opcode().value())); + if (!LCMType) + return std::unexpected(LCMType.error()); + auto Remerged = buildWidenedRemergeToDst(*Destination, *LCMType, Parts); + if (!Remerged) + return std::unexpected(Remerged.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::narrowScalarBasic(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType NarrowType) { + if (TypeIndex != 0 || Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::Unsupported, + "basic scalar narrowing requires type index zero and two sources"); + + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + const Register *FirstSource = Instruction.operandAt(1)->getIf<Register>(); + const Register *SecondSource = Instruction.operandAt(2)->getIf<Register>(); + if (Destination == nullptr || FirstSource == nullptr || SecondSource == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "basic scalar narrowing requires register operands"); + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + + LowLevelType LeftoverType; + LowLevelType UnusedType; + std::vector<Register> FirstParts; + std::vector<Register> FirstLeftovers; + std::vector<Register> SecondParts; + std::vector<Register> SecondLeftovers; + auto FirstExtracted = extractParts(*FirstSource, *DestinationType, NarrowType, LeftoverType, + FirstParts, FirstLeftovers, MIRBuilder, MRI); + if (!FirstExtracted) + return std::unexpected(FirstExtracted.error()); + auto SecondExtracted = extractParts(*SecondSource, *DestinationType, NarrowType, UnusedType, + SecondParts, SecondLeftovers, MIRBuilder, MRI); + if (!SecondExtracted) + return std::unexpected(SecondExtracted.error()); + if (!*FirstExtracted || !*SecondExtracted || FirstParts.size() != SecondParts.size() || + FirstLeftovers.size() != SecondLeftovers.size()) { + return makeUnexpected(ErrorCode::Unsupported, + "basic scalar operands cannot be split consistently"); + } + + std::vector<Register> DestinationParts; + DestinationParts.reserve(FirstParts.size()); + for (std::size_t Index = 0; Index != FirstParts.size(); ++Index) { + auto Built = MIRBuilder.buildInstr(Instruction.opcode(), {NarrowType}, + {FirstParts[Index], SecondParts[Index]}); + if (!Built) + return std::unexpected(Built.error()); + auto Result = resultRegister(**Built); + if (!Result) + return std::unexpected(Result.error()); + DestinationParts.push_back(*Result); + } + + std::vector<Register> DestinationLeftovers; + DestinationLeftovers.reserve(FirstLeftovers.size()); + for (std::size_t Index = 0; Index != FirstLeftovers.size(); ++Index) { + auto Built = MIRBuilder.buildInstr(Instruction.opcode(), {LeftoverType}, + {FirstLeftovers[Index], SecondLeftovers[Index]}); + if (!Built) + return std::unexpected(Built.error()); + auto Result = resultRegister(**Built); + if (!Result) + return std::unexpected(Result.error()); + DestinationLeftovers.push_back(*Result); + } + + if (auto Inserted = insertParts(*Destination, *DestinationType, NarrowType, DestinationParts, + LeftoverType, DestinationLeftovers); + !Inserted) { + return std::unexpected(Inserted.error()); + } + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::narrowScalarSelect(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType NarrowType) { + if (TypeIndex != 0 || Instruction.operandCount() != 4) + return makeUnexpected(ErrorCode::Unsupported, + "select narrowing requires type index zero and three sources"); + + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + const Register *Condition = Instruction.operandAt(1)->getIf<Register>(); + const Register *TrueSource = Instruction.operandAt(2)->getIf<Register>(); + const Register *FalseSource = Instruction.operandAt(3)->getIf<Register>(); + if (Destination == nullptr || Condition == nullptr || TrueSource == nullptr || + FalseSource == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "select narrowing requires register operands"); + } + auto DestinationType = MRI.getType(*Destination); + auto ConditionType = MRI.getType(*Condition); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (!ConditionType) + return std::unexpected(ConditionType.error()); + if (ConditionType->isVector()) + return makeUnexpected(ErrorCode::Unsupported, "vector select narrowing is not implemented"); + + LowLevelType LeftoverType; + LowLevelType UnusedType; + std::vector<Register> TrueParts; + std::vector<Register> TrueLeftovers; + std::vector<Register> FalseParts; + std::vector<Register> FalseLeftovers; + auto TrueExtracted = extractParts(*TrueSource, *DestinationType, NarrowType, LeftoverType, + TrueParts, TrueLeftovers, MIRBuilder, MRI); + if (!TrueExtracted) + return std::unexpected(TrueExtracted.error()); + auto FalseExtracted = extractParts(*FalseSource, *DestinationType, NarrowType, UnusedType, + FalseParts, FalseLeftovers, MIRBuilder, MRI); + if (!FalseExtracted) + return std::unexpected(FalseExtracted.error()); + if (!*TrueExtracted || !*FalseExtracted || TrueParts.size() != FalseParts.size() || + TrueLeftovers.size() != FalseLeftovers.size()) { + return makeUnexpected(ErrorCode::Unsupported, "select operands cannot be split consistently"); + } + + std::vector<Register> DestinationParts; + DestinationParts.reserve(TrueParts.size()); + for (std::size_t Index = 0; Index != TrueParts.size(); ++Index) { + auto Built = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {NarrowType}, + {*Condition, TrueParts[Index], FalseParts[Index]}); + if (!Built) + return std::unexpected(Built.error()); + auto Result = resultRegister(**Built); + if (!Result) + return std::unexpected(Result.error()); + DestinationParts.push_back(*Result); + } + + std::vector<Register> DestinationLeftovers; + DestinationLeftovers.reserve(TrueLeftovers.size()); + for (std::size_t Index = 0; Index != TrueLeftovers.size(); ++Index) { + auto Built = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {LeftoverType}, + {*Condition, TrueLeftovers[Index], FalseLeftovers[Index]}); + if (!Built) + return std::unexpected(Built.error()); + auto Result = resultRegister(**Built); + if (!Result) + return std::unexpected(Result.error()); + DestinationLeftovers.push_back(*Result); + } + + if (auto Inserted = insertParts(*Destination, *DestinationType, NarrowType, DestinationParts, + LeftoverType, DestinationLeftovers); + !Inserted) { + return std::unexpected(Inserted.error()); + } + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::narrowScalarShiftByConstant(MachineInstr &Instruction, + const APInt &Amount, + LowLevelType HalfType, + LowLevelType AmountType) { + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + const Register *Source = Instruction.operandAt(1)->getIf<Register>(); + if (Destination == nullptr || Source == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "constant shift narrowing requires register operands"); + + auto LowPart = MRI.createVirtualRegister(HalfType); + auto HighPart = MRI.createVirtualRegister(HalfType); + if (!LowPart) + return std::unexpected(LowPart.error()); + if (!HighPart) + return std::unexpected(HighPart.error()); + if (auto Built = MIRBuilder.buildUnmergeValues({*LowPart, *HighPart}, *Source); !Built) + return std::unexpected(Built.error()); + + if (Amount.isZero()) { + if (auto Built = MIRBuilder.buildMergeLikeInstr(*Destination, {*LowPart, *HighPart}); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); + } + + const std::uint64_t HalfBits = HalfType.minimumBitWidth(); + const std::uint64_t TotalBits = HalfBits * 2U; + const bool AmountExceeds64Bits = Amount.activeBitWidth() > 64; + const std::uint64_t AmountValue = Amount.word(0); + const bool ExceedsTotal = AmountExceeds64Bits || AmountValue > TotalBits; + const bool ExceedsHalf = AmountExceeds64Bits || AmountValue > HalfBits; + + const auto BuildConstant = [&](LowLevelType Type, std::uint64_t Value) { + return buildConstantRegister(MIRBuilder, Type, static_cast<std::int64_t>(Value)); + }; + const auto BuildBinary = [&](GenericOpcode Generic, Register Left, Register Right) { + return buildResultInstruction(MIRBuilder, Generic, HalfType, {Left, Right}); + }; + + Register LowResult; + Register HighResult; + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (Generic == GenericOpcode::G_SHL) { + if (ExceedsTotal) { + auto Zero = BuildConstant(HalfType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + LowResult = *Zero; + HighResult = *Zero; + } else if (ExceedsHalf) { + auto Zero = BuildConstant(HalfType, 0); + auto Shift = BuildConstant(AmountType, AmountValue - HalfBits); + if (!Zero) + return std::unexpected(Zero.error()); + if (!Shift) + return std::unexpected(Shift.error()); + auto Shifted = BuildBinary(GenericOpcode::G_SHL, *LowPart, *Shift); + if (!Shifted) + return std::unexpected(Shifted.error()); + LowResult = *Zero; + HighResult = *Shifted; + } else if (AmountValue == HalfBits) { + auto Zero = BuildConstant(HalfType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + LowResult = *Zero; + HighResult = *LowPart; + } else { + auto Shift = BuildConstant(AmountType, AmountValue); + auto InverseShift = BuildConstant(AmountType, HalfBits - AmountValue); + if (!Shift) + return std::unexpected(Shift.error()); + if (!InverseShift) + return std::unexpected(InverseShift.error()); + auto LowShift = BuildBinary(GenericOpcode::G_SHL, *LowPart, *Shift); + auto HighShift = BuildBinary(GenericOpcode::G_SHL, *HighPart, *Shift); + auto Carry = BuildBinary(GenericOpcode::G_LSHR, *LowPart, *InverseShift); + if (!LowShift) + return std::unexpected(LowShift.error()); + if (!HighShift) + return std::unexpected(HighShift.error()); + if (!Carry) + return std::unexpected(Carry.error()); + auto Combined = BuildBinary(GenericOpcode::G_OR, *HighShift, *Carry); + if (!Combined) + return std::unexpected(Combined.error()); + LowResult = *LowShift; + HighResult = *Combined; + } + } else if (Generic == GenericOpcode::G_LSHR) { + if (ExceedsTotal) { + auto Zero = BuildConstant(HalfType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + LowResult = *Zero; + HighResult = *Zero; + } else if (ExceedsHalf) { + auto Shift = BuildConstant(AmountType, AmountValue - HalfBits); + auto Zero = BuildConstant(HalfType, 0); + if (!Shift) + return std::unexpected(Shift.error()); + if (!Zero) + return std::unexpected(Zero.error()); + auto Shifted = BuildBinary(GenericOpcode::G_LSHR, *HighPart, *Shift); + if (!Shifted) + return std::unexpected(Shifted.error()); + LowResult = *Shifted; + HighResult = *Zero; + } else if (AmountValue == HalfBits) { + auto Zero = BuildConstant(HalfType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + LowResult = *HighPart; + HighResult = *Zero; + } else { + auto Shift = BuildConstant(AmountType, AmountValue); + auto InverseShift = BuildConstant(AmountType, HalfBits - AmountValue); + if (!Shift) + return std::unexpected(Shift.error()); + if (!InverseShift) + return std::unexpected(InverseShift.error()); + auto LowShift = BuildBinary(GenericOpcode::G_LSHR, *LowPart, *Shift); + auto Carry = BuildBinary(GenericOpcode::G_SHL, *HighPart, *InverseShift); + auto HighShift = BuildBinary(GenericOpcode::G_LSHR, *HighPart, *Shift); + if (!LowShift) + return std::unexpected(LowShift.error()); + if (!Carry) + return std::unexpected(Carry.error()); + if (!HighShift) + return std::unexpected(HighShift.error()); + auto Combined = BuildBinary(GenericOpcode::G_OR, *LowShift, *Carry); + if (!Combined) + return std::unexpected(Combined.error()); + LowResult = *Combined; + HighResult = *HighShift; + } + } else if (Generic == GenericOpcode::G_ASHR) { + auto SignShift = BuildConstant(AmountType, HalfBits - 1U); + if (!SignShift) + return std::unexpected(SignShift.error()); + auto Sign = BuildBinary(GenericOpcode::G_ASHR, *HighPart, *SignShift); + if (!Sign) + return std::unexpected(Sign.error()); + if (ExceedsTotal) { + LowResult = *Sign; + HighResult = *Sign; + } else if (ExceedsHalf) { + auto Shift = BuildConstant(AmountType, AmountValue - HalfBits); + if (!Shift) + return std::unexpected(Shift.error()); + auto Shifted = BuildBinary(GenericOpcode::G_ASHR, *HighPart, *Shift); + if (!Shifted) + return std::unexpected(Shifted.error()); + LowResult = *Shifted; + HighResult = *Sign; + } else if (AmountValue == HalfBits) { + LowResult = *HighPart; + HighResult = *Sign; + } else { + auto Shift = BuildConstant(AmountType, AmountValue); + auto InverseShift = BuildConstant(AmountType, HalfBits - AmountValue); + if (!Shift) + return std::unexpected(Shift.error()); + if (!InverseShift) + return std::unexpected(InverseShift.error()); + auto LowShift = BuildBinary(GenericOpcode::G_LSHR, *LowPart, *Shift); + auto Carry = BuildBinary(GenericOpcode::G_SHL, *HighPart, *InverseShift); + auto HighShift = BuildBinary(GenericOpcode::G_ASHR, *HighPart, *Shift); + if (!LowShift) + return std::unexpected(LowShift.error()); + if (!Carry) + return std::unexpected(Carry.error()); + if (!HighShift) + return std::unexpected(HighShift.error()); + auto Combined = BuildBinary(GenericOpcode::G_OR, *LowShift, *Carry); + if (!Combined) + return std::unexpected(Combined.error()); + LowResult = *Combined; + HighResult = *HighShift; + } + } else { + return makeUnexpected(ErrorCode::InternalError, + "constant shift narrowing received a non-shift opcode"); + } + + if (auto Built = MIRBuilder.buildMergeLikeInstr(*Destination, {LowResult, HighResult}); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::narrowScalarShift(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType NarrowType) { + if (Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::VerificationError, + "shift narrowing requires one destination and two sources"); + if (TypeIndex == 1) { + Observer.changingInstr(Instruction); + auto Narrowed = narrowScalarSrc(Instruction, NarrowType, 2); + if (!Narrowed) + return std::unexpected(Narrowed.error()); + Observer.changedInstr(Instruction); + return {}; + } + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::Unsupported, "shift narrowing has an invalid type index"); + + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + const Register *Source = Instruction.operandAt(1)->getIf<Register>(); + const Register *Amount = Instruction.operandAt(2)->getIf<Register>(); + if (Destination == nullptr || Source == nullptr || Amount == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "shift narrowing requires register operands"); + auto DestinationType = MRI.getType(*Destination); + auto AmountType = MRI.getType(*Amount); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (!AmountType) + return std::unexpected(AmountType.error()); + if (!DestinationType->isCategoryNeutralScalar()) + return makeUnexpected(ErrorCode::Unsupported, "vector shift narrowing is not implemented"); + + const std::uint64_t DestinationBits = DestinationType->scalarBitWidth(); + if (DestinationBits == 0 || DestinationBits % 2U != 0) + return makeUnexpected(ErrorCode::Unsupported, "shift narrowing requires an even scalar width"); + + if (NarrowType.isCategoryNeutralScalar() && NarrowType.scalarBitWidth() != 0 && + DestinationBits % NarrowType.scalarBitWidth() == 0 && + DestinationBits / NarrowType.scalarBitWidth() >= 8U) { + return narrowScalarShiftMultiway(Instruction, NarrowType); + } + + auto HalfType = LowLevelType::makeCategoryNeutralScalar(DestinationBits / 2U); + if (!HalfType) + return std::unexpected(HalfType.error()); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Constant = getIConstantVRegValWithLookThrough(*Amount, MRI)) + return narrowScalarShiftByConstant(Instruction, Constant->Value, *HalfType, *AmountType); + + auto HalfBits = buildConstantRegister(MIRBuilder, *AmountType, + static_cast<std::int64_t>(DestinationBits / 2U)); + if (!HalfBits) + return std::unexpected(HalfBits.error()); + auto LowPart = MRI.createVirtualRegister(*HalfType); + auto HighPart = MRI.createVirtualRegister(*HalfType); + if (!LowPart) + return std::unexpected(LowPart.error()); + if (!HighPart) + return std::unexpected(HighPart.error()); + if (auto Built = MIRBuilder.buildUnmergeValues({*LowPart, *HighPart}, *Source); !Built) + return std::unexpected(Built.error()); + + auto AmountExcess = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *AmountType, {*Amount, *HalfBits}); + auto AmountLack = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *AmountType, {*HalfBits, *Amount}); + auto ZeroAmount = buildConstantRegister(MIRBuilder, *AmountType, 0); + auto ConditionType = LowLevelType::makeCategoryNeutralScalar(1); + if (!AmountExcess) + return std::unexpected(AmountExcess.error()); + if (!AmountLack) + return std::unexpected(AmountLack.error()); + if (!ZeroAmount) + return std::unexpected(ZeroAmount.error()); + if (!ConditionType) + return std::unexpected(ConditionType.error()); + auto IsShort = buildICmpRegister(MIRBuilder, IntegerPredicate::UnsignedLessThan, *ConditionType, + *Amount, *HalfBits); + auto IsZero = + buildICmpRegister(MIRBuilder, IntegerPredicate::Equal, *ConditionType, *Amount, *ZeroAmount); + if (!IsShort) + return std::unexpected(IsShort.error()); + if (!IsZero) + return std::unexpected(IsZero.error()); + + const auto Build = [&](GenericOpcode Generic, Register Left, Register Right) { + return buildResultInstruction(MIRBuilder, Generic, *HalfType, {Left, Right}); + }; + const auto Select = [&](Register Condition, Register TrueValue, Register FalseValue) { + return buildResultInstruction(MIRBuilder, GenericOpcode::G_SELECT, *HalfType, + {Condition, TrueValue, FalseValue}); + }; + + Register LowResult; + Register HighResult; + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (Generic == GenericOpcode::G_SHL) { + auto LowShort = Build(GenericOpcode::G_SHL, *LowPart, *Amount); + auto LowCarry = Build(GenericOpcode::G_LSHR, *LowPart, *AmountLack); + auto HighShift = Build(GenericOpcode::G_SHL, *HighPart, *Amount); + if (!LowShort) + return std::unexpected(LowShort.error()); + if (!LowCarry) + return std::unexpected(LowCarry.error()); + if (!HighShift) + return std::unexpected(HighShift.error()); + auto HighShort = Build(GenericOpcode::G_OR, *LowCarry, *HighShift); + auto LowLong = buildConstantRegister(MIRBuilder, *HalfType, 0); + auto HighLong = Build(GenericOpcode::G_SHL, *LowPart, *AmountExcess); + if (!HighShort) + return std::unexpected(HighShort.error()); + if (!LowLong) + return std::unexpected(LowLong.error()); + if (!HighLong) + return std::unexpected(HighLong.error()); + auto Low = Select(*IsShort, *LowShort, *LowLong); + auto HighNonZero = Select(*IsShort, *HighShort, *HighLong); + if (!Low) + return std::unexpected(Low.error()); + if (!HighNonZero) + return std::unexpected(HighNonZero.error()); + auto High = Select(*IsZero, *HighPart, *HighNonZero); + if (!High) + return std::unexpected(High.error()); + LowResult = *Low; + HighResult = *High; + } else if (Generic == GenericOpcode::G_LSHR || Generic == GenericOpcode::G_ASHR) { + auto HighShort = Build(Generic, *HighPart, *Amount); + auto LowShift = Build(GenericOpcode::G_LSHR, *LowPart, *Amount); + auto HighCarry = Build(GenericOpcode::G_SHL, *HighPart, *AmountLack); + if (!HighShort) + return std::unexpected(HighShort.error()); + if (!LowShift) + return std::unexpected(LowShift.error()); + if (!HighCarry) + return std::unexpected(HighCarry.error()); + auto LowShort = Build(GenericOpcode::G_OR, *LowShift, *HighCarry); + if (!LowShort) + return std::unexpected(LowShort.error()); + + Expected<Register> HighLong = + Generic == GenericOpcode::G_LSHR + ? buildConstantRegister(MIRBuilder, *HalfType, 0) + : buildConstantRegister(MIRBuilder, *AmountType, + static_cast<std::int64_t>(DestinationBits / 2U - 1U)) + .and_then([&](Register Shift) { + return Build(GenericOpcode::G_ASHR, *HighPart, Shift); + }); + auto LowLong = Build(Generic, *HighPart, *AmountExcess); + if (!HighLong) + return std::unexpected(HighLong.error()); + if (!LowLong) + return std::unexpected(LowLong.error()); + auto LowNonZero = Select(*IsShort, *LowShort, *LowLong); + if (!LowNonZero) + return std::unexpected(LowNonZero.error()); + auto Low = Select(*IsZero, *LowPart, *LowNonZero); + auto High = Select(*IsShort, *HighShort, *HighLong); + if (!Low) + return std::unexpected(Low.error()); + if (!High) + return std::unexpected(High.error()); + LowResult = *Low; + HighResult = *High; + } else { + return makeUnexpected(ErrorCode::InternalError, "shift narrowing received a non-shift opcode"); + } + + if (auto Built = MIRBuilder.buildMergeLikeInstr(*Destination, {LowResult, HighResult}); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); +} + +Expected<Register> LegalizerHelper::buildConstantShiftPart(GenericOpcode Generic, + std::size_t PartIndex, + std::size_t PartCount, + const std::vector<Register> &SourceParts, + const ShiftParameters &Parameters, + LowLevelType TargetType) { + const auto WordShift = getIConstantVRegValWithLookThrough(Parameters.WordShift, MRI, false); + const auto BitShift = getIConstantVRegValWithLookThrough(Parameters.BitShift, MRI, false); + if (!WordShift || !BitShift || WordShift->Value.activeBitWidth() > 64 || + BitShift->Value.activeBitWidth() > 64) { + return makeUnexpected(ErrorCode::InternalError, + "multiway shift parameters are not representable constants"); + } + const std::uint64_t ShiftWords = WordShift->Value.word(0); + const std::uint64_t ShiftBits = BitShift->Value.word(0); + const bool NeedsCarry = ShiftBits != 0; + const auto Build = [&](GenericOpcode Opcode, Register Left, Register Right) { + return buildResultInstruction(MIRBuilder, Opcode, TargetType, {Left, Right}); + }; + + if (Generic == GenericOpcode::G_SHL) { + if (PartIndex < ShiftWords) + return Parameters.Zero; + const std::size_t SourceIndex = PartIndex - static_cast<std::size_t>(ShiftWords); + if (!NeedsCarry) + return SourceParts[SourceIndex]; + auto High = Build(GenericOpcode::G_SHL, SourceParts[SourceIndex], Parameters.BitShift); + if (!High) + return std::unexpected(High.error()); + if (SourceIndex == 0) + return *High; + auto Low = + Build(GenericOpcode::G_LSHR, SourceParts[SourceIndex - 1U], Parameters.InverseBitShift); + if (!Low) + return std::unexpected(Low.error()); + return Build(GenericOpcode::G_OR, *High, *Low); + } + + if (Generic == GenericOpcode::G_LSHR) { + const std::uint64_t SourceIndex = PartIndex + ShiftWords; + if (SourceIndex >= PartCount) + return Parameters.Zero; + if (!NeedsCarry) + return SourceParts[static_cast<std::size_t>(SourceIndex)]; + auto Low = Build(GenericOpcode::G_LSHR, SourceParts[static_cast<std::size_t>(SourceIndex)], + Parameters.BitShift); + if (!Low) + return std::unexpected(Low.error()); + if (SourceIndex + 1U >= PartCount) + return *Low; + auto High = Build(GenericOpcode::G_SHL, SourceParts[static_cast<std::size_t>(SourceIndex + 1U)], + Parameters.InverseBitShift); + if (!High) + return std::unexpected(High.error()); + return Build(GenericOpcode::G_OR, *Low, *High); + } + + if (Generic == GenericOpcode::G_ASHR) { + const std::uint64_t SourceIndex = PartIndex + ShiftWords; + if (SourceIndex >= PartCount) + return Parameters.SignBit; + if (!NeedsCarry) + return SourceParts[static_cast<std::size_t>(SourceIndex)]; + const GenericOpcode MainOpcode = + SourceIndex + 1U == PartCount ? GenericOpcode::G_ASHR : GenericOpcode::G_LSHR; + auto Low = + Build(MainOpcode, SourceParts[static_cast<std::size_t>(SourceIndex)], Parameters.BitShift); + if (!Low) + return std::unexpected(Low.error()); + const Register HighSource = SourceIndex + 1U < PartCount + ? SourceParts[static_cast<std::size_t>(SourceIndex + 1U)] + : Parameters.SignBit; + auto High = Build(GenericOpcode::G_SHL, HighSource, Parameters.InverseBitShift); + if (!High) + return std::unexpected(High.error()); + return Build(GenericOpcode::G_OR, *Low, *High); + } + + return makeUnexpected(ErrorCode::InternalError, + "constant shift part received a non-shift opcode"); +} + +Expected<Register> LegalizerHelper::buildVariableShiftPart(GenericOpcode Generic, + Register MainOperand, + Register ShiftAmount, + LowLevelType TargetType, + Register CarryOperand) { + const GenericOpcode MainOpcode = + Generic == GenericOpcode::G_ASHR ? GenericOpcode::G_LSHR : Generic; + auto MainShifted = + buildResultInstruction(MIRBuilder, MainOpcode, TargetType, {MainOperand, ShiftAmount}); + if (!MainShifted) + return std::unexpected(MainShifted.error()); + if (CarryOperand.isNoRegister()) + return *MainShifted; + + auto AmountType = MRI.getType(ShiftAmount); + if (!AmountType) + return std::unexpected(AmountType.error()); + auto ZeroAmount = buildConstantRegister(MIRBuilder, *AmountType, 0); + auto BoolType = LowLevelType::makeCategoryNeutralScalar(1); + if (!ZeroAmount) + return std::unexpected(ZeroAmount.error()); + if (!BoolType) + return std::unexpected(BoolType.error()); + auto IsZero = + buildICmpRegister(MIRBuilder, IntegerPredicate::Equal, *BoolType, ShiftAmount, *ZeroAmount); + auto TargetBits = buildConstantRegister(MIRBuilder, *AmountType, + static_cast<std::int64_t>(TargetType.scalarBitWidth())); + if (!IsZero) + return std::unexpected(IsZero.error()); + if (!TargetBits) + return std::unexpected(TargetBits.error()); + auto InverseAmount = buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *AmountType, + {*TargetBits, ShiftAmount}); + if (!InverseAmount) + return std::unexpected(InverseAmount.error()); + + const GenericOpcode CarryOpcode = + Generic == GenericOpcode::G_SHL ? GenericOpcode::G_LSHR : GenericOpcode::G_SHL; + auto CarryBits = + buildResultInstruction(MIRBuilder, CarryOpcode, TargetType, {CarryOperand, *InverseAmount}); + auto Zero = buildConstantRegister(MIRBuilder, TargetType, 0); + if (!CarryBits) + return std::unexpected(CarryBits.error()); + if (!Zero) + return std::unexpected(Zero.error()); + auto SafeCarry = buildResultInstruction(MIRBuilder, GenericOpcode::G_SELECT, TargetType, + {*IsZero, *Zero, *CarryBits}); + if (!SafeCarry) + return std::unexpected(SafeCarry.error()); + return buildResultInstruction(MIRBuilder, GenericOpcode::G_OR, TargetType, + {*MainShifted, *SafeCarry}); +} + +Expected<void> LegalizerHelper::narrowScalarShiftByConstantMultiway(MachineInstr &Instruction, + const APInt &Amount, + LowLevelType TargetType, + LowLevelType AmountType) { + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + const Register *Source = Instruction.operandAt(1)->getIf<Register>(); + if (Destination == nullptr || Source == nullptr || Amount.activeBitWidth() > 64) + return makeUnexpected(ErrorCode::Unsupported, + "constant multiway shift requires representable register operands"); + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + const std::uint64_t DestinationBits = DestinationType->scalarBitWidth(); + const std::uint64_t TargetBits = TargetType.scalarBitWidth(); + if (TargetBits == 0 || DestinationBits % TargetBits != 0) + return makeUnexpected(ErrorCode::VerificationError, + "multiway shift target type does not divide the destination"); + const std::size_t PartCount = static_cast<std::size_t>(DestinationBits / TargetBits); + + std::vector<Register> SourceParts; + auto Extracted = + extractParts(*Source, TargetType, static_cast<int>(PartCount), SourceParts, MIRBuilder, MRI); + if (!Extracted) + return std::unexpected(Extracted.error()); + if (Amount.isZero()) { + if (auto Built = MIRBuilder.buildMergeLikeInstr(*Destination, SourceParts); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); + } + + const std::uint64_t AmountValue = Amount.word(0); + const std::uint64_t ShiftWords = AmountValue / TargetBits; + const std::uint64_t ShiftBits = AmountValue % TargetBits; + ShiftParameters Parameters; + auto WordShift = + buildConstantRegister(MIRBuilder, AmountType, static_cast<std::int64_t>(ShiftWords)); + auto BitShift = + buildConstantRegister(MIRBuilder, AmountType, static_cast<std::int64_t>(ShiftBits)); + auto InverseBitShift = buildConstantRegister(MIRBuilder, AmountType, + static_cast<std::int64_t>(TargetBits - ShiftBits)); + auto Zero = buildConstantRegister(MIRBuilder, TargetType, 0); + if (!WordShift) + return std::unexpected(WordShift.error()); + if (!BitShift) + return std::unexpected(BitShift.error()); + if (!InverseBitShift) + return std::unexpected(InverseBitShift.error()); + if (!Zero) + return std::unexpected(Zero.error()); + Parameters.WordShift = *WordShift; + Parameters.BitShift = *BitShift; + Parameters.InverseBitShift = *InverseBitShift; + Parameters.Zero = *Zero; + + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (Generic == GenericOpcode::G_ASHR) { + auto SignAmount = + buildConstantRegister(MIRBuilder, AmountType, static_cast<std::int64_t>(TargetBits - 1U)); + if (!SignAmount) + return std::unexpected(SignAmount.error()); + auto SignBit = buildResultInstruction(MIRBuilder, GenericOpcode::G_ASHR, TargetType, + {SourceParts.back(), *SignAmount}); + if (!SignBit) + return std::unexpected(SignBit.error()); + Parameters.SignBit = *SignBit; + } + + std::vector<Register> DestinationParts; + DestinationParts.reserve(PartCount); + for (std::size_t Index = 0; Index != PartCount; ++Index) { + auto Part = + buildConstantShiftPart(Generic, Index, PartCount, SourceParts, Parameters, TargetType); + if (!Part) + return std::unexpected(Part.error()); + DestinationParts.push_back(*Part); + } + if (auto Built = MIRBuilder.buildMergeLikeInstr(*Destination, DestinationParts); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::narrowScalarShiftMultiway(MachineInstr &Instruction, + LowLevelType TargetType) { + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + const Register *Source = Instruction.operandAt(1)->getIf<Register>(); + const Register *Amount = Instruction.operandAt(2)->getIf<Register>(); + if (Destination == nullptr || Source == nullptr || Amount == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "multiway shift narrowing requires register operands"); + auto DestinationType = MRI.getType(*Destination); + auto AmountType = MRI.getType(*Amount); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (!AmountType) + return std::unexpected(AmountType.error()); + const std::uint64_t DestinationBits = DestinationType->scalarBitWidth(); + const std::uint64_t TargetBits = TargetType.scalarBitWidth(); + if (TargetBits == 0 || DestinationBits % TargetBits != 0 || !std::has_single_bit(TargetBits)) { + return makeUnexpected(ErrorCode::VerificationError, + "multiway shift requires a power-of-two divisor type"); + } + const std::size_t PartCount = static_cast<std::size_t>(DestinationBits / TargetBits); + if (auto Constant = getIConstantVRegValWithLookThrough(*Amount, MRI)) { + return narrowScalarShiftByConstantMultiway(Instruction, Constant->Value, TargetType, + *AmountType); + } + + std::vector<Register> SourceParts; + auto Extracted = + extractParts(*Source, TargetType, static_cast<int>(PartCount), SourceParts, MIRBuilder, MRI); + if (!Extracted) + return std::unexpected(Extracted.error()); + + auto ZeroAmount = buildConstantRegister(MIRBuilder, *AmountType, 0); + auto BoolType = LowLevelType::makeCategoryNeutralScalar(1); + if (!ZeroAmount) + return std::unexpected(ZeroAmount.error()); + if (!BoolType) + return std::unexpected(BoolType.error()); + auto IsZero = + buildICmpRegister(MIRBuilder, IntegerPredicate::Equal, *BoolType, *Amount, *ZeroAmount); + if (!IsZero) + return std::unexpected(IsZero.error()); + + const auto TargetLog2 = static_cast<std::int64_t>(std::countr_zero(TargetBits)); + auto Log2 = buildConstantRegister(MIRBuilder, *AmountType, TargetLog2); + auto Mask = + buildConstantRegister(MIRBuilder, *AmountType, static_cast<std::int64_t>(TargetBits - 1U)); + if (!Log2) + return std::unexpected(Log2.error()); + if (!Mask) + return std::unexpected(Mask.error()); + auto WordShift = + buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *AmountType, {*Amount, *Log2}); + auto BitShift = + buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *AmountType, {*Amount, *Mask}); + auto Zero = buildConstantRegister(MIRBuilder, TargetType, 0); + if (!WordShift) + return std::unexpected(WordShift.error()); + if (!BitShift) + return std::unexpected(BitShift.error()); + if (!Zero) + return std::unexpected(Zero.error()); + + Register FillValue = *Zero; + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (Generic == GenericOpcode::G_ASHR) { + auto SignAmount = + buildConstantRegister(MIRBuilder, *AmountType, static_cast<std::int64_t>(TargetBits - 1U)); + if (!SignAmount) + return std::unexpected(SignAmount.error()); + auto Sign = buildResultInstruction(MIRBuilder, GenericOpcode::G_ASHR, TargetType, + {SourceParts.back(), *SignAmount}); + if (!Sign) + return std::unexpected(Sign.error()); + FillValue = *Sign; + } + + std::vector<Register> DestinationParts(PartCount); + for (std::size_t PartIndex = 0; PartIndex != PartCount; ++PartIndex) { + Register InBoundsResult = FillValue; + for (std::size_t WordIndex = 0; WordIndex != PartCount; ++WordIndex) { + auto WordConstant = + buildConstantRegister(MIRBuilder, *AmountType, static_cast<std::int64_t>(WordIndex)); + if (!WordConstant) + return std::unexpected(WordConstant.error()); + auto IsWord = buildICmpRegister(MIRBuilder, IntegerPredicate::Equal, *BoolType, *WordShift, + *WordConstant); + if (!IsWord) + return std::unexpected(IsWord.error()); + + std::ptrdiff_t MainIndex; + std::ptrdiff_t CarryIndex; + if (Generic == GenericOpcode::G_SHL) { + MainIndex = static_cast<std::ptrdiff_t>(PartIndex) - static_cast<std::ptrdiff_t>(WordIndex); + CarryIndex = MainIndex - 1; + } else if (Generic == GenericOpcode::G_LSHR || Generic == GenericOpcode::G_ASHR) { + MainIndex = static_cast<std::ptrdiff_t>(PartIndex) + static_cast<std::ptrdiff_t>(WordIndex); + CarryIndex = MainIndex + 1; + } else { + return makeUnexpected(ErrorCode::InternalError, + "multiway shift received a non-shift opcode"); + } + + Register Result = FillValue; + if (MainIndex >= 0 && MainIndex < static_cast<std::ptrdiff_t>(PartCount)) { + Register Carry; + if (CarryIndex >= 0 && CarryIndex < static_cast<std::ptrdiff_t>(PartCount)) + Carry = SourceParts[static_cast<std::size_t>(CarryIndex)]; + else if (Generic == GenericOpcode::G_ASHR && + CarryIndex >= static_cast<std::ptrdiff_t>(PartCount)) + Carry = FillValue; + auto Shifted = + buildVariableShiftPart(Generic, SourceParts[static_cast<std::size_t>(MainIndex)], + *BitShift, TargetType, Carry); + if (!Shifted) + return std::unexpected(Shifted.error()); + Result = *Shifted; + } + + auto Selected = buildResultInstruction(MIRBuilder, GenericOpcode::G_SELECT, TargetType, + {*IsWord, Result, InBoundsResult}); + if (!Selected) + return std::unexpected(Selected.error()); + InBoundsResult = *Selected; + } + auto Selected = buildResultInstruction(MIRBuilder, GenericOpcode::G_SELECT, TargetType, + {*IsZero, SourceParts[PartIndex], InBoundsResult}); + if (!Selected) + return std::unexpected(Selected.error()); + DestinationParts[PartIndex] = *Selected; + } + + if (auto Built = MIRBuilder.buildMergeLikeInstr(*Destination, DestinationParts); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::narrowScalarLoadStore(MachineInstr &Instruction, + std::size_t TypeIndex, + LowLevelType NarrowType) { + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (Generic != GenericOpcode::G_LOAD && Generic != GenericOpcode::G_STORE) { + return makeUnexpected(ErrorCode::InvalidArgument, + "memory scalar narrowing received an unsupported opcode"); + } + if (TypeIndex != 0 || !NarrowType.isCategoryNeutralScalar() || + NarrowType.minimumBitWidth() == 0 || NarrowType.minimumBitWidth() % 8U != 0) { + return makeUnexpected(ErrorCode::VerificationError, + "memory scalar narrowing requires a byte-sized scalar value type"); + } + if (Instruction.operandCount() != 2 || Instruction.memOperands().size() != 1 || + Instruction.memOperands().front() == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "memory scalar narrowing requires one address and one memory operand"); + } + + const MachineOperand *ValueOperand = Instruction.operandAt(0); + const MachineOperand *AddressOperand = Instruction.operandAt(1); + const Register *Value = ValueOperand == nullptr ? nullptr : ValueOperand->getIf<Register>(); + const Register *Address = AddressOperand == nullptr ? nullptr : AddressOperand->getIf<Register>(); + if (Value == nullptr || Address == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "memory scalar narrowing requires register value and address operands"); + } + auto ValueType = MRI.getType(*Value); + if (!ValueType) + return std::unexpected(ValueType.error()); + if (!ValueType->isCategoryNeutralScalar() || + NarrowType.minimumBitWidth() >= ValueType->minimumBitWidth()) { + return makeUnexpected(ErrorCode::Unsupported, + "memory scalar narrowing requires a smaller integer scalar type"); + } + + const MachineMemOperand &Memory = *Instruction.memOperands().front(); + const MachineMemOperandDesc &Description = Memory.description(); + if (!Description.Size.isKnown() || Description.Size.isScalable() || !Description.MemoryType) { + return makeUnexpected(ErrorCode::Unsupported, + "memory scalar narrowing requires a fixed typed memory operand"); + } + const std::uint64_t MemorySizeInBits = Description.Size.minimumBytes() * 8U; + if (MemorySizeInBits != ValueType->minimumBitWidth()) { + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + if (Generic == GenericOpcode::G_LOAD) { + auto Temporary = MRI.createVirtualRegister(NarrowType); + if (!Temporary) + return std::unexpected(Temporary.error()); + auto Load = MIRBuilder.buildInstr(opcode(GenericOpcode::G_LOAD)); + if (!Load) + return std::unexpected(Load.error()); + if (auto Added = Load->addDef(*Temporary); !Added) + return std::unexpected(Added.error()); + if (auto Added = Load->addUse(*Address); !Added) + return std::unexpected(Added.error()); + if (auto Added = Load->addMemOperand(Memory); !Added) + return std::unexpected(Added.error()); + if (auto Built = Load->build(); !Built) + return std::unexpected(Built.error()); + if (auto Extended = MIRBuilder.buildAnyExt(*Value, *Temporary); !Extended) + return std::unexpected(Extended.error()); + } else { + auto Temporary = MRI.createVirtualRegister(NarrowType); + if (!Temporary) + return std::unexpected(Temporary.error()); + if (auto Truncated = MIRBuilder.buildTrunc(*Temporary, *Value); !Truncated) + return std::unexpected(Truncated.error()); + auto Store = MIRBuilder.buildInstr(opcode(GenericOpcode::G_STORE)); + if (!Store) + return std::unexpected(Store.error()); + if (auto Added = Store->addUse(*Temporary); !Added) + return std::unexpected(Added.error()); + if (auto Added = Store->addUse(*Address); !Added) + return std::unexpected(Added.error()); + if (auto Added = Store->addMemOperand(Memory); !Added) + return std::unexpected(Added.error()); + if (auto Built = Store->build(); !Built) + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); + } + + return reduceLoadStoreWidth(Instruction, TypeIndex, NarrowType); +} + +Expected<void> LegalizerHelper::reduceLoadStoreWidth(MachineInstr &Instruction, + std::size_t TypeIndex, + LowLevelType NarrowType) { + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (Generic != GenericOpcode::G_LOAD && Generic != GenericOpcode::G_STORE) { + return makeUnexpected(ErrorCode::InvalidArgument, + "memory width reduction received an unsupported opcode"); + } + if (TypeIndex != 0 || !NarrowType.isCategoryNeutralScalar() || + NarrowType.minimumBitWidth() == 0 || NarrowType.minimumBitWidth() % 8U != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "memory width reduction requires a byte-sized scalar target type"); + } + if (Instruction.operandCount() != 2 || Instruction.memOperands().size() != 1 || + Instruction.memOperands().front() == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "memory width reduction requires one address and one memory operand"); + } + + const bool IsLoad = Generic == GenericOpcode::G_LOAD; + const MachineOperand *ValueOperand = Instruction.operandAt(0); + const MachineOperand *AddressOperand = Instruction.operandAt(1); + const Register *Value = ValueOperand == nullptr ? nullptr : ValueOperand->getIf<Register>(); + const Register *Address = AddressOperand == nullptr ? nullptr : AddressOperand->getIf<Register>(); + if (Value == nullptr || Address == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "memory width reduction requires register value and address operands"); + } + auto ValueType = MRI.getType(*Value); + if (!ValueType) + return std::unexpected(ValueType.error()); + if (!ValueType->isCategoryNeutralScalar()) { + return makeUnexpected(ErrorCode::Unsupported, + "memory width reduction does not support pointer or vector values"); + } + + const MachineMemOperand &Memory = *Instruction.memOperands().front(); + const MachineMemOperandDesc &Description = Memory.description(); + if (Memory.isAtomic()) { + return makeUnexpected(ErrorCode::Unsupported, + "memory width reduction does not support atomic operations"); + } + if (!Description.Size.isKnown() || Description.Size.isScalable() || !Description.MemoryType || + Description.MemoryType->isVector()) { + return makeUnexpected(ErrorCode::Unsupported, + "memory width reduction requires a fixed scalar memory operand"); + } + const std::uint64_t ValueBits = ValueType->minimumBitWidth(); + if (ValueBits != Description.Size.minimumBytes() * 8U || + NarrowType.minimumBitWidth() >= ValueBits) { + return makeUnexpected( + ErrorCode::Unsupported, + "memory width reduction cannot split the requested value and memory widths"); + } + + const std::size_t PartCount = static_cast<std::size_t>(ValueBits / NarrowType.minimumBitWidth()); + const std::uint64_t LeftoverBits = ValueBits - PartCount * NarrowType.minimumBitWidth(); + LowLevelType ExpectedLeftoverType; + if (LeftoverBits != 0) { + auto Type = LowLevelType::makeCategoryNeutralScalar(LeftoverBits); + if (!Type) + return std::unexpected(Type.error()); + if (LeftoverBits % 8U != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "memory width reduction leaves a non-byte-sized scalar fragment"); + } + ExpectedLeftoverType = *Type; + } + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + std::vector<Register> Parts; + std::vector<Register> Leftovers; + LowLevelType LeftoverType; + if (IsLoad) { + Parts.reserve(PartCount); + for (std::size_t Index = 0; Index != PartCount; ++Index) { + auto Part = MRI.createVirtualRegister(NarrowType); + if (!Part) + return std::unexpected(Part.error()); + Parts.push_back(*Part); + } + LeftoverType = ExpectedLeftoverType; + if (ExpectedLeftoverType.isValid()) { + auto Leftover = MRI.createVirtualRegister(ExpectedLeftoverType); + if (!Leftover) + return std::unexpected(Leftover.error()); + Leftovers.push_back(*Leftover); + } + } else { + auto Extracted = extractParts(*Value, *ValueType, NarrowType, LeftoverType, Parts, Leftovers, + MIRBuilder, MRI); + if (!Extracted) + return std::unexpected(Extracted.error()); + if (!*Extracted || Parts.size() != PartCount || LeftoverType != ExpectedLeftoverType || + (ExpectedLeftoverType.isValid() && Leftovers.size() != 1U)) { + return makeUnexpected(ErrorCode::Unsupported, + "memory width reduction cannot split the store value consistently"); + } + } + + const auto SplitPieces = [&](LowLevelType PartType, const std::vector<Register> &Values, + std::uint64_t &Offset) -> Expected<void> { + const std::uint64_t PartBytes = PartType.minimumBitWidth() / 8U; + for (Register Part : Values) { + auto AddressPart = buildObjectPointerOffset(MIRBuilder, *Address, Offset); + if (!AddressPart) + return std::unexpected(AddressPart.error()); + auto MemoryPart = deriveMemoryOperand(Function, Memory, PartType, Offset); + if (!MemoryPart) + return std::unexpected(MemoryPart.error()); + + auto Access = + MIRBuilder.buildInstr(opcode(IsLoad ? GenericOpcode::G_LOAD : GenericOpcode::G_STORE)); + if (!Access) + return std::unexpected(Access.error()); + if (IsLoad) { + if (auto Added = Access->addDef(Part); !Added) + return std::unexpected(Added.error()); + } else if (auto Added = Access->addUse(Part); !Added) { + return std::unexpected(Added.error()); + } + if (auto Added = Access->addUse(*AddressPart); !Added) + return std::unexpected(Added.error()); + if (auto Added = Access->addMemOperand(**MemoryPart); !Added) + return std::unexpected(Added.error()); + if (auto Built = Access->build(); !Built) + return std::unexpected(Built.error()); + Offset += PartBytes; + } + return {}; + }; + + std::uint64_t Offset = 0; + if (auto Split = SplitPieces(NarrowType, Parts, Offset); !Split) + return std::unexpected(Split.error()); + if (LeftoverType.isValid()) { + if (auto Split = SplitPieces(LeftoverType, Leftovers, Offset); !Split) + return std::unexpected(Split.error()); + } + + if (IsLoad) { + if (auto Inserted = insertParts(*Value, *ValueType, NarrowType, Parts, LeftoverType, Leftovers); + !Inserted) { + return std::unexpected(Inserted.error()); + } + } + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::narrowScalar(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType NarrowType) { + switch (static_cast<GenericOpcode>(Instruction.opcode().value())) { + case GenericOpcode::G_IMPLICIT_DEF: { + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_IMPLICIT_DEF narrowing requires a register destination"); + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + + const std::uint64_t DestinationSize = DestinationType->minimumBitWidth(); + const std::uint64_t NarrowSize = NarrowType.minimumBitWidth(); + if (NarrowSize == 0 || NarrowSize >= DestinationSize) + return makeUnexpected(ErrorCode::VerificationError, + "G_IMPLICIT_DEF narrowing requires a smaller nonzero type"); + + if (DestinationSize % NarrowSize != 0) { + LowLevelType ImplicitType = NarrowType; + if (DestinationType->isVector()) { + auto VectorType = + DestinationType->isScalableVector() + ? LowLevelType::makeScalableVector(DestinationType->minimumElementCount(), + NarrowType) + : LowLevelType::makeFixedVector(DestinationType->minimumElementCount(), NarrowType); + if (!VectorType) + return std::unexpected(VectorType.error()); + ImplicitType = *VectorType; + } + auto Implicit = MIRBuilder.buildUndef(ImplicitType); + if (!Implicit) + return std::unexpected(Implicit.error()); + auto ImplicitReg = resultRegister(**Implicit); + if (!ImplicitReg) + return std::unexpected(ImplicitReg.error()); + if (auto Extended = MIRBuilder.buildAnyExt(*Destination, *ImplicitReg); !Extended) + return std::unexpected(Extended.error()); + } else { + const std::size_t PartCount = static_cast<std::size_t>(DestinationSize / NarrowSize); + std::vector<Register> Parts; + Parts.reserve(PartCount); + for (std::size_t Index = 0; Index != PartCount; ++Index) { + auto Part = MIRBuilder.buildUndef(NarrowType); + if (!Part) + return std::unexpected(Part.error()); + auto PartReg = resultRegister(**Part); + if (!PartReg) + return std::unexpected(PartReg.error()); + Parts.push_back(*PartReg); + } + if (auto Merged = MIRBuilder.buildMergeLikeInstr(*Destination, Parts); !Merged) + return std::unexpected(Merged.error()); + } + + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_CONSTANT: { + const Register *Destination = Instruction.operandAt(0)->getIf<Register>(); + const auto *Immediate = Instruction.operandAt(1)->getIf<IntegerImmediateOperand>(); + if (Destination == nullptr || Immediate == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_CONSTANT narrowing requires register and integer operands"); + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + + const std::uint64_t TotalSize = DestinationType->minimumBitWidth(); + const std::uint64_t NarrowSize = NarrowType.minimumBitWidth(); + if (NarrowSize == 0 || NarrowSize >= TotalSize || NarrowSize > APInt::MaxBitWidth || + TotalSize > APInt::MaxBitWidth) { + return makeUnexpected(ErrorCode::VerificationError, + "G_CONSTANT narrowing requires a smaller representable type"); + } + + const std::size_t PartCount = static_cast<std::size_t>(TotalSize / NarrowSize); + std::vector<Register> Parts; + Parts.reserve(PartCount); + for (std::size_t Index = 0; Index != PartCount; ++Index) { + auto Shifted = Immediate->Value.logicalShiftRight(Index * NarrowSize); + if (!Shifted) + return std::unexpected(Shifted.error()); + APInt PartValue = std::move(*Shifted); + if (PartValue.bitWidth() > NarrowSize) { + auto Truncated = PartValue.trunc(static_cast<std::uint32_t>(NarrowSize)); + if (!Truncated) + return std::unexpected(Truncated.error()); + PartValue = std::move(*Truncated); + } + auto Part = buildConstantRegister(MIRBuilder, NarrowType, std::move(PartValue)); + if (!Part) + return std::unexpected(Part.error()); + Parts.push_back(*Part); + } + + LowLevelType LeftoverType; + std::vector<Register> Leftovers; + const std::uint64_t LeftoverBits = TotalSize - PartCount * NarrowSize; + if (LeftoverBits != 0) { + auto Type = LowLevelType::makeCategoryNeutralScalar(LeftoverBits); + if (!Type) + return std::unexpected(Type.error()); + LeftoverType = *Type; + auto Shifted = Immediate->Value.logicalShiftRight(PartCount * NarrowSize); + if (!Shifted) + return std::unexpected(Shifted.error()); + APInt LeftoverValue = std::move(*Shifted); + if (LeftoverValue.bitWidth() > LeftoverBits) { + auto Truncated = LeftoverValue.trunc(static_cast<std::uint32_t>(LeftoverBits)); + if (!Truncated) + return std::unexpected(Truncated.error()); + LeftoverValue = std::move(*Truncated); + } + auto Leftover = buildConstantRegister(MIRBuilder, LeftoverType, std::move(LeftoverValue)); + if (!Leftover) + return std::unexpected(Leftover.error()); + Leftovers.push_back(*Leftover); + } + + if (auto Inserted = + insertParts(*Destination, *DestinationType, NarrowType, Parts, LeftoverType, Leftovers); + !Inserted) { + return std::unexpected(Inserted.error()); + } + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_ADD: + case GenericOpcode::G_SUB: + case GenericOpcode::G_SADDO: + case GenericOpcode::G_SSUBO: + case GenericOpcode::G_SADDE: + case GenericOpcode::G_SSUBE: + case GenericOpcode::G_UADDO: + case GenericOpcode::G_USUBO: + case GenericOpcode::G_UADDE: + case GenericOpcode::G_USUBE: + return narrowScalarAddSub(Instruction, TypeIndex, NarrowType); + case GenericOpcode::G_MUL: + case GenericOpcode::G_UMULH: + return narrowScalarMul(Instruction, TypeIndex, NarrowType); + case GenericOpcode::G_LOAD: + case GenericOpcode::G_STORE: + return narrowScalarLoadStore(Instruction, TypeIndex, NarrowType); + case GenericOpcode::G_SEXT: + case GenericOpcode::G_ZEXT: + case GenericOpcode::G_ANYEXT: + return narrowScalarExt(Instruction, TypeIndex, NarrowType); + case GenericOpcode::G_INTTOPTR: + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::Unsupported, + "G_INTTOPTR destination narrowing is not defined"); + Observer.changingInstr(Instruction); + if (auto Narrowed = narrowScalarSrc(Instruction, NarrowType, 1); !Narrowed) + return std::unexpected(Narrowed.error()); + Observer.changedInstr(Instruction); + return {}; + case GenericOpcode::G_PTRTOINT: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::Unsupported, "G_PTRTOINT source narrowing is not defined"); + Observer.changingInstr(Instruction); + if (auto Narrowed = narrowScalarDst(Instruction, NarrowType, 0, GenericOpcode::G_ZEXT); + !Narrowed) { + return std::unexpected(Narrowed.error()); + } + Observer.changedInstr(Instruction); + return {}; + case GenericOpcode::G_PHI: { + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::Unsupported, "G_PHI narrowing requires type index zero"); + if (Instruction.operandCount() < 3 || Instruction.operandCount() % 2U == 0) + return makeUnexpected(ErrorCode::VerificationError, + "G_PHI narrowing requires incoming value/block pairs"); + MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_PHI narrowing requires a register destination"); + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + const std::uint64_t DestinationSize = DestinationType->minimumBitWidth(); + const std::uint64_t NarrowSize = NarrowType.minimumBitWidth(); + if (NarrowSize == 0 || NarrowSize >= DestinationSize || DestinationSize % NarrowSize != 0) + return makeUnexpected(ErrorCode::Unsupported, + "G_PHI narrowing requires an exact scalar split"); + + const std::size_t PartCount = static_cast<std::size_t>(DestinationSize / NarrowSize); + const std::size_t IncomingCount = (Instruction.operandCount() - 1U) / 2U; + std::vector<std::vector<Register>> IncomingParts(IncomingCount); + std::vector<MachineBasicBlock *> IncomingBlocks(IncomingCount); + Observer.changingInstr(Instruction); + for (std::size_t Index = 1; Index < Instruction.operandCount(); Index += 2) { + const MachineOperand *ValueOperand = Instruction.operandAt(Index); + const MachineOperand *BlockOperand = Instruction.operandAt(Index + 1U); + const Register *Value = ValueOperand == nullptr ? nullptr : ValueOperand->getIf<Register>(); + const auto *Incoming = + BlockOperand == nullptr ? nullptr : BlockOperand->getIf<BasicBlockOperand>(); + if (Value == nullptr || Incoming == nullptr || Incoming->Block == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_PHI narrowing has an invalid incoming pair"); + if (auto Point = setInsertPointAtFirstTerminator(MIRBuilder, *Incoming->Block); !Point) + return std::unexpected(Point.error()); + const std::size_t IncomingIndex = Index / 2U; + IncomingBlocks[IncomingIndex] = Incoming->Block; + auto &Parts = IncomingParts[IncomingIndex]; + Parts.reserve(PartCount); + for (std::size_t PartIndex = 0; PartIndex != PartCount; ++PartIndex) { + auto Part = MRI.createVirtualRegister(NarrowType); + if (!Part) + return std::unexpected(Part.error()); + Parts.push_back(*Part); + } + if (auto Unmerged = MIRBuilder.buildUnmergeValues(Parts, *Value); !Unmerged) + return std::unexpected(Unmerged.error()); + } + + MachineBasicBlock &Block = *Instruction.parent(); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + std::vector<Register> Destinations; + Destinations.reserve(PartCount); + for (std::size_t PartIndex = 0; PartIndex != PartCount; ++PartIndex) { + auto DestinationPart = MRI.createVirtualRegister(NarrowType); + if (!DestinationPart) + return std::unexpected(DestinationPart.error()); + auto Phi = MIRBuilder.buildInstr(opcode(GenericOpcode::G_PHI)); + if (!Phi) + return std::unexpected(Phi.error()); + if (auto Added = Phi->addDef(*DestinationPart); !Added) + return std::unexpected(Added.error()); + for (std::size_t IncomingIndex = 0; IncomingIndex != IncomingCount; ++IncomingIndex) { + if (auto Added = Phi->addUse(IncomingParts[IncomingIndex][PartIndex]); !Added) + return std::unexpected(Added.error()); + if (auto Added = Phi->addBasicBlock(*IncomingBlocks[IncomingIndex]); !Added) + return std::unexpected(Added.error()); + } + if (auto Built = Phi->build(); !Built) + return std::unexpected(Built.error()); + Destinations.push_back(*DestinationPart); + } + + if (auto Point = setInsertPointAfterPHIs(MIRBuilder, Block); !Point) + return std::unexpected(Point.error()); + if (auto Merged = MIRBuilder.buildMergeLikeInstr(*Destination, Destinations); !Merged) + return std::unexpected(Merged.error()); + Observer.changedInstr(Instruction); + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_FCMP: + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::Unsupported, + "floating comparison source narrowing is not defined"); + Observer.changingInstr(Instruction); + if (auto Narrowed = narrowScalarDst(Instruction, NarrowType, 0, GenericOpcode::G_ZEXT); + !Narrowed) { + return std::unexpected(Narrowed.error()); + } + Observer.changedInstr(Instruction); + return {}; + case GenericOpcode::G_SEXT_INREG: { + if (TypeIndex != 0) + return makeUnexpected(ErrorCode::Unsupported, + "G_SEXT_INREG narrowing requires type index zero"); + + MachineOperand *DestinationOperand = Instruction.operandAt(0); + MachineOperand *SourceOperand = Instruction.operandAt(1); + MachineOperand *WidthOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Destination == nullptr || Source == nullptr || WidthOperand == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_SEXT_INREG narrowing requires register and width operands"); + auto SizeInBits = signedImmediate(*WidthOperand); + if (!SizeInBits) + return std::unexpected(SizeInBits.error()); + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + + const std::uint64_t TotalSize = DestinationType->minimumBitWidth(); + const std::uint64_t NarrowSize = NarrowType.minimumBitWidth(); + if (*SizeInBits <= 0 || static_cast<std::uint64_t>(*SizeInBits) > TotalSize || + NarrowSize == 0 || NarrowSize >= TotalSize) { + return makeUnexpected(ErrorCode::VerificationError, + "G_SEXT_INREG narrowing has invalid scalar widths"); + } + + if (NarrowSize > static_cast<std::uint64_t>(*SizeInBits)) { + const Register OriginalDestination = *Destination; + Observer.changingInstr(Instruction); + auto NarrowedSource = narrowScalarSrc(Instruction, NarrowType, 1); + if (!NarrowedSource) + return std::unexpected(NarrowedSource.error()); + auto NarrowDestination = MRI.createVirtualRegister(NarrowType); + if (!NarrowDestination) + return std::unexpected(NarrowDestination.error()); + if (auto Changed = DestinationOperand->setRegister(*NarrowDestination); !Changed) + return std::unexpected(Changed.error()); + if (auto Point = MIRBuilder.setInsertPointAfter(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Extended = MIRBuilder.buildSExt(OriginalDestination, *NarrowDestination); !Extended) + return std::unexpected(Extended.error()); + Observer.changedInstr(Instruction); + return {}; + } + + if (TotalSize % NarrowSize != 0) + return makeUnexpected(ErrorCode::Unsupported, + "G_SEXT_INREG cannot be split evenly to the requested type"); + const std::size_t PartCount = static_cast<std::size_t>(TotalSize / NarrowSize); + std::vector<Register> SourceParts; + SourceParts.reserve(PartCount); + for (std::size_t Index = 0; Index != PartCount; ++Index) { + auto Part = MRI.createVirtualRegister(NarrowType); + if (!Part) + return std::unexpected(Part.error()); + SourceParts.push_back(*Part); + } + if (auto Unmerged = MIRBuilder.buildUnmergeValues(SourceParts, *Source); !Unmerged) + return std::unexpected(Unmerged.error()); + + auto ShiftAmount = + buildConstantRegister(MIRBuilder, NarrowType, static_cast<std::int64_t>(NarrowSize - 1U)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + + std::vector<Register> DestinationParts; + DestinationParts.reserve(PartCount); + Register FullExtension; + Register PartialExtension; + const std::uint64_t ExtensionBits = static_cast<std::uint64_t>(*SizeInBits); + for (std::size_t Index = 0; Index != PartCount; ++Index) { + if ((Index + 1U) * NarrowSize <= ExtensionBits) { + DestinationParts.push_back(SourceParts[Index]); + PartialExtension = SourceParts[Index]; + continue; + } + if (Index * NarrowSize >= ExtensionBits) { + if (PartialExtension.isNoRegister()) + return makeUnexpected(ErrorCode::InternalError, + "G_SEXT_INREG split has no sign-bearing part"); + if (FullExtension.isNoRegister()) { + auto Extended = buildResultInstruction(MIRBuilder, GenericOpcode::G_ASHR, NarrowType, + {PartialExtension, *ShiftAmount}); + if (!Extended) + return std::unexpected(Extended.error()); + FullExtension = *Extended; + } + DestinationParts.push_back(FullExtension); + continue; + } + + auto Extended = MIRBuilder.buildSExtInReg( + NarrowType, SourceParts[Index], static_cast<std::uint32_t>(ExtensionBits % NarrowSize)); + if (!Extended) + return std::unexpected(Extended.error()); + auto ExtendedRegister = resultRegister(**Extended); + if (!ExtendedRegister) + return std::unexpected(ExtendedRegister.error()); + DestinationParts.push_back(*ExtendedRegister); + PartialExtension = *ExtendedRegister; + } + + if (auto Merged = MIRBuilder.buildMergeLikeInstr(*Destination, DestinationParts); !Merged) + return std::unexpected(Merged.error()); + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_AND: + case GenericOpcode::G_OR: + case GenericOpcode::G_XOR: + return narrowScalarBasic(Instruction, TypeIndex, NarrowType); + case GenericOpcode::G_SELECT: + return narrowScalarSelect(Instruction, TypeIndex, NarrowType); + case GenericOpcode::G_SHL: + case GenericOpcode::G_LSHR: + case GenericOpcode::G_ASHR: + return narrowScalarShift(Instruction, TypeIndex, NarrowType); + case GenericOpcode::G_ICMP: { + if (TypeIndex != 1) + return makeUnexpected(ErrorCode::Unsupported, + "G_ICMP narrowing requires the source type index"); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *PredicateMachineOperand = Instruction.operandAt(1); + const MachineOperand *LeftOperand = Instruction.operandAt(2); + const MachineOperand *RightOperand = Instruction.operandAt(3); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const auto *PredicateValue = PredicateMachineOperand == nullptr + ? nullptr + : PredicateMachineOperand->getIf<PredicateOperand>(); + const Register *Left = LeftOperand == nullptr ? nullptr : LeftOperand->getIf<Register>(); + const Register *Right = RightOperand == nullptr ? nullptr : RightOperand->getIf<Register>(); + const IntegerPredicate *Predicate = + PredicateValue == nullptr ? nullptr : PredicateValue->integerPredicate(); + if (Destination == nullptr || Left == nullptr || Right == nullptr || Predicate == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "G_ICMP narrowing requires an integer predicate and register operands"); + } + auto SourceType = MRI.getType(*Left); + auto ResultType = MRI.getType(*Destination); + if (!SourceType) + return std::unexpected(SourceType.error()); + if (!ResultType) + return std::unexpected(ResultType.error()); + + LowLevelType LeftoverType; + LowLevelType UnusedType; + std::vector<Register> LeftParts; + std::vector<Register> LeftLeftovers; + std::vector<Register> RightParts; + std::vector<Register> RightLeftovers; + auto LeftExtracted = extractParts(*Left, *SourceType, NarrowType, LeftoverType, LeftParts, + LeftLeftovers, MIRBuilder, MRI); + if (!LeftExtracted) + return std::unexpected(LeftExtracted.error()); + auto RightExtracted = extractParts(*Right, *SourceType, NarrowType, UnusedType, RightParts, + RightLeftovers, MIRBuilder, MRI); + if (!RightExtracted) + return std::unexpected(RightExtracted.error()); + if (!*LeftExtracted || !*RightExtracted || LeftParts.size() != RightParts.size() || + LeftLeftovers.size() != RightLeftovers.size()) { + return makeUnexpected(ErrorCode::Unsupported, "G_ICMP operands cannot be split consistently"); + } + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (isEqualityPredicate(*Predicate)) { + auto Zero = buildConstantRegister(MIRBuilder, NarrowType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + + std::vector<Register> Xors; + Xors.reserve(LeftParts.size() + LeftLeftovers.size()); + for (std::size_t Index = 0; Index != LeftParts.size(); ++Index) { + auto Xor = buildResultInstruction(MIRBuilder, GenericOpcode::G_XOR, NarrowType, + {LeftParts[Index], RightParts[Index]}); + if (!Xor) + return std::unexpected(Xor.error()); + Xors.push_back(*Xor); + } + + std::vector<Register> WidenedXors; + for (std::size_t Index = 0; Index != LeftLeftovers.size(); ++Index) { + auto Xor = buildResultInstruction(MIRBuilder, GenericOpcode::G_XOR, LeftoverType, + {LeftLeftovers[Index], RightLeftovers[Index]}); + if (!Xor) + return std::unexpected(Xor.error()); + auto GCDType = extractGCDType(WidenedXors, NarrowType, LeftoverType, *Xor); + if (!GCDType) + return std::unexpected(GCDType.error()); + auto LCMType = buildLCMMergePieces(LeftoverType, NarrowType, *GCDType, WidenedXors, + GenericOpcode::G_ZEXT); + if (!LCMType) + return std::unexpected(LCMType.error()); + Xors.insert(Xors.end(), WidenedXors.begin(), WidenedXors.end()); + } + + if (Xors.size() < 2) { + return makeUnexpected(ErrorCode::InternalError, + "G_ICMP narrowing produced fewer than two comparison parts"); + } + auto Combined = + buildResultInstruction(MIRBuilder, GenericOpcode::G_OR, NarrowType, {Xors[0], Xors[1]}); + if (!Combined) + return std::unexpected(Combined.error()); + for (std::size_t Index = 2; Index != Xors.size(); ++Index) { + auto Next = buildResultInstruction(MIRBuilder, GenericOpcode::G_OR, NarrowType, + {*Combined, Xors[Index]}); + if (!Next) + return std::unexpected(Next.error()); + Combined = std::move(Next); + } + if (auto Built = MIRBuilder.buildICmp(*Predicate, *Destination, *Combined, *Zero); !Built) + return std::unexpected(Built.error()); + } else { + Register ComparisonInput; + const auto buildComparisonPart = [&](Register LeftPart, Register RightPart, + bool IsFinal) -> Expected<Register> { + IntegerPredicate PartPredicate = *Predicate; + if (!IsFinal) { + auto Unsigned = unsignedPredicate(*Predicate); + if (!Unsigned) + return std::unexpected(Unsigned.error()); + PartPredicate = *Unsigned; + } + Register ComparisonOutput = *Destination; + if (!IsFinal) { + auto Temporary = MRI.createVirtualRegister(*ResultType); + if (!Temporary) + return std::unexpected(Temporary.error()); + ComparisonOutput = *Temporary; + } + if (ComparisonInput.isNoRegister()) { + auto Built = MIRBuilder.buildICmp(PartPredicate, ComparisonOutput, LeftPart, RightPart); + if (!Built) + return std::unexpected(Built.error()); + } else { + auto Comparison = MRI.createVirtualRegister(*ResultType); + auto Equal = MRI.createVirtualRegister(*ResultType); + if (!Comparison) + return std::unexpected(Comparison.error()); + if (!Equal) + return std::unexpected(Equal.error()); + if (auto Built = MIRBuilder.buildICmp(PartPredicate, *Comparison, LeftPart, RightPart); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::Equal, *Equal, LeftPart, RightPart); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = + MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {ComparisonOutput}, + {*Equal, ComparisonInput, *Comparison}); + !Built) { + return std::unexpected(Built.error()); + } + } + return ComparisonOutput; + }; + + for (std::size_t Index = 0; Index != LeftParts.size(); ++Index) { + const bool IsFinal = Index + 1U == LeftParts.size() && LeftLeftovers.empty(); + auto Comparison = buildComparisonPart(LeftParts[Index], RightParts[Index], IsFinal); + if (!Comparison) + return std::unexpected(Comparison.error()); + ComparisonInput = *Comparison; + } + for (std::size_t Index = 0; Index != LeftLeftovers.size(); ++Index) { + const bool IsFinal = Index + 1U == LeftLeftovers.size(); + auto Comparison = buildComparisonPart(LeftLeftovers[Index], RightLeftovers[Index], IsFinal); + if (!Comparison) + return std::unexpected(Comparison.error()); + ComparisonInput = *Comparison; + } + } + + return Instruction.eraseFromParent(); + } + default: + return makeUnexpected(ErrorCode::Unsupported, + "scalar narrowing is not implemented for this opcode"); + } +} + +Expected<void> LegalizerHelper::fewerElements(MachineInstr &Instruction, std::size_t TypeIndex, + LowLevelType NarrowType) { + auto Query = LegalizerInfo::buildQuery(Instruction, Function, MIRBuilder.target()); + if (!Query) + return std::unexpected(Query.error()); + if (TypeIndex >= Query->Types.size() || TypeIndex >= Query->TypeOperands.size()) { + return makeUnexpected(ErrorCode::VerificationError, + "vector legalization has an invalid type index"); + } + + const LowLevelType VectorType = Query->Types[TypeIndex]; + const std::optional<LowLevelType> ElementType = VectorType.scalarElementType(); + if (!VectorType.isVector() || VectorType.isScalableVector() || !ElementType || + *ElementType != NarrowType || !NarrowType.isCategoryNeutralScalar()) { + return makeUnexpected(ErrorCode::Unsupported, + "vector legalization requires fixed vectors split into scalar elements"); + } + + std::size_t ExplicitOperandCount = 0; + while (ExplicitOperandCount < Instruction.operandCount() && + !Instruction.operandAt(ExplicitOperandCount)->isImplicit()) { + ++ExplicitOperandCount; + } + const std::vector<std::size_t> &TypedOperands = Query->TypeOperands[TypeIndex]; + if (TypedOperands.size() != ExplicitOperandCount || TypedOperands.empty()) { + return makeUnexpected( + ErrorCode::Unsupported, + "vector legalization requires all explicit operands to share one vector type"); + } + + std::vector<std::size_t> DefinitionIndices; + std::vector<std::size_t> SourceIndices; + DefinitionIndices.reserve(TypedOperands.size()); + SourceIndices.reserve(TypedOperands.size()); + for (const std::size_t OperandIndex : TypedOperands) { + const MachineOperand *Operand = Instruction.operandAt(OperandIndex); + const Register *RegisterOperand = Operand == nullptr ? nullptr : Operand->getIf<Register>(); + if (RegisterOperand == nullptr || !RegisterOperand->isVirtual()) { + return makeUnexpected(ErrorCode::VerificationError, + "vector legalization requires virtual-register operands"); + } + auto OperandType = MRI.getType(*RegisterOperand); + if (!OperandType) + return std::unexpected(OperandType.error()); + if (*OperandType != VectorType) { + return makeUnexpected( + ErrorCode::VerificationError, + "vector legalization type operands do not match the requested vector type"); + } + if (Operand->isDef() && !Operand->isUse()) { + DefinitionIndices.push_back(OperandIndex); + } else if (Operand->isUse() && !Operand->isDef()) { + SourceIndices.push_back(OperandIndex); + } else { + return makeUnexpected(ErrorCode::Unsupported, + "vector legalization does not support tied or non-register operands"); + } + } + if (DefinitionIndices.empty() || SourceIndices.empty()) { + return makeUnexpected(ErrorCode::VerificationError, + "vector legalization requires register definitions and sources"); + } + + const std::size_t ElementCount = VectorType.minimumElementCount(); + std::vector<std::vector<Register>> SourceParts; + SourceParts.reserve(SourceIndices.size()); + for (const std::size_t SourceIndex : SourceIndices) { + const Register Source = *Instruction.operandAt(SourceIndex)->getIf<Register>(); + std::vector<Register> Parts; + auto Extracted = + extractParts(Source, NarrowType, static_cast<int>(ElementCount), Parts, MIRBuilder, MRI); + if (!Extracted) + return std::unexpected(Extracted.error()); + SourceParts.push_back(std::move(Parts)); + } + + std::vector<std::vector<Register>> DestinationParts(DefinitionIndices.size()); + for (std::vector<Register> &Parts : DestinationParts) + Parts.reserve(ElementCount); + for (std::size_t ElementIndex = 0; ElementIndex < ElementCount; ++ElementIndex) { + auto ScalarInstruction = MIRBuilder.buildInstr(Instruction.opcode()); + if (!ScalarInstruction) + return std::unexpected(ScalarInstruction.error()); + if (auto SetFlags = ScalarInstruction->setFlags(Instruction.flags()); !SetFlags) + return std::unexpected(SetFlags.error()); + for (std::size_t DefinitionNumber = 0; DefinitionNumber < DefinitionIndices.size(); + ++DefinitionNumber) { + auto Destination = MRI.createVirtualRegister(NarrowType); + if (!Destination) + return std::unexpected(Destination.error()); + if (auto Added = ScalarInstruction->addDef(*Destination); !Added) + return std::unexpected(Added.error()); + DestinationParts[DefinitionNumber].push_back(*Destination); + } + for (const std::vector<Register> &Parts : SourceParts) { + if (auto Added = ScalarInstruction->addUse(Parts[ElementIndex]); !Added) + return std::unexpected(Added.error()); + } + if (auto Built = ScalarInstruction->build(); !Built) + return std::unexpected(Built.error()); + } + + for (std::size_t DefinitionNumber = 0; DefinitionNumber < DefinitionIndices.size(); + ++DefinitionNumber) { + const Register Destination = + *Instruction.operandAt(DefinitionIndices[DefinitionNumber])->getIf<Register>(); + if (auto Merged = + MIRBuilder.buildMergeLikeInstr(Destination, DestinationParts[DefinitionNumber]); + !Merged) { + return std::unexpected(Merged.error()); + } + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerVAArg(MachineInstr &Instruction) { + if (Instruction.opcode() != opcode(GenericOpcode::G_VAARG) || Instruction.operandCount() != 3 || + !Instruction.memOperands().empty()) { + return makeUnexpected(ErrorCode::VerificationError, "G_VAARG has invalid operands"); + } + + MachineOperand *DestinationOperand = Instruction.operandAt(0); + MachineOperand *ListOperand = Instruction.operandAt(1); + MachineOperand *AlignmentOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *ListPointer = ListOperand == nullptr ? nullptr : ListOperand->getIf<Register>(); + if (Destination == nullptr || ListPointer == nullptr || !Destination->isVirtual() || + !ListPointer->isVirtual() || !DestinationOperand->isDef() || !ListOperand->isUse() || + AlignmentOperand == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "G_VAARG requires virtual-register operands"); + } + + auto DestinationInfo = MRI.virtualRegisterInfo(*Destination); + auto PointerInfo = MRI.virtualRegisterInfo(*ListPointer); + auto RequestedAlignment = signedImmediate(*AlignmentOperand); + if (!DestinationInfo || !PointerInfo) + return makeUnexpected(ErrorCode::VerificationError, "G_VAARG has invalid register information"); + const std::optional<LowLevelType> &DestinationTypeInfo = DestinationInfo->get().Type; + const std::optional<LowLevelType> &PointerTypeInfo = PointerInfo->get().Type; + if (!DestinationTypeInfo.has_value() || !PointerTypeInfo.has_value() || !RequestedAlignment || + *RequestedAlignment <= 0 || + !std::has_single_bit(static_cast<std::uint64_t>(*RequestedAlignment))) { + return makeUnexpected(ErrorCode::VerificationError, + "G_VAARG has invalid type or alignment information"); + } + + const LowLevelType DestinationType = DestinationTypeInfo.value(); + const LowLevelType PointerType = PointerTypeInfo.value(); + const auto DestinationLayout = scalarLayout(DestinationType); + const auto PointerLayout = scalarLayout(PointerType); + if (!DestinationLayout || !PointerLayout || !PointerType.isPointer()) { + return makeUnexpected(ErrorCode::Unsupported, + "G_VAARG requires scalar result and pointer list types"); + } + + auto ListLoadMemory = + createMemoryAccess(Function, MemoryAccessFlag::Load, PointerType, *PointerLayout); + auto ListStoreMemory = + createMemoryAccess(Function, MemoryAccessFlag::Store, PointerType, *PointerLayout); + if (!ListLoadMemory) + return std::unexpected(ListLoadMemory.error()); + if (!ListStoreMemory) + return std::unexpected(ListStoreMemory.error()); + + auto CurrentList = MRI.createVirtualRegister(PointerType); + if (!CurrentList) + return std::unexpected(CurrentList.error()); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto ListLoad = MIRBuilder.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(*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; + const std::uint64_t AlignmentValue = static_cast<std::uint64_t>(*RequestedAlignment); + auto PointerScalarType = LowLevelType::makeCategoryNeutralScalar(PointerType.scalarBitWidth()); + if (!PointerScalarType) + return std::unexpected(PointerScalarType.error()); + if (AlignmentValue > 1) { + auto AlignmentOffset = + buildConstantBefore(MIRBuilder, Instruction, *PointerScalarType, AlignmentValue - 1U); + if (!AlignmentOffset) + return std::unexpected(AlignmentOffset.error()); + auto BiasedAddress = buildBinaryBefore(MIRBuilder, Instruction, GenericOpcode::G_PTR_ADD, + PointerType, ValueAddress, *AlignmentOffset); + if (!BiasedAddress) + return std::unexpected(BiasedAddress.error()); + auto AlignmentMask = + buildConstantBefore(MIRBuilder, Instruction, *PointerScalarType, ~(AlignmentValue - 1U)); + if (!AlignmentMask) + return std::unexpected(AlignmentMask.error()); + auto AlignedAddress = buildBinaryBefore(MIRBuilder, Instruction, GenericOpcode::G_PTRMASK, + PointerType, *BiasedAddress, *AlignmentMask); + if (!AlignedAddress) + return std::unexpected(AlignedAddress.error()); + ValueAddress = *AlignedAddress; + } + + auto Increment = buildConstantBefore(MIRBuilder, Instruction, *PointerScalarType, + DestinationLayout->AllocationSize); + if (!Increment) + return std::unexpected(Increment.error()); + auto NextList = buildBinaryBefore(MIRBuilder, Instruction, GenericOpcode::G_PTR_ADD, PointerType, + ValueAddress, *Increment); + if (!NextList) + return std::unexpected(NextList.error()); + + auto ListStore = MIRBuilder.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(*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 (!MRI.useEmpty(*Destination)) { + auto ValueLoadMemory = + createMemoryAccess(Function, MemoryAccessFlag::Load, DestinationType, *DestinationLayout); + if (!ValueLoadMemory) + return std::unexpected(ValueLoadMemory.error()); + auto ValueLoad = MIRBuilder.buildInstr(opcode(GenericOpcode::G_LOAD)); + if (!ValueLoad) + return std::unexpected(ValueLoad.error()); + if (auto Added = ValueLoad->addDef(*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 Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerLoad(MachineInstr &Instruction) { + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (Generic != GenericOpcode::G_LOAD && Generic != GenericOpcode::G_SEXTLOAD && + Generic != GenericOpcode::G_ZEXTLOAD) { + return makeUnexpected(ErrorCode::InvalidArgument, + "load lowering received an unsupported opcode"); + } + if (Instruction.operandCount() != 2 || Instruction.memOperands().size() != 1 || + Instruction.memOperands().front() == nullptr) { + return makeUnexpected( + ErrorCode::VerificationError, + "load lowering requires one destination, one address, and one memory operand"); + } + + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *AddressOperand = Instruction.operandAt(1); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Address = AddressOperand == nullptr ? nullptr : AddressOperand->getIf<Register>(); + if (Destination == nullptr || Address == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "load lowering requires register destination and address operands"); + } + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (!DestinationType->isScalarValue()) { + return makeUnexpected(ErrorCode::Unsupported, + "load lowering does not support vector result types"); + } + + const MachineMemOperand &Memory = *Instruction.memOperands().front(); + const MachineMemOperandDesc &Description = Memory.description(); + if (Memory.isAtomic()) + return makeUnexpected(ErrorCode::Unsupported, + "load lowering does not support atomic operations"); + if (!Description.Size.isKnown() || Description.Size.isScalable() || !Description.MemoryType || + Description.MemoryType->isVector()) { + return makeUnexpected(ErrorCode::Unsupported, + "load lowering requires a fixed scalar memory operand"); + } + const LowLevelType MemoryType = *Description.MemoryType; + const std::uint64_t MemoryBits = MemoryType.minimumBitWidth(); + const std::uint64_t MemoryStoreBits = Description.Size.minimumBytes() * 8U; + if (MemoryBits == 0 || MemoryBits > MemoryStoreBits) { + return makeUnexpected(ErrorCode::VerificationError, + "load lowering has an inconsistent memory type and size"); + } + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + const auto BuildLoad = [&](GenericOpcode LoadOpcode, Register Result, Register Pointer, + const MachineMemOperand &LoadMemory) -> Expected<void> { + auto Load = MIRBuilder.buildInstr(opcode(LoadOpcode)); + if (!Load) + return std::unexpected(Load.error()); + if (auto Added = Load->addDef(Result); !Added) + return std::unexpected(Added.error()); + if (auto Added = Load->addUse(Pointer); !Added) + return std::unexpected(Added.error()); + if (auto Added = Load->addMemOperand(LoadMemory); !Added) + return std::unexpected(Added.error()); + if (auto Built = Load->build(); !Built) + return std::unexpected(Built.error()); + return {}; + }; + + if (MemoryBits != MemoryStoreBits) { + if (!MemoryType.isCategoryNeutralScalar()) { + return makeUnexpected(ErrorCode::Unsupported, + "non-byte-sized pointer loads are not supported"); + } + auto WideMemoryType = LowLevelType::makeCategoryNeutralScalar(MemoryStoreBits); + if (!WideMemoryType) + return std::unexpected(WideMemoryType.error()); + auto WideMemory = deriveMemoryOperand(Function, Memory, *WideMemoryType, 0); + if (!WideMemory) + return std::unexpected(WideMemory.error()); + + LowLevelType LoadType = *DestinationType; + Register LoadDestination = *Destination; + if (MemoryStoreBits > DestinationType->minimumBitWidth()) { + LoadType = *WideMemoryType; + auto Temporary = MRI.createVirtualRegister(LoadType); + if (!Temporary) + return std::unexpected(Temporary.error()); + LoadDestination = *Temporary; + } + + if (Generic == GenericOpcode::G_SEXTLOAD) { + auto Loaded = MRI.createVirtualRegister(LoadType); + if (!Loaded) + return std::unexpected(Loaded.error()); + if (auto Built = BuildLoad(GenericOpcode::G_LOAD, *Loaded, *Address, **WideMemory); !Built) + return std::unexpected(Built.error()); + if (auto Extended = MIRBuilder.buildSExtInReg(LoadDestination, *Loaded, + static_cast<std::uint32_t>(MemoryBits)); + !Extended) { + return std::unexpected(Extended.error()); + } + } else if (Generic == GenericOpcode::G_ZEXTLOAD || *WideMemoryType == LoadType) { + auto Loaded = MRI.createVirtualRegister(LoadType); + if (!Loaded) + return std::unexpected(Loaded.error()); + if (auto Built = BuildLoad(GenericOpcode::G_LOAD, *Loaded, *Address, **WideMemory); !Built) + return std::unexpected(Built.error()); + auto Assert = MIRBuilder.buildInstr(opcode(GenericOpcode::G_ASSERT_ZEXT)); + if (!Assert) + return std::unexpected(Assert.error()); + if (auto Added = Assert->addDef(LoadDestination); !Added) + return std::unexpected(Added.error()); + if (auto Added = Assert->addUse(*Loaded); !Added) + return std::unexpected(Added.error()); + if (auto Added = Assert->addImmediate(static_cast<std::int64_t>(MemoryBits)); !Added) + return std::unexpected(Added.error()); + if (auto Built = Assert->build(); !Built) + return std::unexpected(Built.error()); + } else if (auto Built = + BuildLoad(GenericOpcode::G_LOAD, LoadDestination, *Address, **WideMemory); + !Built) { + return std::unexpected(Built.error()); + } + + if (*DestinationType != LoadType) { + if (auto Truncated = MIRBuilder.buildTrunc(*Destination, LoadDestination); !Truncated) + return std::unexpected(Truncated.error()); + } + return Instruction.eraseFromParent(); + } + + std::uint64_t LargeSplitBits = 0; + std::uint64_t SmallSplitBits = 0; + if (!std::has_single_bit(MemoryBits)) { + LargeSplitBits = std::bit_floor(MemoryBits); + SmallSplitBits = MemoryBits - LargeSplitBits; + } else { + if (MemoryBits < 16U) { + return makeUnexpected(ErrorCode::Unsupported, + "load lowering cannot split a byte-sized memory operation"); + } + LargeSplitBits = MemoryBits / 2U; + SmallSplitBits = LargeSplitBits; + } + if (LargeSplitBits == 0 || SmallSplitBits == 0 || LargeSplitBits % 8U != 0 || + SmallSplitBits % 8U != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "load lowering cannot split the requested memory width into bytes"); + } + + const std::uint64_t AnyExtBits = std::bit_ceil(DestinationType->minimumBitWidth()); + if (AnyExtBits == 0 || AnyExtBits > LowLevelType::MaxBitWidth) { + return makeUnexpected(ErrorCode::Unsupported, + "load lowering result width cannot be represented by the machine IR"); + } + auto AnyExtType = LowLevelType::makeCategoryNeutralScalar(AnyExtBits); + auto LargeMemoryType = LowLevelType::makeCategoryNeutralScalar(LargeSplitBits); + auto SmallMemoryType = LowLevelType::makeCategoryNeutralScalar(SmallSplitBits); + if (!AnyExtType) + return std::unexpected(AnyExtType.error()); + if (!LargeMemoryType) + return std::unexpected(LargeMemoryType.error()); + if (!SmallMemoryType) + return std::unexpected(SmallMemoryType.error()); + auto LargeMemory = deriveMemoryOperand(Function, Memory, *LargeMemoryType, 0); + auto SmallMemory = deriveMemoryOperand(Function, Memory, *SmallMemoryType, LargeSplitBits / 8U); + if (!LargeMemory) + return std::unexpected(LargeMemory.error()); + if (!SmallMemory) + return std::unexpected(SmallMemory.error()); + + auto LargeLoad = MRI.createVirtualRegister(*AnyExtType); + if (!LargeLoad) + return std::unexpected(LargeLoad.error()); + if (auto Built = BuildLoad(GenericOpcode::G_ZEXTLOAD, *LargeLoad, *Address, **LargeMemory); + !Built) + return std::unexpected(Built.error()); + auto SmallAddress = buildObjectPointerOffset(MIRBuilder, *Address, LargeSplitBits / 8U); + if (!SmallAddress) + return std::unexpected(SmallAddress.error()); + auto SmallLoad = MRI.createVirtualRegister(*AnyExtType); + if (!SmallLoad) + return std::unexpected(SmallLoad.error()); + if (auto Built = BuildLoad(Generic, *SmallLoad, *SmallAddress, **SmallMemory); !Built) + return std::unexpected(Built.error()); + auto ShiftAmount = + buildConstantRegister(MIRBuilder, *AnyExtType, static_cast<std::int64_t>(LargeSplitBits)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto Shifted = buildResultInstruction(MIRBuilder, GenericOpcode::G_SHL, *AnyExtType, + {*SmallLoad, *ShiftAmount}); + if (!Shifted) + return std::unexpected(Shifted.error()); + + if (*AnyExtType == *DestinationType) { + if (auto Combined = MIRBuilder.buildOr(*Destination, *Shifted, *LargeLoad); !Combined) + return std::unexpected(Combined.error()); + } else { + auto Combined = buildResultInstruction(MIRBuilder, GenericOpcode::G_OR, *AnyExtType, + {*Shifted, *LargeLoad}); + if (!Combined) + return std::unexpected(Combined.error()); + if (AnyExtBits != DestinationType->minimumBitWidth()) { + if (auto Truncated = MIRBuilder.buildTrunc(*Destination, *Combined); !Truncated) + return std::unexpected(Truncated.error()); + } else if (DestinationType->isPointer()) { + if (auto Converted = MIRBuilder.buildIntToPtr(*Destination, *Combined); !Converted) + return std::unexpected(Converted.error()); + } else { + return makeUnexpected(ErrorCode::InternalError, + "load lowering produced an incompatible scalar result type"); + } + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerStore(MachineInstr &Instruction) { + if (Instruction.opcode() != opcode(GenericOpcode::G_STORE)) { + return makeUnexpected(ErrorCode::InvalidArgument, + "store lowering received an unsupported opcode"); + } + if (Instruction.operandCount() != 2 || Instruction.memOperands().size() != 1 || + Instruction.memOperands().front() == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "store lowering requires one value, one address, and one memory operand"); + } + + const MachineOperand *ValueOperand = Instruction.operandAt(0); + const MachineOperand *AddressOperand = Instruction.operandAt(1); + const Register *Value = ValueOperand == nullptr ? nullptr : ValueOperand->getIf<Register>(); + const Register *Address = AddressOperand == nullptr ? nullptr : AddressOperand->getIf<Register>(); + if (Value == nullptr || Address == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "store lowering requires register value and address operands"); + } + auto ValueType = MRI.getType(*Value); + if (!ValueType) + return std::unexpected(ValueType.error()); + if (!ValueType->isScalarValue()) { + return makeUnexpected(ErrorCode::Unsupported, + "store lowering does not support vector value types"); + } + + const MachineMemOperand &Memory = *Instruction.memOperands().front(); + const MachineMemOperandDesc &Description = Memory.description(); + if (Memory.isAtomic()) + return makeUnexpected(ErrorCode::Unsupported, + "store lowering does not support atomic operations"); + if (!Description.Size.isKnown() || Description.Size.isScalable() || !Description.MemoryType || + Description.MemoryType->isVector()) { + return makeUnexpected(ErrorCode::Unsupported, + "store lowering requires a fixed scalar memory operand"); + } + const LowLevelType MemoryType = *Description.MemoryType; + const std::uint64_t StoreBits = MemoryType.minimumBitWidth(); + const std::uint64_t StoreSizeBits = Description.Size.minimumBytes() * 8U; + if (StoreBits == 0 || StoreBits > StoreSizeBits) { + return makeUnexpected(ErrorCode::VerificationError, + "store lowering has an inconsistent memory type and size"); + } + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + const auto BuildStore = [&](Register StoredValue, Register Pointer, + const MachineMemOperand &StoreMemory) -> Expected<void> { + auto Store = MIRBuilder.buildInstr(opcode(GenericOpcode::G_STORE)); + if (!Store) + return std::unexpected(Store.error()); + if (auto Added = Store->addUse(StoredValue); !Added) + return std::unexpected(Added.error()); + if (auto Added = Store->addUse(Pointer); !Added) + return std::unexpected(Added.error()); + if (auto Added = Store->addMemOperand(StoreMemory); !Added) + return std::unexpected(Added.error()); + if (auto Built = Store->build(); !Built) + return std::unexpected(Built.error()); + return {}; + }; + + Register Source = *Value; + LowLevelType SourceType = *ValueType; + if (StoreBits != StoreSizeBits) { + if (!MemoryType.isCategoryNeutralScalar() || !SourceType.isCategoryNeutralScalar()) { + return makeUnexpected(ErrorCode::Unsupported, + "non-byte-sized pointer stores are not supported"); + } + auto WideMemoryType = LowLevelType::makeCategoryNeutralScalar(StoreSizeBits); + if (!WideMemoryType) + return std::unexpected(WideMemoryType.error()); + if (StoreSizeBits > SourceType.minimumBitWidth()) { + auto Extended = + buildResultInstruction(MIRBuilder, GenericOpcode::G_ANYEXT, *WideMemoryType, {Source}); + if (!Extended) + return std::unexpected(Extended.error()); + Source = *Extended; + SourceType = *WideMemoryType; + } + auto MaskValue = + APInt::getLowBitsSet(SourceType.scalarBitWidth(), static_cast<std::uint32_t>(StoreBits)); + if (!MaskValue) + return std::unexpected(MaskValue.error()); + auto Mask = buildConstantRegister(MIRBuilder, SourceType, std::move(*MaskValue)); + if (!Mask) + return std::unexpected(Mask.error()); + auto ZeroExtended = + buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, SourceType, {Source, *Mask}); + if (!ZeroExtended) + return std::unexpected(ZeroExtended.error()); + auto WideMemory = deriveMemoryOperand(Function, Memory, *WideMemoryType, 0); + if (!WideMemory) + return std::unexpected(WideMemory.error()); + if (auto Built = BuildStore(*ZeroExtended, *Address, **WideMemory); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); + } + + std::uint64_t LargeSplitBits = 0; + std::uint64_t SmallSplitBits = 0; + if (!std::has_single_bit(StoreBits)) { + LargeSplitBits = std::bit_floor(StoreBits); + SmallSplitBits = StoreBits - LargeSplitBits; + } else { + if (StoreBits < 16U) { + return makeUnexpected(ErrorCode::Unsupported, + "store lowering cannot split a byte-sized memory operation"); + } + LargeSplitBits = StoreBits / 2U; + SmallSplitBits = LargeSplitBits; + } + if (LargeSplitBits == 0 || SmallSplitBits == 0 || LargeSplitBits % 8U != 0 || + SmallSplitBits % 8U != 0) { + return makeUnexpected(ErrorCode::Unsupported, + "store lowering cannot split the requested memory width into bytes"); + } + + const std::uint64_t AnyExtBits = std::bit_ceil(StoreBits); + if (AnyExtBits == 0 || AnyExtBits > LowLevelType::MaxBitWidth) { + return makeUnexpected(ErrorCode::Unsupported, + "store lowering value width cannot be represented by the machine IR"); + } + auto NewSourceType = LowLevelType::makeCategoryNeutralScalar(AnyExtBits); + auto LargeMemoryType = LowLevelType::makeCategoryNeutralScalar(LargeSplitBits); + auto SmallMemoryType = LowLevelType::makeCategoryNeutralScalar(SmallSplitBits); + if (!NewSourceType) + return std::unexpected(NewSourceType.error()); + if (!LargeMemoryType) + return std::unexpected(LargeMemoryType.error()); + if (!SmallMemoryType) + return std::unexpected(SmallMemoryType.error()); + + if (SourceType.isPointer()) { + auto IntegerPointerType = LowLevelType::makeCategoryNeutralScalar(SourceType.scalarBitWidth()); + if (!IntegerPointerType) + return std::unexpected(IntegerPointerType.error()); + auto Converted = buildResultInstruction(MIRBuilder, GenericOpcode::G_PTRTOINT, + *IntegerPointerType, {Source}); + if (!Converted) + return std::unexpected(Converted.error()); + Source = *Converted; + SourceType = *IntegerPointerType; + } + auto Extended = MIRBuilder.buildAnyExtOrTrunc(*NewSourceType, Source); + if (!Extended) + return std::unexpected(Extended.error()); + auto ExtendedValue = resultRegister(**Extended); + if (!ExtendedValue) + return std::unexpected(ExtendedValue.error()); + auto ShiftAmount = + buildConstantRegister(MIRBuilder, *NewSourceType, static_cast<std::int64_t>(LargeSplitBits)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto SmallValue = buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *NewSourceType, + {*ExtendedValue, *ShiftAmount}); + if (!SmallValue) + return std::unexpected(SmallValue.error()); + auto SmallAddress = buildObjectPointerOffset(MIRBuilder, *Address, LargeSplitBits / 8U); + if (!SmallAddress) + return std::unexpected(SmallAddress.error()); + auto LargeMemory = deriveMemoryOperand(Function, Memory, *LargeMemoryType, 0); + auto SmallMemory = deriveMemoryOperand(Function, Memory, *SmallMemoryType, LargeSplitBits / 8U); + if (!LargeMemory) + return std::unexpected(LargeMemory.error()); + if (!SmallMemory) + return std::unexpected(SmallMemory.error()); + if (auto Built = BuildStore(*ExtendedValue, *Address, **LargeMemory); !Built) + return std::unexpected(Built.error()); + if (auto Built = BuildStore(*SmallValue, *SmallAddress, **SmallMemory); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lower(MachineInstr &Instruction, + const LegalizationDecision &Decision) { + if (Decision.Action != LegalizationAction::Lower) + return makeUnexpected(ErrorCode::InvalidArgument, + "lower received a non-lower legalization action"); + switch (static_cast<GenericOpcode>(Instruction.opcode().value())) { + case GenericOpcode::G_ABS: + return lowerAbsToAddXor(Instruction); + case GenericOpcode::G_LOAD: + case GenericOpcode::G_SEXTLOAD: + case GenericOpcode::G_ZEXTLOAD: + return lowerLoad(Instruction); + case GenericOpcode::G_STORE: + return lowerStore(Instruction); + case GenericOpcode::G_SMIN: + case GenericOpcode::G_SMAX: + case GenericOpcode::G_UMIN: + case GenericOpcode::G_UMAX: + return lowerMinMax(Instruction); + case GenericOpcode::G_SCMP: + case GenericOpcode::G_UCMP: + return lowerThreewayCompare(Instruction); + case GenericOpcode::G_SDIVREM: + case GenericOpcode::G_UDIVREM: + return lowerDivRem(Instruction); + case GenericOpcode::G_SMULH: + case GenericOpcode::G_UMULH: + return lowerHighMultiply(Instruction); + case GenericOpcode::G_SMULO: + case GenericOpcode::G_UMULO: + return lowerMulo(Instruction); + case GenericOpcode::G_CTLZ_ZERO_POISON: + case GenericOpcode::G_CTTZ_ZERO_POISON: + case GenericOpcode::G_CTLZ: + case GenericOpcode::G_CTTZ: + case GenericOpcode::G_CTPOP: + case GenericOpcode::G_CTLS: + return lowerBitCount(Instruction); + case GenericOpcode::G_BSWAP: + return lowerBswap(Instruction); + case GenericOpcode::G_FSHL: + case GenericOpcode::G_FSHR: + return lowerFunnelShift(Instruction); + case GenericOpcode::G_ROTL: + case GenericOpcode::G_ROTR: + return lowerRotate(Instruction); + case GenericOpcode::G_DYN_STACKALLOC: + return lowerDynStackAlloc(Instruction); + case GenericOpcode::G_STACKSAVE: + return lowerStackSave(Instruction); + case GenericOpcode::G_STACKRESTORE: + return lowerStackRestore(Instruction); + case GenericOpcode::G_MEMCPY_INLINE: + return lowerMemcpyInline(Instruction); + case GenericOpcode::G_ABDS: + case GenericOpcode::G_ABDU: { + const bool IsSigned = Instruction.opcode() == opcode(GenericOpcode::G_ABDS); + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + auto Type = MRI.getType(Destination); + if (!Type) + return std::unexpected(Type.error()); + + const GenericOpcode MinOpcode = IsSigned ? GenericOpcode::G_SMIN : GenericOpcode::G_UMIN; + const GenericOpcode MaxOpcode = IsSigned ? GenericOpcode::G_SMAX : GenericOpcode::G_UMAX; + auto MinLegal = Info.isLegal(LegalizationQuery(opcode(MinOpcode), {*Type})); + if (!MinLegal) + return std::unexpected(MinLegal.error()); + auto MaxLegal = Info.isLegal(LegalizationQuery(opcode(MaxOpcode), {*Type})); + if (!MaxLegal) + return std::unexpected(MaxLegal.error()); + if (*MinLegal && *MaxLegal) + return lowerAbsDiffToMinMax(Instruction); + return lowerAbsDiffToSelect(Instruction); + } + case GenericOpcode::G_SADDO: + case GenericOpcode::G_SSUBO: + case GenericOpcode::G_SADDE: + case GenericOpcode::G_SSUBE: + return lowerSignedAddSubOverflow(Instruction); + case GenericOpcode::G_UADDSAT: + case GenericOpcode::G_SADDSAT: + case GenericOpcode::G_USUBSAT: + case GenericOpcode::G_SSUBSAT: { + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + const bool IsSigned = + Generic == GenericOpcode::G_SADDSAT || Generic == GenericOpcode::G_SSUBSAT; + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "saturating add/sub lowering requires a register destination"); + auto Type = MRI.getType(*Destination); + if (!Type) + return std::unexpected(Type.error()); + + const GenericOpcode MinOpcode = IsSigned ? GenericOpcode::G_SMIN : GenericOpcode::G_UMIN; + auto MinLegal = Info.isLegal(LegalizationQuery(opcode(MinOpcode), {*Type})); + if (!MinLegal) + return std::unexpected(MinLegal.error()); + if (!IsSigned && *MinLegal) + return lowerSaturatingAddSubToMinMax(Instruction); + if (IsSigned) { + auto MaxLegal = Info.isLegal(LegalizationQuery(opcode(GenericOpcode::G_SMAX), {*Type})); + if (!MaxLegal) + return std::unexpected(MaxLegal.error()); + if (*MinLegal && *MaxLegal) + return lowerSaturatingAddSubToMinMax(Instruction); + } + return lowerSaturatingAddSubToOverflow(Instruction); + } + case GenericOpcode::G_SSHLSAT: + case GenericOpcode::G_USHLSAT: + return lowerSaturatingShiftLeft(Instruction); + case GenericOpcode::G_SEXT_INREG: { + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Source = *Instruction.operandAt(1)->getIf<Register>(); + auto SizeInBits = signedImmediate(*Instruction.operandAt(2)); + if (!SizeInBits) + return std::unexpected(SizeInBits.error()); + auto Type = MRI.getType(Destination); + if (!Type) + return std::unexpected(Type.error()); + + auto Temporary = MRI.createVirtualRegister(*Type); + if (!Temporary) + return std::unexpected(Temporary.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + const std::int64_t Shift = static_cast<std::int64_t>(Type->scalarBitWidth()) - *SizeInBits; + auto ShiftAmount = buildConstantRegister(MIRBuilder, *Type, Shift); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + if (auto Built = MIRBuilder.buildShl(*Temporary, Source, *ShiftAmount); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildAShr(Destination, *Temporary, *ShiftAmount); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_VAARG: + return lowerVAArg(Instruction); + case GenericOpcode::G_ATOMIC_CMPXCHG_WITH_SUCCESS: { + const Register OldValue = *Instruction.operandAt(0)->getIf<Register>(); + const Register Success = *Instruction.operandAt(1)->getIf<Register>(); + const Register Address = *Instruction.operandAt(2)->getIf<Register>(); + const Register Compare = *Instruction.operandAt(3)->getIf<Register>(); + const Register NewValue = *Instruction.operandAt(4)->getIf<Register>(); + auto NewOldValue = MRI.cloneVirtualRegister(OldValue); + if (!NewOldValue) + return std::unexpected(NewOldValue.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto Atomic = MIRBuilder.buildInstr(opcode(GenericOpcode::G_ATOMIC_CMPXCHG)); + if (!Atomic) + return std::unexpected(Atomic.error()); + if (auto Added = Atomic->addDef(*NewOldValue); !Added) + return std::unexpected(Added.error()); + if (auto Added = Atomic->addUse(Address); !Added) + return std::unexpected(Added.error()); + if (auto Added = Atomic->addUse(Compare); !Added) + return std::unexpected(Added.error()); + if (auto Added = Atomic->addUse(NewValue); !Added) + return std::unexpected(Added.error()); + if (auto Added = Atomic->addMemOperand(*Instruction.memOperands().front()); !Added) + return std::unexpected(Added.error()); + if (auto Built = Atomic->build(); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildICmp(IntegerPredicate::Equal, Success, *NewOldValue, Compare); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildCopy(OldValue, *NewOldValue); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_ATOMICRMW_SUB: { + const Register Result = *Instruction.operandAt(0)->getIf<Register>(); + const Register Address = *Instruction.operandAt(1)->getIf<Register>(); + const Register Value = *Instruction.operandAt(2)->getIf<Register>(); + auto ValueType = MRI.getType(Value); + if (!ValueType) + return std::unexpected(ValueType.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto Zero = buildConstantRegister(MIRBuilder, *ValueType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + auto Negated = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *ValueType, {*Zero, Value}); + if (!Negated) + return std::unexpected(Negated.error()); + + auto AtomicAdd = MIRBuilder.buildInstr(opcode(GenericOpcode::G_ATOMICRMW_ADD)); + if (!AtomicAdd) + return std::unexpected(AtomicAdd.error()); + if (auto Added = AtomicAdd->addDef(Result); !Added) + return std::unexpected(Added.error()); + if (auto Added = AtomicAdd->addUse(Address); !Added) + return std::unexpected(Added.error()); + if (auto Added = AtomicAdd->addUse(*Negated); !Added) + return std::unexpected(Added.error()); + if (auto Added = AtomicAdd->addMemOperand(*Instruction.memOperands().front()); !Added) + return std::unexpected(Added.error()); + if (auto Built = AtomicAdd->build(); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_UADDO: { + const Register Result = *Instruction.operandAt(0)->getIf<Register>(); + const Register CarryOut = *Instruction.operandAt(1)->getIf<Register>(); + const Register Left = *Instruction.operandAt(2)->getIf<Register>(); + const Register Right = *Instruction.operandAt(3)->getIf<Register>(); + auto NewResult = MRI.cloneVirtualRegister(Result); + if (!NewResult) + return std::unexpected(NewResult.error()); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildAdd(*NewResult, Left, Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::UnsignedLessThan, CarryOut, *NewResult, Right); + !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildCopy(Result, *NewResult); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_UADDE: { + const Register Result = *Instruction.operandAt(0)->getIf<Register>(); + const Register CarryOut = *Instruction.operandAt(1)->getIf<Register>(); + const Register Left = *Instruction.operandAt(2)->getIf<Register>(); + const Register Right = *Instruction.operandAt(3)->getIf<Register>(); + const Register CarryIn = *Instruction.operandAt(4)->getIf<Register>(); + auto ResultType = MRI.getType(Result); + auto ConditionType = MRI.getType(CarryOut); + if (!ResultType) + return std::unexpected(ResultType.error()); + if (!ConditionType) + return std::unexpected(ConditionType.error()); + auto NewResult = MRI.cloneVirtualRegister(Result); + auto TemporaryResult = MRI.createVirtualRegister(*ResultType); + auto Carry = MRI.createVirtualRegister(*ConditionType); + auto ExtendedCarryIn = MRI.createVirtualRegister(*ResultType); + auto ResultIsZero = MRI.createVirtualRegister(*ConditionType); + auto SecondCarry = MRI.createVirtualRegister(*ConditionType); + if (!NewResult) + return std::unexpected(NewResult.error()); + if (!TemporaryResult) + return std::unexpected(TemporaryResult.error()); + if (!Carry) + return std::unexpected(Carry.error()); + if (!ExtendedCarryIn) + return std::unexpected(ExtendedCarryIn.error()); + if (!ResultIsZero) + return std::unexpected(ResultIsZero.error()); + if (!SecondCarry) + return std::unexpected(SecondCarry.error()); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildAdd(*TemporaryResult, Left, Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildICmp(IntegerPredicate::UnsignedLessThan, *Carry, + *TemporaryResult, Left); + !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildZExt(*ExtendedCarryIn, CarryIn); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildAdd(*NewResult, *TemporaryResult, *ExtendedCarryIn); !Built) + return std::unexpected(Built.error()); + auto Zero = buildConstantRegister(MIRBuilder, *ResultType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::Equal, *ResultIsZero, *NewResult, *Zero); + !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildAnd(*SecondCarry, *ResultIsZero, CarryIn); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildOr(CarryOut, *Carry, *SecondCarry); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildCopy(Result, *NewResult); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_USUBO: { + const Register Result = *Instruction.operandAt(0)->getIf<Register>(); + const Register BorrowOut = *Instruction.operandAt(1)->getIf<Register>(); + const Register Left = *Instruction.operandAt(2)->getIf<Register>(); + const Register Right = *Instruction.operandAt(3)->getIf<Register>(); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildSub(Result, Left, Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::UnsignedLessThan, BorrowOut, Left, Right); + !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); + } + case GenericOpcode::G_USUBE: { + const Register Result = *Instruction.operandAt(0)->getIf<Register>(); + const Register BorrowOut = *Instruction.operandAt(1)->getIf<Register>(); + const Register Left = *Instruction.operandAt(2)->getIf<Register>(); + const Register Right = *Instruction.operandAt(3)->getIf<Register>(); + const Register BorrowIn = *Instruction.operandAt(4)->getIf<Register>(); + auto ResultType = MRI.getType(Result); + auto ConditionType = MRI.getType(BorrowOut); + if (!ResultType) + return std::unexpected(ResultType.error()); + if (!ConditionType) + return std::unexpected(ConditionType.error()); + auto TemporaryResult = MRI.createVirtualRegister(*ResultType); + auto Borrow = MRI.createVirtualRegister(*ConditionType); + auto ExtendedBorrowIn = MRI.createVirtualRegister(*ResultType); + auto TemporaryResultIsZero = MRI.createVirtualRegister(*ConditionType); + auto SecondBorrow = MRI.createVirtualRegister(*ConditionType); + if (!TemporaryResult) + return std::unexpected(TemporaryResult.error()); + if (!Borrow) + return std::unexpected(Borrow.error()); + if (!ExtendedBorrowIn) + return std::unexpected(ExtendedBorrowIn.error()); + if (!TemporaryResultIsZero) + return std::unexpected(TemporaryResultIsZero.error()); + if (!SecondBorrow) + return std::unexpected(SecondBorrow.error()); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildSub(*TemporaryResult, Left, Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildICmp(IntegerPredicate::UnsignedGreaterThan, *Borrow, + *TemporaryResult, Left); + !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildZExt(*ExtendedBorrowIn, BorrowIn); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildSub(Result, *TemporaryResult, *ExtendedBorrowIn); !Built) + return std::unexpected(Built.error()); + auto Zero = buildConstantRegister(MIRBuilder, *ResultType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + if (auto Built = MIRBuilder.buildICmp(IntegerPredicate::Equal, *TemporaryResultIsZero, + *TemporaryResult, *Zero); + !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildAnd(*SecondBorrow, *TemporaryResultIsZero, BorrowIn); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildOr(BorrowOut, *Borrow, *SecondBorrow); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); + } + default: { + const InstructionDescriptor *Descriptor = + MIRBuilder.target().instructionDescriptor(Instruction.opcode()); + const std::string_view Name = + Descriptor == nullptr ? std::string_view{"<unknown>"} : Descriptor->Name; + return makeUnexpected(ErrorCode::Unsupported, + "generic lowering is not implemented for opcode '" + std::string(Name) + + "'"); + } + } +} + +Expected<void> LegalizerHelper::lowerDynStackAlloc(MachineInstr &Instruction) { + if (Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::VerificationError, + "G_DYN_STACKALLOC requires destination, size, and alignment operands"); + if (MIRBuilder.target().stackGrowthDirection() != StackGrowthDirection::Down) + return makeUnexpected(ErrorCode::Unsupported, + "dynamic stack allocation requires a downward-growing stack"); + + const PhysicalRegister StackPointer = MIRBuilder.target().stackPointerRegisterToSaveRestore(); + if (!StackPointer.isValid()) + return makeUnexpected(ErrorCode::Unsupported, + "target does not define a stack pointer for dynamic allocation"); + + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *SizeOperand = Instruction.operandAt(1); + const MachineOperand *AlignmentOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *AllocationSize = + SizeOperand == nullptr ? nullptr : SizeOperand->getIf<Register>(); + if (Destination == nullptr || AllocationSize == nullptr || AlignmentOperand == nullptr) + return makeUnexpected(ErrorCode::VerificationError, "G_DYN_STACKALLOC has invalid operands"); + + auto PointerType = MRI.getType(*Destination); + auto AllocationSizeType = MRI.getType(*AllocationSize); + auto AlignmentValue = signedImmediate(*AlignmentOperand); + if (!PointerType) + return std::unexpected(PointerType.error()); + if (!AllocationSizeType) + return std::unexpected(AllocationSizeType.error()); + if (!AlignmentValue) + return std::unexpected(AlignmentValue.error()); + if (!PointerType->isPointer() || !AllocationSizeType->isCategoryNeutralScalar() || + PointerType->scalarBitWidth() != AllocationSizeType->scalarBitWidth()) { + return makeUnexpected(ErrorCode::VerificationError, + "G_DYN_STACKALLOC requires pointer-width integer allocation size"); + } + if (*AlignmentValue <= 0 || !std::has_single_bit(static_cast<std::uint64_t>(*AlignmentValue))) { + return makeUnexpected(ErrorCode::VerificationError, + "G_DYN_STACKALLOC alignment must be a positive power of two"); + } + + auto StackPointerCopy = MRI.createVirtualRegister(*PointerType); + auto IntegerStackPointer = MRI.createVirtualRegister(*AllocationSizeType); + auto Allocation = MRI.createVirtualRegister(*AllocationSizeType); + if (!StackPointerCopy) + return std::unexpected(StackPointerCopy.error()); + if (!IntegerStackPointer) + return std::unexpected(IntegerStackPointer.error()); + if (!Allocation) + return std::unexpected(Allocation.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildCopy(*StackPointerCopy, Register::fromPhysical(StackPointer)); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildPtrToInt(*IntegerStackPointer, *StackPointerCopy); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildSub(*Allocation, *IntegerStackPointer, *AllocationSize); !Built) + return std::unexpected(Built.error()); + + Register AlignedAllocation = *Allocation; + if (*AlignmentValue > 1) { + auto LowBits = APInt::fromUnsigned(AllocationSizeType->scalarBitWidth(), + static_cast<std::uint64_t>(*AlignmentValue) - 1U); + if (!LowBits) + return std::unexpected(LowBits.error()); + auto AlignmentMask = LowBits->bitwiseNot(); + if (!AlignmentMask) + return std::unexpected(AlignmentMask.error()); + auto Mask = buildConstantRegister(MIRBuilder, *AllocationSizeType, std::move(*AlignmentMask)); + if (!Mask) + return std::unexpected(Mask.error()); + auto MaskedAllocation = MRI.createVirtualRegister(*AllocationSizeType); + if (!MaskedAllocation) + return std::unexpected(MaskedAllocation.error()); + if (auto Built = MIRBuilder.buildAnd(*MaskedAllocation, *Allocation, *Mask); !Built) + return std::unexpected(Built.error()); + AlignedAllocation = *MaskedAllocation; + } + + auto TargetPointer = MRI.createVirtualRegister(*PointerType); + if (!TargetPointer) + return std::unexpected(TargetPointer.error()); + if (auto Built = MIRBuilder.buildIntToPtr(*TargetPointer, AlignedAllocation); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildCopy(Register::fromPhysical(StackPointer), *TargetPointer); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildCopy(*Destination, *TargetPointer); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerStackSave(MachineInstr &Instruction) { + const PhysicalRegister StackPointer = MIRBuilder.target().stackPointerRegisterToSaveRestore(); + if (!StackPointer.isValid()) + return makeUnexpected(ErrorCode::Unsupported, + "target does not define a stack pointer for stack save"); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Instruction.operandCount() != 1 || Destination == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_STACKSAVE requires one register destination"); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildCopy(*Destination, Register::fromPhysical(StackPointer)); + !Built) { + return std::unexpected(Built.error()); + } + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerStackRestore(MachineInstr &Instruction) { + const PhysicalRegister StackPointer = MIRBuilder.target().stackPointerRegisterToSaveRestore(); + if (!StackPointer.isValid()) + return makeUnexpected(ErrorCode::Unsupported, + "target does not define a stack pointer for stack restore"); + const MachineOperand *SourceOperand = Instruction.operandAt(0); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Instruction.operandCount() != 1 || Source == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "G_STACKRESTORE requires one register source"); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildCopy(Register::fromPhysical(StackPointer), *Source); !Built) + return std::unexpected(Built.error()); + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerMemcpyInline(MachineInstr &Instruction) { + if (Instruction.operandCount() != 3 || Instruction.memOperands().size() != 2 || + Instruction.memOperands()[0] == nullptr || Instruction.memOperands()[1] == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "G_MEMCPY_INLINE requires destination, source, length, and two MMOs"); + } + + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *SourceOperand = Instruction.operandAt(1); + const MachineOperand *LengthOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + const Register *Length = LengthOperand == nullptr ? nullptr : LengthOperand->getIf<Register>(); + if (Destination == nullptr || Source == nullptr || Length == nullptr || + !Destination->isVirtual() || !Source->isVirtual() || !Length->isVirtual()) { + return makeUnexpected(ErrorCode::VerificationError, + "G_MEMCPY_INLINE requires virtual-register operands"); + } + + const MachineMemOperand &StoreMemory = *Instruction.memOperands()[0]; + const MachineMemOperand &LoadMemory = *Instruction.memOperands()[1]; + if (!StoreMemory.isStore() || StoreMemory.isLoad() || !LoadMemory.isLoad() || + LoadMemory.isStore() || StoreMemory.isAtomic() || LoadMemory.isAtomic()) { + return makeUnexpected(ErrorCode::VerificationError, + "G_MEMCPY_INLINE requires non-atomic store and load memory operands"); + } + + const std::optional<ValueAndVReg> ConstantLength = + getIConstantVRegValWithLookThrough(*Length, MRI); + if (!ConstantLength || ConstantLength->Value.bitWidth() == 0 || + ConstantLength->Value.bitWidth() > 64) { + return makeUnexpected(ErrorCode::Unsupported, + "G_MEMCPY_INLINE requires a constant length no wider than 64 bits"); + } + const std::uint64_t KnownLength = ConstantLength->Value.words().front(); + if (KnownLength == 0) { + return Instruction.eraseFromParent(); + } + + auto ByteType = LowLevelType::makeCategoryNeutralScalar(8); + if (!ByteType) + return std::unexpected(ByteType.error()); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + MachineFunction &Owner = MIRBuilder.function(); + for (std::uint64_t Offset = 0; Offset != KnownLength; ++Offset) { + auto LoadAddress = buildObjectPointerOffset(MIRBuilder, *Source, Offset); + if (!LoadAddress) + return std::unexpected(LoadAddress.error()); + auto ByteLoadMemory = deriveMemoryOperand(Owner, LoadMemory, *ByteType, Offset); + if (!ByteLoadMemory) + return std::unexpected(ByteLoadMemory.error()); + auto LoadedByte = MRI.createVirtualRegister(*ByteType); + if (!LoadedByte) + return std::unexpected(LoadedByte.error()); + auto Load = MIRBuilder.buildInstr(opcode(GenericOpcode::G_LOAD)); + if (!Load) + return std::unexpected(Load.error()); + if (auto Added = Load->addDef(*LoadedByte); !Added) + return std::unexpected(Added.error()); + if (auto Added = Load->addUse(*LoadAddress); !Added) + return std::unexpected(Added.error()); + if (auto Added = Load->addMemOperand(**ByteLoadMemory); !Added) + return std::unexpected(Added.error()); + if (auto Built = Load->build(); !Built) + return std::unexpected(Built.error()); + + auto StoreAddress = buildObjectPointerOffset(MIRBuilder, *Destination, Offset); + if (!StoreAddress) + return std::unexpected(StoreAddress.error()); + auto ByteStoreMemory = deriveMemoryOperand(Owner, StoreMemory, *ByteType, Offset); + if (!ByteStoreMemory) + return std::unexpected(ByteStoreMemory.error()); + auto Store = MIRBuilder.buildInstr(opcode(GenericOpcode::G_STORE)); + if (!Store) + return std::unexpected(Store.error()); + if (auto Added = Store->addUse(*LoadedByte); !Added) + return std::unexpected(Added.error()); + if (auto Added = Store->addUse(*StoreAddress); !Added) + return std::unexpected(Added.error()); + if (auto Added = Store->addMemOperand(**ByteStoreMemory); !Added) + return std::unexpected(Added.error()); + if (auto Built = Store->build(); !Built) + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerAbsToAddXor(MachineInstr &Instruction) { + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Source = *Instruction.operandAt(1)->getIf<Register>(); + auto Type = MRI.getType(Destination); + if (!Type) + return std::unexpected(Type.error()); + + auto Shift = MRI.createVirtualRegister(*Type); + auto Add = MRI.createVirtualRegister(*Type); + if (!Shift) + return std::unexpected(Shift.error()); + if (!Add) + return std::unexpected(Add.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto ShiftAmount = buildConstantRegister(MIRBuilder, *Type, + static_cast<std::int64_t>(Type->scalarBitWidth() - 1U)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + if (auto Built = MIRBuilder.buildAShr(*Shift, Source, *ShiftAmount); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildAdd(*Add, Source, *Shift); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildXor(Destination, *Add, *Shift); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerAbsDiffToSelect(MachineInstr &Instruction) { + const bool IsSigned = Instruction.opcode() == opcode(GenericOpcode::G_ABDS); + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Left = *Instruction.operandAt(1)->getIf<Register>(); + const Register Right = *Instruction.operandAt(2)->getIf<Register>(); + auto Type = MRI.getType(Left); + if (!Type) + return std::unexpected(Type.error()); + auto ConditionType = LowLevelType::makeCategoryNeutralScalar(1); + if (!ConditionType) + return std::unexpected(ConditionType.error()); + + auto LeftSub = MRI.createVirtualRegister(*Type); + auto RightSub = MRI.createVirtualRegister(*Type); + auto Condition = MRI.createVirtualRegister(*ConditionType); + if (!LeftSub) + return std::unexpected(LeftSub.error()); + if (!RightSub) + return std::unexpected(RightSub.error()); + if (!Condition) + return std::unexpected(Condition.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildSub(*LeftSub, Left, Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildSub(*RightSub, Right, Left); !Built) + return std::unexpected(Built.error()); + const IntegerPredicate Predicate = + IsSigned ? IntegerPredicate::SignedGreaterThan : IntegerPredicate::UnsignedGreaterThan; + if (auto Built = MIRBuilder.buildICmp(Predicate, *Condition, Left, Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {Destination}, + {*Condition, *LeftSub, *RightSub}); + !Built) { + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerAbsDiffToMinMax(MachineInstr &Instruction) { + const bool IsSigned = Instruction.opcode() == opcode(GenericOpcode::G_ABDS); + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Left = *Instruction.operandAt(1)->getIf<Register>(); + const Register Right = *Instruction.operandAt(2)->getIf<Register>(); + auto Type = MRI.getType(Left); + if (!Type) + return std::unexpected(Type.error()); + + auto Maximum = MRI.createVirtualRegister(*Type); + auto Minimum = MRI.createVirtualRegister(*Type); + if (!Maximum) + return std::unexpected(Maximum.error()); + if (!Minimum) + return std::unexpected(Minimum.error()); + + const GenericOpcode MaxOpcode = IsSigned ? GenericOpcode::G_SMAX : GenericOpcode::G_UMAX; + const GenericOpcode MinOpcode = IsSigned ? GenericOpcode::G_SMIN : GenericOpcode::G_UMIN; + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(MaxOpcode), {*Maximum}, {Left, Right}); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(MinOpcode), {*Minimum}, {Left, Right}); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildSub(Destination, *Maximum, *Minimum); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerMinMax(MachineInstr &Instruction) { + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Left = *Instruction.operandAt(1)->getIf<Register>(); + const Register Right = *Instruction.operandAt(2)->getIf<Register>(); + + auto DestinationType = MRI.getType(Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + auto ConditionType = booleanTypeFor(*DestinationType); + if (!ConditionType) + return std::unexpected(ConditionType.error()); + + auto Condition = MRI.createVirtualRegister(*ConditionType); + if (!Condition) + return std::unexpected(Condition.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + const auto Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (auto Built = MIRBuilder.buildICmp(minMaxToCompare(Generic), *Condition, Left, Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {Destination}, + {*Condition, Left, Right}); + !Built) { + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerThreewayCompare(MachineInstr &Instruction) { + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Left = *Instruction.operandAt(1)->getIf<Register>(); + const Register Right = *Instruction.operandAt(2)->getIf<Register>(); + auto DestinationType = MRI.getType(Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + auto ConditionType = booleanTypeFor(*DestinationType); + if (!ConditionType) + return std::unexpected(ConditionType.error()); + + auto IsGreater = MRI.createVirtualRegister(*ConditionType); + auto IsLess = MRI.createVirtualRegister(*ConditionType); + if (!IsGreater) + return std::unexpected(IsGreater.error()); + if (!IsLess) + return std::unexpected(IsLess.error()); + + const bool IsSigned = Instruction.opcode() == opcode(GenericOpcode::G_SCMP); + const IntegerPredicate GreaterPredicate = + IsSigned ? IntegerPredicate::SignedGreaterThan : IntegerPredicate::UnsignedGreaterThan; + const IntegerPredicate LessPredicate = + IsSigned ? IntegerPredicate::SignedLessThan : IntegerPredicate::UnsignedLessThan; + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = MIRBuilder.buildICmp(GreaterPredicate, *IsGreater, Left, Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildICmp(LessPredicate, *IsLess, Left, Right); !Built) + return std::unexpected(Built.error()); + + const BooleanContent Contents = + MIRBuilder.target().getBooleanContents(DestinationType->isVector(), false); + if (Contents == BooleanContent::Undefined) { + auto Zero = buildConstantRegister(MIRBuilder, *DestinationType, 0); + auto One = buildConstantRegister(MIRBuilder, *DestinationType, 1); + auto MinusOne = buildConstantRegister(MIRBuilder, *DestinationType, -1); + if (!Zero) + return std::unexpected(Zero.error()); + if (!One) + return std::unexpected(One.error()); + if (!MinusOne) + return std::unexpected(MinusOne.error()); + auto ZeroOrOne = buildResultInstruction(MIRBuilder, GenericOpcode::G_SELECT, *DestinationType, + {*IsGreater, *One, *Zero}); + if (!ZeroOrOne) + return std::unexpected(ZeroOrOne.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {Destination}, + {*IsLess, *MinusOne, *ZeroOrOne}); + !Built) { + return std::unexpected(Built.error()); + } + } else { + if (Contents == BooleanContent::ZeroOrNegativeOne) + std::swap(IsGreater, IsLess); + const GenericOpcode ExtensionOpcode = Contents == BooleanContent::ZeroOrNegativeOne + ? GenericOpcode::G_SEXT + : GenericOpcode::G_ZEXT; + auto ExtendedGreater = MRI.createVirtualRegister(*DestinationType); + auto ExtendedLess = MRI.createVirtualRegister(*DestinationType); + if (!ExtendedGreater) + return std::unexpected(ExtendedGreater.error()); + if (!ExtendedLess) + return std::unexpected(ExtendedLess.error()); + if (auto Built = + MIRBuilder.buildInstr(opcode(ExtensionOpcode), {*ExtendedGreater}, {*IsGreater}); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildInstr(opcode(ExtensionOpcode), {*ExtendedLess}, {*IsLess}); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildSub(Destination, *ExtendedGreater, *ExtendedLess); !Built) + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerDivRem(MachineInstr &Instruction) { + const bool IsSigned = Instruction.opcode() == opcode(GenericOpcode::G_SDIVREM); + const Register Quotient = *Instruction.operandAt(0)->getIf<Register>(); + const Register Remainder = *Instruction.operandAt(1)->getIf<Register>(); + const Register Dividend = *Instruction.operandAt(2)->getIf<Register>(); + const Register Divisor = *Instruction.operandAt(3)->getIf<Register>(); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + const GenericOpcode DivideOpcode = IsSigned ? GenericOpcode::G_SDIV : GenericOpcode::G_UDIV; + const GenericOpcode RemainderOpcode = IsSigned ? GenericOpcode::G_SREM : GenericOpcode::G_UREM; + if (auto Built = MIRBuilder.buildInstr(opcode(DivideOpcode), {Quotient}, {Dividend, Divisor}); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildInstr(opcode(RemainderOpcode), {Remainder}, {Dividend, Divisor}); + !Built) { + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerHighMultiply(MachineInstr &Instruction) { + if (Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::VerificationError, + "high multiply lowering requires a destination and two sources"); + const bool IsSigned = Instruction.opcode() == opcode(GenericOpcode::G_SMULH); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *FirstSourceOperand = Instruction.operandAt(1); + const MachineOperand *SecondSourceOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *FirstSource = + FirstSourceOperand == nullptr ? nullptr : FirstSourceOperand->getIf<Register>(); + const Register *SecondSource = + SecondSourceOperand == nullptr ? nullptr : SecondSourceOperand->getIf<Register>(); + if (Destination == nullptr || FirstSource == nullptr || SecondSource == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "high multiply lowering requires register operands"); + auto OriginalType = MRI.getType(*Destination); + if (!OriginalType) + return std::unexpected(OriginalType.error()); + if (!OriginalType->isCategoryNeutralScalar() || OriginalType->scalarBitWidth() == 0 || + OriginalType->scalarBitWidth() > LowLevelType::MaxBitWidth / 2U) { + return makeUnexpected( + ErrorCode::Unsupported, + "high multiply lowering requires a scalar type with a representable double width"); + } + const std::uint32_t SizeInBits = OriginalType->scalarBitWidth(); + auto WideType = LowLevelType::makeCategoryNeutralScalar(SizeInBits * 2U); + if (!WideType) + return std::unexpected(WideType.error()); + + auto WideFirstSource = MRI.createVirtualRegister(*WideType); + auto WideSecondSource = MRI.createVirtualRegister(*WideType); + if (!WideFirstSource) + return std::unexpected(WideFirstSource.error()); + if (!WideSecondSource) + return std::unexpected(WideSecondSource.error()); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = IsSigned ? MIRBuilder.buildSExt(*WideFirstSource, *FirstSource) + : MIRBuilder.buildZExt(*WideFirstSource, *FirstSource); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = IsSigned ? MIRBuilder.buildSExt(*WideSecondSource, *SecondSource) + : MIRBuilder.buildZExt(*WideSecondSource, *SecondSource); + !Built) { + return std::unexpected(Built.error()); + } + auto Product = buildResultInstruction(MIRBuilder, GenericOpcode::G_MUL, *WideType, + {*WideFirstSource, *WideSecondSource}); + if (!Product) + return std::unexpected(Product.error()); + auto ShiftAmount = buildConstantRegister(MIRBuilder, *WideType, SizeInBits); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto Shifted = + buildResultInstruction(MIRBuilder, IsSigned ? GenericOpcode::G_ASHR : GenericOpcode::G_LSHR, + *WideType, {*Product, *ShiftAmount}); + if (!Shifted) + return std::unexpected(Shifted.error()); + if (auto Built = MIRBuilder.buildTrunc(*Destination, *Shifted); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerMulo(MachineInstr &Instruction) { + if (Instruction.operandCount() != 4) + return makeUnexpected(ErrorCode::VerificationError, + "multiply-overflow lowering requires four register operands"); + + const bool IsSigned = Instruction.opcode() == opcode(GenericOpcode::G_SMULO); + const Register *ResultOperand = Instruction.operandAt(0)->getIf<Register>(); + const Register *OverflowOperand = Instruction.operandAt(1)->getIf<Register>(); + const Register *LeftOperand = Instruction.operandAt(2)->getIf<Register>(); + const Register *RightOperand = Instruction.operandAt(3)->getIf<Register>(); + if (ResultOperand == nullptr || OverflowOperand == nullptr || LeftOperand == nullptr || + RightOperand == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "multiply-overflow lowering requires register operands"); + } + const Register Result = *ResultOperand; + const Register Overflow = *OverflowOperand; + const Register Left = *LeftOperand; + const Register Right = *RightOperand; + auto ResultType = MRI.getType(Result); + if (!ResultType) + return std::unexpected(ResultType.error()); + if (!ResultType->isCategoryNeutralScalar() || ResultType->scalarBitWidth() == 0) + return makeUnexpected(ErrorCode::Unsupported, + "multiply-overflow lowering requires a scalar result type"); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + Observer.changingInstr(Instruction); + if (auto Changed = Instruction.setOpcode(opcode(GenericOpcode::G_MUL)); !Changed) + return std::unexpected(Changed.error()); + if (auto Changed = Instruction.eraseOperand(1); !Changed) + return std::unexpected(Changed.error()); + Observer.changedInstr(Instruction); + + auto HighPart = + buildResultInstruction(MIRBuilder, IsSigned ? GenericOpcode::G_SMULH : GenericOpcode::G_UMULH, + *ResultType, {Left, Right}); + if (!HighPart) + return std::unexpected(HighPart.error()); + auto Zero = buildConstantRegister(MIRBuilder, *ResultType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + + if (auto Point = MIRBuilder.setInsertPointAfter(Instruction); !Point) + return std::unexpected(Point.error()); + if (IsSigned) { + auto ShiftAmount = + buildConstantRegister(MIRBuilder, *ResultType, ResultType->scalarBitWidth() - 1U); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto Shifted = buildResultInstruction(MIRBuilder, GenericOpcode::G_ASHR, *ResultType, + {Result, *ShiftAmount}); + if (!Shifted) + return std::unexpected(Shifted.error()); + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::NotEqual, Overflow, *HighPart, *Shifted); + !Built) { + return std::unexpected(Built.error()); + } + } else if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::NotEqual, Overflow, *HighPart, *Zero); + !Built) { + return std::unexpected(Built.error()); + } + return {}; +} + +Expected<void> LegalizerHelper::lowerBitCount(MachineInstr &Instruction) { + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + MachineOperand *DestinationOperand = Instruction.operandAt(0); + MachineOperand *SourceOperand = Instruction.operandAt(1); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Source = SourceOperand == nullptr ? nullptr : SourceOperand->getIf<Register>(); + if (Destination == nullptr || Source == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "bit-count lowering requires register operands"); + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + auto SourceType = MRI.getType(*Source); + if (!SourceType) + return std::unexpected(SourceType.error()); + if (!DestinationType->isCategoryNeutralScalar() || !SourceType->isCategoryNeutralScalar()) + return makeUnexpected(ErrorCode::Unsupported, + "non-scalar bit-count lowering is not implemented"); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + const auto IsSupported = [this](GenericOpcode Opcode, LowLevelType ResultType, + LowLevelType OperandType) -> Expected<bool> { + auto Decision = Info.getDecision(LegalizationQuery(opcode(Opcode), {ResultType, OperandType})); + if (!Decision) + return std::unexpected(Decision.error()); + return Decision->Action == LegalizationAction::Legal || + Decision->Action == LegalizationAction::Libcall || + Decision->Action == LegalizationAction::Custom; + }; + const auto EraseOriginal = [&Instruction]() -> Expected<void> { + return Instruction.eraseFromParent(); + }; + + switch (Generic) { + case GenericOpcode::G_CTLZ_ZERO_POISON: + case GenericOpcode::G_CTTZ_ZERO_POISON: { + const GenericOpcode Replacement = Generic == GenericOpcode::G_CTLZ_ZERO_POISON + ? GenericOpcode::G_CTLZ + : GenericOpcode::G_CTTZ; + Observer.changingInstr(Instruction); + if (auto Changed = Instruction.setOpcode(opcode(Replacement)); !Changed) + return std::unexpected(Changed.error()); + Observer.changedInstr(Instruction); + return {}; + } + + case GenericOpcode::G_CTLZ: { + const std::uint32_t Size = SourceType->scalarBitWidth(); + auto PoisonSupported = + IsSupported(GenericOpcode::G_CTLZ_ZERO_POISON, *DestinationType, *SourceType); + if (!PoisonSupported) + return std::unexpected(PoisonSupported.error()); + if (*PoisonSupported) { + auto PoisonCount = buildResultInstruction(MIRBuilder, GenericOpcode::G_CTLZ_ZERO_POISON, + *DestinationType, {*Source}); + if (!PoisonCount) + return std::unexpected(PoisonCount.error()); + auto Zero = buildConstantRegister(MIRBuilder, *SourceType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + auto ConditionType = booleanTypeFor(*SourceType); + if (!ConditionType) + return std::unexpected(ConditionType.error()); + auto IsZero = + buildICmpRegister(MIRBuilder, IntegerPredicate::Equal, *ConditionType, *Source, *Zero); + if (!IsZero) + return std::unexpected(IsZero.error()); + auto SizeConstant = + buildConstantRegister(MIRBuilder, *DestinationType, static_cast<std::int64_t>(Size)); + if (!SizeConstant) + return std::unexpected(SizeConstant.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {*Destination}, + {*IsZero, *SizeConstant, *PoisonCount}); + !Built) { + return std::unexpected(Built.error()); + } + return EraseOriginal(); + } + + Register Value = *Source; + const std::uint32_t ExpandedSize = std::bit_ceil(Size); + for (std::uint32_t Shift = 1; Shift <= ExpandedSize / 2U; Shift <<= 1U) { + auto ShiftAmount = + buildConstantRegister(MIRBuilder, *SourceType, static_cast<std::int64_t>(Shift)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto Shifted = buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *SourceType, + {Value, *ShiftAmount}); + if (!Shifted) + return std::unexpected(Shifted.error()); + auto Propagated = + buildResultInstruction(MIRBuilder, GenericOpcode::G_OR, *SourceType, {Value, *Shifted}); + if (!Propagated) + return std::unexpected(Propagated.error()); + Value = *Propagated; + } + auto Population = + buildResultInstruction(MIRBuilder, GenericOpcode::G_CTPOP, *DestinationType, {Value}); + if (!Population) + return std::unexpected(Population.error()); + auto SizeConstant = + buildConstantRegister(MIRBuilder, *DestinationType, static_cast<std::int64_t>(Size)); + if (!SizeConstant) + return std::unexpected(SizeConstant.error()); + if (auto Built = MIRBuilder.buildSub(*Destination, *SizeConstant, *Population); !Built) + return std::unexpected(Built.error()); + return EraseOriginal(); + } + + case GenericOpcode::G_CTTZ: { + const std::uint32_t Size = SourceType->scalarBitWidth(); + auto PoisonSupported = + IsSupported(GenericOpcode::G_CTTZ_ZERO_POISON, *DestinationType, *SourceType); + if (!PoisonSupported) + return std::unexpected(PoisonSupported.error()); + if (*PoisonSupported) { + auto PoisonCount = buildResultInstruction(MIRBuilder, GenericOpcode::G_CTTZ_ZERO_POISON, + *DestinationType, {*Source}); + if (!PoisonCount) + return std::unexpected(PoisonCount.error()); + auto Zero = buildConstantRegister(MIRBuilder, *SourceType, 0); + if (!Zero) + return std::unexpected(Zero.error()); + auto ConditionType = booleanTypeFor(*DestinationType); + if (!ConditionType) + return std::unexpected(ConditionType.error()); + auto IsZero = + buildICmpRegister(MIRBuilder, IntegerPredicate::Equal, *ConditionType, *Source, *Zero); + if (!IsZero) + return std::unexpected(IsZero.error()); + auto SizeConstant = + buildConstantRegister(MIRBuilder, *DestinationType, static_cast<std::int64_t>(Size)); + if (!SizeConstant) + return std::unexpected(SizeConstant.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {*Destination}, + {*IsZero, *SizeConstant, *PoisonCount}); + !Built) { + return std::unexpected(Built.error()); + } + return EraseOriginal(); + } + + auto MinusOne = buildConstantRegister(MIRBuilder, *SourceType, -1); + if (!MinusOne) + return std::unexpected(MinusOne.error()); + auto Inverted = + buildResultInstruction(MIRBuilder, GenericOpcode::G_XOR, *SourceType, {*Source, *MinusOne}); + if (!Inverted) + return std::unexpected(Inverted.error()); + auto Decremented = + buildResultInstruction(MIRBuilder, GenericOpcode::G_ADD, *SourceType, {*Source, *MinusOne}); + if (!Decremented) + return std::unexpected(Decremented.error()); + auto Masked = buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *SourceType, + {*Inverted, *Decremented}); + if (!Masked) + return std::unexpected(Masked.error()); + + auto PopulationSupported = IsSupported(GenericOpcode::G_CTPOP, *SourceType, *SourceType); + if (!PopulationSupported) + return std::unexpected(PopulationSupported.error()); + auto LeadingZeroSupported = IsSupported(GenericOpcode::G_CTLZ, *SourceType, *SourceType); + if (!LeadingZeroSupported) + return std::unexpected(LeadingZeroSupported.error()); + if (!*PopulationSupported && *LeadingZeroSupported) { + auto SizeConstant = + buildConstantRegister(MIRBuilder, *SourceType, static_cast<std::int64_t>(Size)); + if (!SizeConstant) + return std::unexpected(SizeConstant.error()); + auto LeadingZeros = + buildResultInstruction(MIRBuilder, GenericOpcode::G_CTLZ, *SourceType, {*Masked}); + if (!LeadingZeros) + return std::unexpected(LeadingZeros.error()); + if (auto Built = MIRBuilder.buildSub(*Destination, *SizeConstant, *LeadingZeros); !Built) + return std::unexpected(Built.error()); + return EraseOriginal(); + } + + Observer.changingInstr(Instruction); + if (auto Changed = Instruction.setOpcode(opcode(GenericOpcode::G_CTPOP)); !Changed) + return std::unexpected(Changed.error()); + if (auto Changed = SourceOperand->setRegister(*Masked); !Changed) + return std::unexpected(Changed.error()); + Observer.changedInstr(Instruction); + return {}; + } + + case GenericOpcode::G_CTPOP: { + const std::uint32_t Size = SourceType->scalarBitWidth(); + if (Size > 128 || Size % 8U != 0) + return makeUnexpected( + ErrorCode::Unsupported, + "population-count lowering requires a byte-sized scalar up to 128 bits"); + + auto One = buildConstantRegister(MIRBuilder, *SourceType, 1); + if (!One) + return std::unexpected(One.error()); + auto ShiftedByOne = + buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *SourceType, {*Source, *One}); + if (!ShiftedByOne) + return std::unexpected(ShiftedByOne.error()); + auto PairMaskValue = repeatedByte(Size, 0x55); + if (!PairMaskValue) + return std::unexpected(PairMaskValue.error()); + auto PairMask = buildConstantRegister(MIRBuilder, *SourceType, std::move(*PairMaskValue)); + if (!PairMask) + return std::unexpected(PairMask.error()); + auto HighPairBits = buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *SourceType, + {*ShiftedByOne, *PairMask}); + if (!HighPairBits) + return std::unexpected(HighPairBits.error()); + auto PairCounts = buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *SourceType, + {*Source, *HighPairBits}); + if (!PairCounts) + return std::unexpected(PairCounts.error()); + + auto Two = buildConstantRegister(MIRBuilder, *SourceType, 2); + if (!Two) + return std::unexpected(Two.error()); + auto ShiftedPairs = + buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *SourceType, {*PairCounts, *Two}); + if (!ShiftedPairs) + return std::unexpected(ShiftedPairs.error()); + auto NibbleMaskValue = repeatedByte(Size, 0x33); + if (!NibbleMaskValue) + return std::unexpected(NibbleMaskValue.error()); + auto NibbleMask = buildConstantRegister(MIRBuilder, *SourceType, std::move(*NibbleMaskValue)); + if (!NibbleMask) + return std::unexpected(NibbleMask.error()); + auto HighNibbleCounts = buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *SourceType, + {*ShiftedPairs, *NibbleMask}); + if (!HighNibbleCounts) + return std::unexpected(HighNibbleCounts.error()); + auto LowNibbleCounts = buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *SourceType, + {*PairCounts, *NibbleMask}); + if (!LowNibbleCounts) + return std::unexpected(LowNibbleCounts.error()); + auto NibbleCounts = buildResultInstruction(MIRBuilder, GenericOpcode::G_ADD, *SourceType, + {*HighNibbleCounts, *LowNibbleCounts}); + if (!NibbleCounts) + return std::unexpected(NibbleCounts.error()); + + auto Four = buildConstantRegister(MIRBuilder, *SourceType, 4); + if (!Four) + return std::unexpected(Four.error()); + auto ShiftedNibbles = buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *SourceType, + {*NibbleCounts, *Four}); + if (!ShiftedNibbles) + return std::unexpected(ShiftedNibbles.error()); + auto DirtyByteCounts = buildResultInstruction(MIRBuilder, GenericOpcode::G_ADD, *SourceType, + {*ShiftedNibbles, *NibbleCounts}); + if (!DirtyByteCounts) + return std::unexpected(DirtyByteCounts.error()); + auto ByteMaskValue = repeatedByte(Size, 0x0f); + if (!ByteMaskValue) + return std::unexpected(ByteMaskValue.error()); + auto ByteMask = buildConstantRegister(MIRBuilder, *SourceType, std::move(*ByteMaskValue)); + if (!ByteMask) + return std::unexpected(ByteMask.error()); + auto ByteCounts = buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *SourceType, + {*DirtyByteCounts, *ByteMask}); + if (!ByteCounts) + return std::unexpected(ByteCounts.error()); + + if (Size == 16) { + auto Eight = buildConstantRegister(MIRBuilder, *SourceType, 8); + if (!Eight) + return std::unexpected(Eight.error()); + auto HighByteCount = buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *SourceType, + {*ByteCounts, *Eight}); + if (!HighByteCount) + return std::unexpected(HighByteCount.error()); + auto Sum = buildResultInstruction(MIRBuilder, GenericOpcode::G_ADD, *SourceType, + {*ByteCounts, *HighByteCount}); + if (!Sum) + return std::unexpected(Sum.error()); + auto ResultMaskValue = APInt::fromUnsigned(Size, 0xff); + if (!ResultMaskValue) + return std::unexpected(ResultMaskValue.error()); + auto ResultMask = buildConstantRegister(MIRBuilder, *SourceType, std::move(*ResultMaskValue)); + if (!ResultMask) + return std::unexpected(ResultMask.error()); + if (auto Built = MIRBuilder.buildAnd(*Destination, *Sum, *ResultMask); !Built) + return std::unexpected(Built.error()); + return EraseOriginal(); + } + + auto MultiplyMaskValue = repeatedByte(Size, 0x01); + if (!MultiplyMaskValue) + return std::unexpected(MultiplyMaskValue.error()); + auto MultiplyMask = + buildConstantRegister(MIRBuilder, *SourceType, std::move(*MultiplyMaskValue)); + if (!MultiplyMask) + return std::unexpected(MultiplyMask.error()); + auto FinalShift = + buildConstantRegister(MIRBuilder, *SourceType, static_cast<std::int64_t>(Size - 8U)); + if (!FinalShift) + return std::unexpected(FinalShift.error()); + + auto MultiplyDecision = + Info.getDecision(LegalizationQuery(opcode(GenericOpcode::G_MUL), {*SourceType})); + if (!MultiplyDecision) + return std::unexpected(MultiplyDecision.error()); + const bool IsMultiplySupported = MultiplyDecision->Action == LegalizationAction::Legal || + MultiplyDecision->Action == LegalizationAction::WidenScalar || + MultiplyDecision->Action == LegalizationAction::Custom; + if (IsMultiplySupported) { + auto Product = buildResultInstruction(MIRBuilder, GenericOpcode::G_MUL, *SourceType, + {*ByteCounts, *MultiplyMask}); + if (!Product) + return std::unexpected(Product.error()); + if (auto Built = MIRBuilder.buildLShr(*Destination, *Product, *FinalShift); !Built) + return std::unexpected(Built.error()); + } else { + Register Sum = *ByteCounts; + for (std::uint32_t Shift = 8; Shift < Size; Shift *= 2U) { + auto ShiftAmount = + buildConstantRegister(MIRBuilder, *SourceType, static_cast<std::int64_t>(Shift)); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto Shifted = buildResultInstruction(MIRBuilder, GenericOpcode::G_SHL, *SourceType, + {Sum, *ShiftAmount}); + if (!Shifted) + return std::unexpected(Shifted.error()); + auto NewSum = + buildResultInstruction(MIRBuilder, GenericOpcode::G_ADD, *SourceType, {Sum, *Shifted}); + if (!NewSum) + return std::unexpected(NewSum.error()); + Sum = *NewSum; + } + if (auto Built = MIRBuilder.buildLShr(*Destination, Sum, *FinalShift); !Built) + return std::unexpected(Built.error()); + } + return EraseOriginal(); + } + + case GenericOpcode::G_CTLS: { + auto SignIndex = buildConstantRegister( + MIRBuilder, *SourceType, static_cast<std::int64_t>(SourceType->scalarBitWidth() - 1U)); + if (!SignIndex) + return std::unexpected(SignIndex.error()); + auto One = buildConstantRegister(MIRBuilder, *DestinationType, 1); + if (!One) + return std::unexpected(One.error()); + auto Sign = buildResultInstruction(MIRBuilder, GenericOpcode::G_ASHR, *SourceType, + {*Source, *SignIndex}); + if (!Sign) + return std::unexpected(Sign.error()); + auto DifferentBits = + buildResultInstruction(MIRBuilder, GenericOpcode::G_XOR, *SourceType, {*Source, *Sign}); + if (!DifferentBits) + return std::unexpected(DifferentBits.error()); + auto LeadingZeros = buildResultInstruction(MIRBuilder, GenericOpcode::G_CTLZ, *DestinationType, + {*DifferentBits}); + if (!LeadingZeros) + return std::unexpected(LeadingZeros.error()); + if (auto Built = MIRBuilder.buildSub(*Destination, *LeadingZeros, *One); !Built) + return std::unexpected(Built.error()); + return EraseOriginal(); + } + + default: + return makeUnexpected(ErrorCode::Unsupported, + "bit-count lowering received an unexpected opcode"); + } +} + +Expected<void> LegalizerHelper::lowerBswap(MachineInstr &Instruction) { + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Source = *Instruction.operandAt(1)->getIf<Register>(); + auto Type = MRI.getType(Source); + if (!Type) + return std::unexpected(Type.error()); + + const std::uint32_t SizeInBytes = (Type->scalarBitWidth() + 7U) / 8U; + const std::uint32_t BaseShiftAmount = (SizeInBytes - 1U) * 8U; + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto ShiftAmount = buildConstantRegister(MIRBuilder, *Type, BaseShiftAmount); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto LowByteShiftedLeft = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SHL, *Type, {Source, *ShiftAmount}); + auto HighByteShiftedRight = + buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *Type, {Source, *ShiftAmount}); + if (!LowByteShiftedLeft) + return std::unexpected(LowByteShiftedLeft.error()); + if (!HighByteShiftedRight) + return std::unexpected(HighByteShiftedRight.error()); + + Register Result; + if (SizeInBytes == 2U) { + if (auto Built = MIRBuilder.buildOr(Destination, *HighByteShiftedRight, *LowByteShiftedLeft); + !Built) { + return std::unexpected(Built.error()); + } + Result = Destination; + } else { + auto OuterBytes = buildResultInstruction(MIRBuilder, GenericOpcode::G_OR, *Type, + {*HighByteShiftedRight, *LowByteShiftedLeft}); + if (!OuterBytes) + return std::unexpected(OuterBytes.error()); + Result = *OuterBytes; + } + + for (std::uint32_t Index = 1; Index < SizeInBytes / 2U; ++Index) { + const std::uint32_t MaskBits = std::uint32_t{0xff} << (Index * 8U); + const std::int64_t MaskValue = static_cast<std::int32_t>(MaskBits); + auto Mask = buildConstantRegister(MIRBuilder, *Type, MaskValue); + auto InnerShiftAmount = buildConstantRegister(MIRBuilder, *Type, BaseShiftAmount - 16U * Index); + if (!Mask) + return std::unexpected(Mask.error()); + if (!InnerShiftAmount) + return std::unexpected(InnerShiftAmount.error()); + + auto LowByte = buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *Type, {Source, *Mask}); + if (!LowByte) + return std::unexpected(LowByte.error()); + auto LowByteShifted = buildResultInstruction(MIRBuilder, GenericOpcode::G_SHL, *Type, + {*LowByte, *InnerShiftAmount}); + if (!LowByteShifted) + return std::unexpected(LowByteShifted.error()); + auto WithLowByte = + buildResultInstruction(MIRBuilder, GenericOpcode::G_OR, *Type, {Result, *LowByteShifted}); + if (!WithLowByte) + return std::unexpected(WithLowByte.error()); + + auto SourceShifted = buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *Type, + {Source, *InnerShiftAmount}); + if (!SourceShifted) + return std::unexpected(SourceShifted.error()); + auto HighByte = + buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *Type, {*SourceShifted, *Mask}); + if (!HighByte) + return std::unexpected(HighByte.error()); + + if (Index + 1U == SizeInBytes / 2U) { + if (auto Built = MIRBuilder.buildOr(Destination, *WithLowByte, *HighByte); !Built) + return std::unexpected(Built.error()); + Result = Destination; + } else { + auto Combined = + buildResultInstruction(MIRBuilder, GenericOpcode::G_OR, *Type, {*WithLowByte, *HighByte}); + if (!Combined) + return std::unexpected(Combined.error()); + Result = *Combined; + } + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerFunnelShift(MachineInstr &Instruction) { + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Left = *Instruction.operandAt(1)->getIf<Register>(); + const Register Right = *Instruction.operandAt(2)->getIf<Register>(); + const Register Amount = *Instruction.operandAt(3)->getIf<Register>(); + auto DestinationType = MRI.getType(Destination); + auto AmountType = MRI.getType(Amount); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (!AmountType) + return std::unexpected(AmountType.error()); + + const std::uint32_t ElementWidth = DestinationType->scalarBitWidth(); + const bool IsLeft = Instruction.opcode() == opcode(GenericOpcode::G_FSHL); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + auto Mask = + buildConstantRegister(MIRBuilder, *AmountType, static_cast<std::int64_t>(ElementWidth - 1U)); + if (!Mask) + return std::unexpected(Mask.error()); + + Register ShiftAmount; + Register InverseShiftAmount; + if (std::has_single_bit(ElementWidth)) { + auto Shift = + buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *AmountType, {Amount, *Mask}); + auto AllOnes = buildConstantRegister(MIRBuilder, *AmountType, -1); + if (!Shift) + return std::unexpected(Shift.error()); + if (!AllOnes) + return std::unexpected(AllOnes.error()); + auto Inverted = + buildResultInstruction(MIRBuilder, GenericOpcode::G_XOR, *AmountType, {Amount, *AllOnes}); + if (!Inverted) + return std::unexpected(Inverted.error()); + auto Inverse = + buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *AmountType, {*Inverted, *Mask}); + if (!Inverse) + return std::unexpected(Inverse.error()); + ShiftAmount = *Shift; + InverseShiftAmount = *Inverse; + } else { + auto Width = + buildConstantRegister(MIRBuilder, *AmountType, static_cast<std::int64_t>(ElementWidth)); + if (!Width) + return std::unexpected(Width.error()); + auto Shift = + buildResultInstruction(MIRBuilder, GenericOpcode::G_UREM, *AmountType, {Amount, *Width}); + if (!Shift) + return std::unexpected(Shift.error()); + auto Inverse = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *AmountType, {*Mask, *Shift}); + if (!Inverse) + return std::unexpected(Inverse.error()); + ShiftAmount = *Shift; + InverseShiftAmount = *Inverse; + } + + auto One = buildConstantRegister(MIRBuilder, *AmountType, 1); + if (!One) + return std::unexpected(One.error()); + + Register ShiftedLeft; + Register ShiftedRight; + if (IsLeft) { + auto ShiftLeft = buildResultInstruction(MIRBuilder, GenericOpcode::G_SHL, *DestinationType, + {Left, ShiftAmount}); + if (!ShiftLeft) + return std::unexpected(ShiftLeft.error()); + auto FirstShiftRight = + buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *DestinationType, {Right, *One}); + if (!FirstShiftRight) + return std::unexpected(FirstShiftRight.error()); + auto ShiftRight = buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *DestinationType, + {*FirstShiftRight, InverseShiftAmount}); + if (!ShiftRight) + return std::unexpected(ShiftRight.error()); + ShiftedLeft = *ShiftLeft; + ShiftedRight = *ShiftRight; + } else { + auto FirstShiftLeft = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SHL, *DestinationType, {Left, *One}); + if (!FirstShiftLeft) + return std::unexpected(FirstShiftLeft.error()); + auto ShiftLeft = buildResultInstruction(MIRBuilder, GenericOpcode::G_SHL, *DestinationType, + {*FirstShiftLeft, InverseShiftAmount}); + if (!ShiftLeft) + return std::unexpected(ShiftLeft.error()); + auto ShiftRight = buildResultInstruction(MIRBuilder, GenericOpcode::G_LSHR, *DestinationType, + {Right, ShiftAmount}); + if (!ShiftRight) + return std::unexpected(ShiftRight.error()); + ShiftedLeft = *ShiftLeft; + ShiftedRight = *ShiftRight; + } + + auto Result = MIRBuilder.buildInstr(opcode(GenericOpcode::G_OR)); + if (!Result) + return std::unexpected(Result.error()); + if (auto Added = Result->addDef(Destination); !Added) + return std::unexpected(Added.error()); + if (auto Added = Result->addUse(ShiftedLeft); !Added) + return std::unexpected(Added.error()); + if (auto Added = Result->addUse(ShiftedRight); !Added) + return std::unexpected(Added.error()); + if (auto Flags = Result->setFlags({MachineInstrFlag::Disjoint}); !Flags) + return std::unexpected(Flags.error()); + if (auto Built = Result->build(); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerRotate(MachineInstr &Instruction) { + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Source = *Instruction.operandAt(1)->getIf<Register>(); + const Register Amount = *Instruction.operandAt(2)->getIf<Register>(); + auto DestinationType = MRI.getType(Destination); + auto AmountType = MRI.getType(Amount); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (!AmountType) + return std::unexpected(AmountType.error()); + + const std::uint32_t ElementWidth = DestinationType->scalarBitWidth(); + const bool IsLeft = Instruction.opcode() == opcode(GenericOpcode::G_ROTL); + const GenericOpcode ShiftOpcode = IsLeft ? GenericOpcode::G_SHL : GenericOpcode::G_LSHR; + const GenericOpcode ReverseShiftOpcode = IsLeft ? GenericOpcode::G_LSHR : GenericOpcode::G_SHL; + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto Zero = buildConstantRegister(MIRBuilder, *AmountType, 0); + auto WidthMinusOne = + buildConstantRegister(MIRBuilder, *AmountType, static_cast<std::int64_t>(ElementWidth - 1U)); + if (!Zero) + return std::unexpected(Zero.error()); + if (!WidthMinusOne) + return std::unexpected(WidthMinusOne.error()); + + Register Shifted; + Register ReverseShifted; + if (std::has_single_bit(ElementWidth)) { + auto NegativeAmount = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *AmountType, {*Zero, Amount}); + auto ShiftAmount = buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *AmountType, + {Amount, *WidthMinusOne}); + if (!NegativeAmount) + return std::unexpected(NegativeAmount.error()); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto Shift = + buildResultInstruction(MIRBuilder, ShiftOpcode, *DestinationType, {Source, *ShiftAmount}); + auto ReverseAmount = buildResultInstruction(MIRBuilder, GenericOpcode::G_AND, *AmountType, + {*NegativeAmount, *WidthMinusOne}); + if (!Shift) + return std::unexpected(Shift.error()); + if (!ReverseAmount) + return std::unexpected(ReverseAmount.error()); + auto ReverseShift = buildResultInstruction(MIRBuilder, ReverseShiftOpcode, *DestinationType, + {Source, *ReverseAmount}); + if (!ReverseShift) + return std::unexpected(ReverseShift.error()); + Shifted = *Shift; + ReverseShifted = *ReverseShift; + } else { + auto Width = + buildConstantRegister(MIRBuilder, *AmountType, static_cast<std::int64_t>(ElementWidth)); + if (!Width) + return std::unexpected(Width.error()); + auto ShiftAmount = + buildResultInstruction(MIRBuilder, GenericOpcode::G_UREM, *AmountType, {Amount, *Width}); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + auto Shift = + buildResultInstruction(MIRBuilder, ShiftOpcode, *DestinationType, {Source, *ShiftAmount}); + auto ReverseAmount = buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *AmountType, + {*WidthMinusOne, *ShiftAmount}); + auto One = buildConstantRegister(MIRBuilder, *AmountType, 1); + if (!Shift) + return std::unexpected(Shift.error()); + if (!ReverseAmount) + return std::unexpected(ReverseAmount.error()); + if (!One) + return std::unexpected(One.error()); + auto Inner = + buildResultInstruction(MIRBuilder, ReverseShiftOpcode, *DestinationType, {Source, *One}); + if (!Inner) + return std::unexpected(Inner.error()); + auto ReverseShift = buildResultInstruction(MIRBuilder, ReverseShiftOpcode, *DestinationType, + {*Inner, *ReverseAmount}); + if (!ReverseShift) + return std::unexpected(ReverseShift.error()); + Shifted = *Shift; + ReverseShifted = *ReverseShift; + } + + auto Result = MIRBuilder.buildInstr(opcode(GenericOpcode::G_OR)); + if (!Result) + return std::unexpected(Result.error()); + if (auto Added = Result->addDef(Destination); !Added) + return std::unexpected(Added.error()); + if (auto Added = Result->addUse(Shifted); !Added) + return std::unexpected(Added.error()); + if (auto Added = Result->addUse(ReverseShifted); !Added) + return std::unexpected(Added.error()); + if (auto Flags = Result->setFlags({MachineInstrFlag::Disjoint}); !Flags) + return std::unexpected(Flags.error()); + if (auto Built = Result->build(); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::libcall(MachineInstr &Instruction) { + if (Libcalls == nullptr || Libcalls->CallLowering == nullptr) { + return makeUnexpected(ErrorCode::Unsupported, + "libcall legalization requires target call lowering information"); + } + + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + auto *DestinationOperand = Instruction.operandAt(0); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + if (Destination == nullptr || !DestinationOperand->isDef()) { + return makeUnexpected(ErrorCode::VerificationError, "libcall requires a register destination"); + } + auto DestinationType = MRI.getType(*Destination); + if (!DestinationType) + return std::unexpected(DestinationType.error()); + if (!DestinationType->isCategoryNeutralScalar()) { + return makeUnexpected(ErrorCode::Unsupported, + "libcall requires a category-neutral scalar destination"); + } + + const bool IsConversion = + Generic == GenericOpcode::G_FPEXT || Generic == GenericOpcode::G_FPTRUNC || + Generic == GenericOpcode::G_FPTOSI || Generic == GenericOpcode::G_FPTOUI || + Generic == GenericOpcode::G_SITOFP || Generic == GenericOpcode::G_UITOFP; + const bool IsFloatingBinary = Generic == GenericOpcode::G_FREM; + if (Instruction.operandCount() < 2 || (IsConversion && Instruction.operandCount() != 2)) { + return makeUnexpected(ErrorCode::VerificationError, + "libcall has an invalid number of operands"); + } + + const MachineOperand *FirstSourceOperand = Instruction.operandAt(1); + const Register *FirstSource = + FirstSourceOperand == nullptr ? nullptr : FirstSourceOperand->getIf<Register>(); + if (FirstSource == nullptr || !FirstSourceOperand->isUse()) { + return makeUnexpected(ErrorCode::VerificationError, + "libcall requires register source operands"); + } + auto SourceType = MRI.getType(*FirstSource); + if (!SourceType) + return std::unexpected(SourceType.error()); + if (!SourceType->isCategoryNeutralScalar()) { + return makeUnexpected(ErrorCode::Unsupported, + "libcall requires category-neutral scalar sources"); + } + + Expected<std::string> Name = + IsConversion ? conversionLibcallName(Generic, DestinationType->scalarBitWidth(), + SourceType->scalarBitWidth()) + : IsFloatingBinary ? floatingBinaryLibcallName(Generic, DestinationType->scalarBitWidth()) + : integerLibcallName(Generic, DestinationType->scalarBitWidth()); + if (!Name) + return std::unexpected(Name.error()); + + const bool DestinationIsFloat = + Generic == GenericOpcode::G_FPEXT || Generic == GenericOpcode::G_FPTRUNC || + Generic == GenericOpcode::G_SITOFP || Generic == GenericOpcode::G_UITOFP || IsFloatingBinary; + const bool SourceIsFloat = + Generic == GenericOpcode::G_FPEXT || Generic == GenericOpcode::G_FPTRUNC || + Generic == GenericOpcode::G_FPTOSI || Generic == GenericOpcode::G_FPTOUI || IsFloatingBinary; + const ABIValueCategory DestinationCategory = + DestinationIsFloat ? ABIValueCategory::FloatingPoint : ABIValueCategory::Integer; + const ABIValueCategory SourceCategory = + SourceIsFloat ? ABIValueCategory::FloatingPoint : ABIValueCategory::Integer; + auto ExtensionFor = [&](ABIValueCategory Category, bool IsResult, + std::uint32_t BitWidth) noexcept { + if (Category != ABIValueCategory::Integer || BitWidth >= 64) + return ABIExtensionKind::None; + if (!IsConversion) + return integerLibcallExtension(Generic, BitWidth); + if ((IsResult && Generic == GenericOpcode::G_FPTOSI) || + (!IsResult && Generic == GenericOpcode::G_SITOFP)) { + return ABIExtensionKind::SignExtend; + } + return ABIExtensionKind::ZeroExtend; + }; + + auto FragmentType = LowLevelType::makeCategoryNeutralScalar(64); + if (!FragmentType) + return std::unexpected(FragmentType.error()); + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + auto AppendValue = [&](Register Value, LowLevelType Type, ABIValueCategory Category, + ABIExtensionKind Extension, std::uint32_t OriginalValue, bool IsResult, + std::vector<ABIValue> &Values, + std::vector<Register> *Fragments) -> Expected<void> { + const std::uint32_t BitWidth = Type.scalarBitWidth(); + auto OriginalAlignment = Alignment::create((BitWidth + 7U) / 8U); + if (!OriginalAlignment) + return std::unexpected(OriginalAlignment.error()); + if (BitWidth != 128) { + auto ABI = libcallABIValue(Value, Type, Category, OriginalValue, 0, 1, 0, *OriginalAlignment, + Extension); + if (!ABI) + return std::unexpected(ABI.error()); + Values.push_back(*ABI); + if (Fragments != nullptr) + Fragments->push_back(Value); + return {}; + } + + std::vector<Register> PartRegisters; + PartRegisters.reserve(2); + for (std::size_t Index = 0; Index != 2; ++Index) { + auto Part = MRI.createVirtualRegister(*FragmentType); + if (!Part) + return std::unexpected(Part.error()); + PartRegisters.push_back(*Part); + } + if (!IsResult) { + auto Unmerged = MIRBuilder.buildUnmergeValues(PartRegisters, Value); + if (!Unmerged) + return std::unexpected(Unmerged.error()); + } + for (std::uint16_t Index = 0; Index != 2; ++Index) { + auto ABI = libcallABIValue(PartRegisters[Index], *FragmentType, ABIValueCategory::Integer, + OriginalValue, Index, 2, static_cast<std::uint32_t>(Index) * 8U, + *OriginalAlignment, ABIExtensionKind::None); + if (!ABI) + return std::unexpected(ABI.error()); + Values.push_back(*ABI); + } + if (Fragments != nullptr) + *Fragments = std::move(PartRegisters); + return {}; + }; + + std::vector<ABIValue> Arguments; + Arguments.reserve((Instruction.operandCount() - 1U) * + (SourceType->scalarBitWidth() == 128 ? 2U : 1U)); + std::uint32_t OriginalArgument = 0; + for (std::size_t Index = 1; Index != Instruction.operandCount(); ++Index) { + const MachineOperand *Operand = Instruction.operandAt(Index); + const Register *Source = Operand == nullptr ? nullptr : Operand->getIf<Register>(); + if (Source == nullptr || !Operand->isUse()) { + return makeUnexpected(ErrorCode::VerificationError, + "libcall requires register source operands"); + } + auto ArgumentType = MRI.getType(*Source); + if (!ArgumentType) + return std::unexpected(ArgumentType.error()); + if (*ArgumentType != *SourceType) { + return makeUnexpected(ErrorCode::VerificationError, "libcall source types must match"); + } + if (!IsConversion && *ArgumentType != *DestinationType) { + return makeUnexpected(ErrorCode::VerificationError, + "binary libcall source and destination types must match"); + } + const ABIExtensionKind Extension = + ExtensionFor(SourceCategory, false, ArgumentType->scalarBitWidth()); + if (auto Appended = AppendValue(*Source, *ArgumentType, SourceCategory, Extension, + OriginalArgument++, false, Arguments, nullptr); + !Appended) { + return std::unexpected(Appended.error()); + } + } + + std::vector<ABIValue> Results; + std::vector<Register> ResultFragments; + const ABIExtensionKind ResultExtension = + ExtensionFor(DestinationCategory, true, DestinationType->scalarBitWidth()); + if (auto Appended = AppendValue(*Destination, *DestinationType, DestinationCategory, + ResultExtension, 0, true, Results, &ResultFragments); + !Appended) { + return std::unexpected(Appended.error()); + } + + auto Callee = MachineCallTarget::externalSymbol(std::move(*Name)); + if (!Callee) + return std::unexpected(Callee.error()); + CallLoweringRequest Request; + Request.Convention = Libcalls->Convention; + Request.Callee = std::move(*Callee); + Request.Arguments = std::move(Arguments); + Request.Results = std::move(Results); + Request.FixedArgumentCount = OriginalArgument; + if (auto Lowered = Libcalls->CallLowering->lowerCall(MIRBuilder, Request); !Lowered) + return std::unexpected(Lowered.error()); + + if (DestinationType->scalarBitWidth() == 128) { + auto Merged = MIRBuilder.buildMergeLikeInstr(*Destination, ResultFragments); + if (!Merged) + return std::unexpected(Merged.error()); + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerSignedAddSubOverflow(MachineInstr &Instruction) { + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + if (Generic == GenericOpcode::G_SADDE || Generic == GenericOpcode::G_SSUBE) { + if (Instruction.operandCount() != 5) + return makeUnexpected(ErrorCode::VerificationError, + "signed extended add/sub lowering requires five register operands"); + + const bool IsAdd = Generic == GenericOpcode::G_SADDE; + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *OverflowOperand = Instruction.operandAt(1); + const MachineOperand *LeftOperand = Instruction.operandAt(2); + const MachineOperand *RightOperand = Instruction.operandAt(3); + const MachineOperand *CarryInOperand = Instruction.operandAt(4); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Overflow = + OverflowOperand == nullptr ? nullptr : OverflowOperand->getIf<Register>(); + const Register *Left = LeftOperand == nullptr ? nullptr : LeftOperand->getIf<Register>(); + const Register *Right = RightOperand == nullptr ? nullptr : RightOperand->getIf<Register>(); + const Register *CarryIn = + CarryInOperand == nullptr ? nullptr : CarryInOperand->getIf<Register>(); + if (Destination == nullptr || Overflow == nullptr || Left == nullptr || Right == nullptr || + CarryIn == nullptr) { + return makeUnexpected(ErrorCode::VerificationError, + "signed extended add/sub lowering requires register operands"); + } + + auto Type = MRI.getType(*Destination); + if (!Type) + return std::unexpected(Type.error()); + auto Result = MRI.cloneVirtualRegister(*Destination); + auto ExtendedCarry = MRI.createVirtualRegister(*Type); + auto FirstXor = MRI.createVirtualRegister(*Type); + auto SecondXor = MRI.createVirtualRegister(*Type); + auto OverflowBits = MRI.createVirtualRegister(*Type); + if (!Result) + return std::unexpected(Result.error()); + if (!ExtendedCarry) + return std::unexpected(ExtendedCarry.error()); + if (!FirstXor) + return std::unexpected(FirstXor.error()); + if (!SecondXor) + return std::unexpected(SecondXor.error()); + if (!OverflowBits) + return std::unexpected(OverflowBits.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + if (IsAdd) { + auto Temporary = MRI.createVirtualRegister(*Type); + if (!Temporary) + return std::unexpected(Temporary.error()); + if (auto Built = MIRBuilder.buildAdd(*Temporary, *Left, *Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildZExt(*ExtendedCarry, *CarryIn); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildAdd(*Result, *Temporary, *ExtendedCarry); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildCopy(*Destination, *Result); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildXor(*FirstXor, *Result, *Left); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildXor(*SecondXor, *Result, *Right); !Built) + return std::unexpected(Built.error()); + } else { + auto RightWithCarry = MRI.createVirtualRegister(*Type); + if (!RightWithCarry) + return std::unexpected(RightWithCarry.error()); + if (auto Built = MIRBuilder.buildZExt(*ExtendedCarry, *CarryIn); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildAdd(*RightWithCarry, *Right, *ExtendedCarry); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildSub(*Result, *Left, *RightWithCarry); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildCopy(*Destination, *Result); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildXor(*FirstXor, *Left, *Right); !Built) + return std::unexpected(Built.error()); + if (auto Built = MIRBuilder.buildXor(*SecondXor, *Left, *Result); !Built) + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildAnd(*OverflowBits, *FirstXor, *SecondXor); !Built) + return std::unexpected(Built.error()); + auto Zero = buildConstantRegister(MIRBuilder, *Type, 0); + if (!Zero) + return std::unexpected(Zero.error()); + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::SignedLessThan, *Overflow, *OverflowBits, *Zero); + !Built) { + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); + } + + const bool IsAdd = Instruction.opcode() == opcode(GenericOpcode::G_SADDO); + const Register Destination = *Instruction.operandAt(0)->getIf<Register>(); + const Register Overflow = *Instruction.operandAt(1)->getIf<Register>(); + const Register Left = *Instruction.operandAt(2)->getIf<Register>(); + const Register Right = *Instruction.operandAt(3)->getIf<Register>(); + auto Type = MRI.getType(Destination); + auto BooleanType = MRI.getType(Overflow); + if (!Type) + return std::unexpected(Type.error()); + if (!BooleanType) + return std::unexpected(BooleanType.error()); + + auto Result = MRI.cloneVirtualRegister(Destination); + auto FirstComparison = MRI.createVirtualRegister(*BooleanType); + auto SecondComparison = MRI.createVirtualRegister(*BooleanType); + if (!Result) + return std::unexpected(Result.error()); + if (!FirstComparison) + return std::unexpected(FirstComparison.error()); + if (!SecondComparison) + return std::unexpected(SecondComparison.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = IsAdd ? MIRBuilder.buildAdd(*Result, Left, Right) + : MIRBuilder.buildSub(*Result, Left, Right); + !Built) { + return std::unexpected(Built.error()); + } + auto Zero = buildConstantRegister(MIRBuilder, *Type, 0); + if (!Zero) + return std::unexpected(Zero.error()); + if (IsAdd) { + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::SignedLessThan, *FirstComparison, *Result, Left); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::SignedLessThan, *SecondComparison, Right, *Zero); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildXor(Overflow, *SecondComparison, *FirstComparison); !Built) + return std::unexpected(Built.error()); + } else { + if (auto Built = + MIRBuilder.buildICmp(IntegerPredicate::SignedLessThan, *FirstComparison, Left, Right); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildICmp(IntegerPredicate::SignedLessThan, *SecondComparison, + *Result, *Zero); + !Built) { + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildXor(Overflow, *FirstComparison, *SecondComparison); !Built) + return std::unexpected(Built.error()); + } + if (auto Built = MIRBuilder.buildCopy(Destination, *Result); !Built) + return std::unexpected(Built.error()); + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerSaturatingAddSubToMinMax(MachineInstr &Instruction) { + if (Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::VerificationError, + "saturating add/sub min/max lowering requires three register operands"); + + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + const bool IsSigned = Generic == GenericOpcode::G_SADDSAT || Generic == GenericOpcode::G_SSUBSAT; + const bool IsAdd = Generic == GenericOpcode::G_UADDSAT || Generic == GenericOpcode::G_SADDSAT; + const GenericOpcode ArithmeticOpcode = IsAdd ? GenericOpcode::G_ADD : GenericOpcode::G_SUB; + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *LeftOperand = Instruction.operandAt(1); + const MachineOperand *RightOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Left = LeftOperand == nullptr ? nullptr : LeftOperand->getIf<Register>(); + const Register *Right = RightOperand == nullptr ? nullptr : RightOperand->getIf<Register>(); + if (Destination == nullptr || Left == nullptr || Right == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "saturating add/sub min/max lowering requires register operands"); + + auto Type = MRI.getType(*Destination); + if (!Type) + return std::unexpected(Type.error()); + if (!Type->isCategoryNeutralScalar() || Type->scalarBitWidth() == 0) + return makeUnexpected(ErrorCode::Unsupported, + "saturating add/sub min/max lowering requires a scalar integer type"); + const std::uint32_t BitWidth = Type->scalarBitWidth(); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (IsSigned) { + auto MaximumValue = APInt::getLowBitsSet(BitWidth, BitWidth - 1U); + auto MinimumValue = oneBitSet(BitWidth, BitWidth - 1U); + if (!MaximumValue) + return std::unexpected(MaximumValue.error()); + if (!MinimumValue) + return std::unexpected(MinimumValue.error()); + auto Maximum = buildConstantRegister(MIRBuilder, *Type, std::move(*MaximumValue)); + auto Minimum = buildConstantRegister(MIRBuilder, *Type, std::move(*MinimumValue)); + if (!Maximum) + return std::unexpected(Maximum.error()); + if (!Minimum) + return std::unexpected(Minimum.error()); + + Register LowerBound; + Register UpperBound; + if (IsAdd) { + auto Zero = buildConstantRegister(MIRBuilder, *Type, 0); + if (!Zero) + return std::unexpected(Zero.error()); + auto LeftMaximum = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SMAX, *Type, {*Left, *Zero}); + if (!LeftMaximum) + return std::unexpected(LeftMaximum.error()); + auto High = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *Type, {*Maximum, *LeftMaximum}); + if (!High) + return std::unexpected(High.error()); + auto LeftMinimum = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SMIN, *Type, {*Left, *Zero}); + if (!LeftMinimum) + return std::unexpected(LeftMinimum.error()); + auto Low = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *Type, {*Minimum, *LeftMinimum}); + if (!Low) + return std::unexpected(Low.error()); + LowerBound = *Low; + UpperBound = *High; + } else { + auto NegativeOne = buildConstantRegister(MIRBuilder, *Type, -1); + if (!NegativeOne) + return std::unexpected(NegativeOne.error()); + auto LeftMaximum = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SMAX, *Type, {*Left, *NegativeOne}); + if (!LeftMaximum) + return std::unexpected(LeftMaximum.error()); + auto Low = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *Type, {*LeftMaximum, *Maximum}); + if (!Low) + return std::unexpected(Low.error()); + auto LeftMinimum = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SMIN, *Type, {*Left, *NegativeOne}); + if (!LeftMinimum) + return std::unexpected(LeftMinimum.error()); + auto High = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SUB, *Type, {*LeftMinimum, *Minimum}); + if (!High) + return std::unexpected(High.error()); + LowerBound = *Low; + UpperBound = *High; + } + + auto LowerClamped = + buildResultInstruction(MIRBuilder, GenericOpcode::G_SMAX, *Type, {LowerBound, *Right}); + if (!LowerClamped) + return std::unexpected(LowerClamped.error()); + auto ClampedRight = buildResultInstruction(MIRBuilder, GenericOpcode::G_SMIN, *Type, + {*LowerClamped, UpperBound}); + if (!ClampedRight) + return std::unexpected(ClampedRight.error()); + if (auto Built = + MIRBuilder.buildInstr(opcode(ArithmeticOpcode), {*Destination}, {*Left, *ClampedRight}); + !Built) { + return std::unexpected(Built.error()); + } + } else { + Register Available; + if (IsAdd) { + auto AllBits = APInt::getLowBitsSet(BitWidth, BitWidth); + if (!AllBits) + return std::unexpected(AllBits.error()); + auto AllOnes = buildConstantRegister(MIRBuilder, *Type, std::move(*AllBits)); + if (!AllOnes) + return std::unexpected(AllOnes.error()); + auto Inverted = + buildResultInstruction(MIRBuilder, GenericOpcode::G_XOR, *Type, {*Left, *AllOnes}); + if (!Inverted) + return std::unexpected(Inverted.error()); + Available = *Inverted; + } else { + Available = *Left; + } + auto Minimum = + buildResultInstruction(MIRBuilder, GenericOpcode::G_UMIN, *Type, {Available, *Right}); + if (!Minimum) + return std::unexpected(Minimum.error()); + if (auto Built = + MIRBuilder.buildInstr(opcode(ArithmeticOpcode), {*Destination}, {*Left, *Minimum}); + !Built) { + return std::unexpected(Built.error()); + } + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerSaturatingAddSubToOverflow(MachineInstr &Instruction) { + if (Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::VerificationError, + "saturating add/sub overflow lowering requires three register operands"); + + const GenericOpcode Generic = static_cast<GenericOpcode>(Instruction.opcode().value()); + const bool IsSigned = Generic == GenericOpcode::G_SADDSAT || Generic == GenericOpcode::G_SSUBSAT; + const bool IsAdd = Generic == GenericOpcode::G_UADDSAT || Generic == GenericOpcode::G_SADDSAT; + const GenericOpcode OverflowOpcode = + IsAdd ? (IsSigned ? GenericOpcode::G_SADDO : GenericOpcode::G_UADDO) + : (IsSigned ? GenericOpcode::G_SSUBO : GenericOpcode::G_USUBO); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *LeftOperand = Instruction.operandAt(1); + const MachineOperand *RightOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Left = LeftOperand == nullptr ? nullptr : LeftOperand->getIf<Register>(); + const Register *Right = RightOperand == nullptr ? nullptr : RightOperand->getIf<Register>(); + if (Destination == nullptr || Left == nullptr || Right == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "saturating add/sub overflow lowering requires register operands"); + + auto Type = MRI.getType(*Destination); + if (!Type) + return std::unexpected(Type.error()); + if (!Type->isCategoryNeutralScalar() || Type->scalarBitWidth() == 0) + return makeUnexpected(ErrorCode::Unsupported, + "saturating add/sub overflow lowering requires a scalar integer type"); + auto BooleanType = booleanTypeFor(*Type); + if (!BooleanType) + return std::unexpected(BooleanType.error()); + auto Temporary = MRI.createVirtualRegister(*Type); + auto Overflow = MRI.createVirtualRegister(*BooleanType); + if (!Temporary) + return std::unexpected(Temporary.error()); + if (!Overflow) + return std::unexpected(Overflow.error()); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + if (auto Built = + MIRBuilder.buildInstr(opcode(OverflowOpcode), {*Temporary, *Overflow}, {*Left, *Right}); + !Built) { + return std::unexpected(Built.error()); + } + + Register Clamp; + if (IsSigned) { + const std::uint32_t BitWidth = Type->scalarBitWidth(); + auto ShiftAmount = + buildConstantRegister(MIRBuilder, *Type, static_cast<std::int64_t>(BitWidth - 1U)); + auto MinimumValue = oneBitSet(BitWidth, BitWidth - 1U); + if (!ShiftAmount) + return std::unexpected(ShiftAmount.error()); + if (!MinimumValue) + return std::unexpected(MinimumValue.error()); + auto Sign = buildResultInstruction(MIRBuilder, GenericOpcode::G_ASHR, *Type, + {*Temporary, *ShiftAmount}); + auto Minimum = buildConstantRegister(MIRBuilder, *Type, std::move(*MinimumValue)); + if (!Sign) + return std::unexpected(Sign.error()); + if (!Minimum) + return std::unexpected(Minimum.error()); + auto Saturated = + buildResultInstruction(MIRBuilder, GenericOpcode::G_ADD, *Type, {*Sign, *Minimum}); + if (!Saturated) + return std::unexpected(Saturated.error()); + Clamp = *Saturated; + } else if (IsAdd) { + auto AllBits = APInt::getLowBitsSet(Type->scalarBitWidth(), Type->scalarBitWidth()); + if (!AllBits) + return std::unexpected(AllBits.error()); + auto Saturated = buildConstantRegister(MIRBuilder, *Type, std::move(*AllBits)); + if (!Saturated) + return std::unexpected(Saturated.error()); + Clamp = *Saturated; + } else { + auto Saturated = buildConstantRegister(MIRBuilder, *Type, 0); + if (!Saturated) + return std::unexpected(Saturated.error()); + Clamp = *Saturated; + } + + if (auto Built = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {*Destination}, + {*Overflow, Clamp, *Temporary}); + !Built) { + return std::unexpected(Built.error()); + } + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::lowerSaturatingShiftLeft(MachineInstr &Instruction) { + if (Instruction.operandCount() != 3) + return makeUnexpected(ErrorCode::VerificationError, + "saturating shift-left lowering requires three register operands"); + + const bool IsSigned = Instruction.opcode() == opcode(GenericOpcode::G_SSHLSAT); + const MachineOperand *DestinationOperand = Instruction.operandAt(0); + const MachineOperand *LeftOperand = Instruction.operandAt(1); + const MachineOperand *RightOperand = Instruction.operandAt(2); + const Register *Destination = + DestinationOperand == nullptr ? nullptr : DestinationOperand->getIf<Register>(); + const Register *Left = LeftOperand == nullptr ? nullptr : LeftOperand->getIf<Register>(); + const Register *Right = RightOperand == nullptr ? nullptr : RightOperand->getIf<Register>(); + if (Destination == nullptr || Left == nullptr || Right == nullptr) + return makeUnexpected(ErrorCode::VerificationError, + "saturating shift-left lowering requires register operands"); + + auto Type = MRI.getType(*Destination); + if (!Type) + return std::unexpected(Type.error()); + if (!Type->isCategoryNeutralScalar() || Type->scalarBitWidth() == 0) + return makeUnexpected(ErrorCode::Unsupported, + "saturating shift-left lowering requires a scalar integer type"); + auto BooleanType = booleanTypeFor(*Type); + if (!BooleanType) + return std::unexpected(BooleanType.error()); + const std::uint32_t BitWidth = Type->scalarBitWidth(); + + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + auto Shifted = buildResultInstruction(MIRBuilder, GenericOpcode::G_SHL, *Type, {*Left, *Right}); + if (!Shifted) + return std::unexpected(Shifted.error()); + auto Restored = + buildResultInstruction(MIRBuilder, IsSigned ? GenericOpcode::G_ASHR : GenericOpcode::G_LSHR, + *Type, {*Shifted, *Right}); + if (!Restored) + return std::unexpected(Restored.error()); + + Register Saturated; + if (IsSigned) { + auto MinimumValue = oneBitSet(BitWidth, BitWidth - 1U); + auto MaximumValue = APInt::getLowBitsSet(BitWidth, BitWidth - 1U); + if (!MinimumValue) + return std::unexpected(MinimumValue.error()); + if (!MaximumValue) + return std::unexpected(MaximumValue.error()); + auto Minimum = buildConstantRegister(MIRBuilder, *Type, std::move(*MinimumValue)); + auto Maximum = buildConstantRegister(MIRBuilder, *Type, std::move(*MaximumValue)); + auto Zero = buildConstantRegister(MIRBuilder, *Type, 0); + if (!Minimum) + return std::unexpected(Minimum.error()); + if (!Maximum) + return std::unexpected(Maximum.error()); + if (!Zero) + return std::unexpected(Zero.error()); + auto Negative = + buildICmpRegister(MIRBuilder, IntegerPredicate::SignedLessThan, *BooleanType, *Left, *Zero); + if (!Negative) + return std::unexpected(Negative.error()); + auto SignSaturated = buildResultInstruction(MIRBuilder, GenericOpcode::G_SELECT, *Type, + {*Negative, *Minimum, *Maximum}); + if (!SignSaturated) + return std::unexpected(SignSaturated.error()); + Saturated = *SignSaturated; + } else { + auto AllBits = APInt::getLowBitsSet(BitWidth, BitWidth); + if (!AllBits) + return std::unexpected(AllBits.error()); + auto Maximum = buildConstantRegister(MIRBuilder, *Type, std::move(*AllBits)); + if (!Maximum) + return std::unexpected(Maximum.error()); + Saturated = *Maximum; + } + + auto Overflow = + buildICmpRegister(MIRBuilder, IntegerPredicate::NotEqual, *BooleanType, *Left, *Restored); + if (!Overflow) + return std::unexpected(Overflow.error()); + if (auto Built = MIRBuilder.buildInstr(opcode(GenericOpcode::G_SELECT), {*Destination}, + {*Overflow, Saturated, *Shifted}); + !Built) { + return std::unexpected(Built.error()); + } + + return Instruction.eraseFromParent(); +} + +Expected<void> LegalizerHelper::legalize(MachineInstr &Instruction, + const LegalizationDecision &Decision) { + if (auto Point = MIRBuilder.setInsertPointBefore(Instruction); !Point) + return std::unexpected(Point.error()); + + switch (Decision.Action) { + case LegalizationAction::WidenScalar: + return widenScalar(Instruction, Decision.TypeIndex, Decision.NewType); + case LegalizationAction::NarrowScalar: + return narrowScalar(Instruction, Decision.TypeIndex, Decision.NewType); + case LegalizationAction::Custom: + return Info.legalizeCustom(*this, Instruction, Decision); + case LegalizationAction::Lower: + return lower(Instruction, Decision); + case LegalizationAction::Libcall: + return libcall(Instruction); + case LegalizationAction::FewerElements: + return fewerElements(Instruction, Decision.TypeIndex, Decision.NewType); + case LegalizationAction::Legal: + return {}; + case LegalizationAction::Unsupported: + case LegalizationAction::MoreElements: + case LegalizationAction::Bitcast: + case LegalizationAction::ChangeElementType: + case LegalizationAction::ChangeAddressSpace: + return makeUnexpected(ErrorCode::Unsupported, + "legalizer helper does not implement the requested action"); + } + return makeUnexpected(ErrorCode::InternalError, "unknown legalization action"); +} + +} // namespace rcc |
