[llvm-branch-commits] [llvm] [5/7][PISA] Add PISAInstructionSelector (PR #214378)

Michal Paszkowski via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Aug 5 17:48:38 PDT 2026


https://github.com/michalpaszkowski created https://github.com/llvm/llvm-project/pull/214378

Add the PISAInstructionSelector, the final Subtarget/TargetMachine wiring, and selection tests.

>From 21fcdd875d39f5f1ed9f37a231b70965e951e83a Mon Sep 17 00:00:00 2001
From: Michal Paszkowski <michal.paszkowski at intel.com>
Date: Mon, 3 Aug 2026 03:26:39 -0700
Subject: [PATCH] Add PISAInstructionSelector

Add the PISAInstructionSelector, the final Subtarget/TargetMachine
wiring, and selection tests.
---
 llvm/lib/Target/PISA/CMakeLists.txt           |    1 +
 .../Target/PISA/PISAInstructionSelector.cpp   | 1794 +++++++++++++++++
 llvm/lib/Target/PISA/PISASubtarget.cpp        |    3 +-
 llvm/lib/Target/PISA/PISATargetMachine.cpp    |   10 +-
 .../inst-select-build-vector-v64.mir          |   24 +
 .../inst-select-constant-float-type.mir       |   36 +
 .../PISA/GlobalISel/inst-select-ctlz.mir      |  103 +
 .../PISA/GlobalISel/inst-select-cttz.mir      |  103 +
 .../GlobalISel/inst-select-debug-trace.mir    |   41 +
 9 files changed, 2112 insertions(+), 3 deletions(-)
 create mode 100644 llvm/lib/Target/PISA/PISAInstructionSelector.cpp
 create mode 100644 llvm/test/CodeGen/PISA/GlobalISel/inst-select-build-vector-v64.mir
 create mode 100644 llvm/test/CodeGen/PISA/GlobalISel/inst-select-constant-float-type.mir
 create mode 100644 llvm/test/CodeGen/PISA/GlobalISel/inst-select-ctlz.mir
 create mode 100644 llvm/test/CodeGen/PISA/GlobalISel/inst-select-cttz.mir
 create mode 100644 llvm/test/CodeGen/PISA/GlobalISel/inst-select-debug-trace.mir

diff --git a/llvm/lib/Target/PISA/CMakeLists.txt b/llvm/lib/Target/PISA/CMakeLists.txt
index 0a666058c14d2..a51c8cfafc728 100644
--- a/llvm/lib/Target/PISA/CMakeLists.txt
+++ b/llvm/lib/Target/PISA/CMakeLists.txt
@@ -21,6 +21,7 @@ add_llvm_target(PISACodeGen
   PISACallLowering.cpp
   PISAISelLowering.cpp
   PISAInstrInfo.cpp
+  PISAInstructionSelector.cpp
   PISALegalizerInfo.cpp
   PISAMCInstLower.cpp
   PISAMachineFunctionInfo.cpp
diff --git a/llvm/lib/Target/PISA/PISAInstructionSelector.cpp b/llvm/lib/Target/PISA/PISAInstructionSelector.cpp
new file mode 100644
index 0000000000000..178d24ff75fe3
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAInstructionSelector.cpp
@@ -0,0 +1,1794 @@
+//===-- PISAInstructionSelector.cpp ---------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "MCTargetDesc/PISAEnum.h"
+#include "PISA.h"
+#include "PISAGenInstrInfo.inc"
+#include "PISAInstrInfo.h"
+#include "PISARegisterBankInfo.h"
+#include "PISARegisterInfo.h"
+#include "PISATargetMachine.h"
+#include "PISAUtils.h"
+
+#include "llvm/ADT/APFloat.h"
+#include "llvm/ADT/APInt.h"
+#include "llvm/ADT/FloatingPointMode.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include "llvm/ADT/StringTable.h"
+#include "llvm/CodeGen/GlobalISel/GIMatchTableExecutorImpl.h"
+#include "llvm/CodeGen/GlobalISel/GenericMachineInstrs.h"
+#include "llvm/CodeGen/GlobalISel/InstructionSelector.h"
+#include "llvm/CodeGen/GlobalISel/MIPatternMatch.h"
+#include "llvm/CodeGen/MachineFrameInfo.h"
+#include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/CodeGen/MachineRegisterInfo.h"
+#include "llvm/IR/IntrinsicsPISA.h"
+#include "llvm/IR/PISAIntrinsicUtils.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/PISAAddrSpace.h"
+
+namespace llvm {
+namespace PISA {
+struct Name2InstrEntry {
+  StringTable::Offset Name;
+  unsigned Opcode;
+};
+
+#define GET_Name2InstrOpTable_DECL
+#define GET_Name2InstrOpTable_IMPL
+#include "PISAGenSearchableTables.inc"
+
+} // namespace PISA
+} // namespace llvm
+
+#define DEBUG_TYPE "pisa-isel"
+
+using namespace llvm;
+using namespace MIPatternMatch;
+
+namespace {
+
+struct SyncScopeInfo {
+  static const StringMap<unsigned> Name2Opcode;
+  DenseMap<llvm::SyncScope::ID, unsigned> ID2Opcode;
+  SyncScope::ID SystemGenericID = SyncScope::System;
+
+  SyncScopeInfo() {}
+  SyncScopeInfo(llvm::LLVMContext &Context) {
+    for (const auto &[Name, Opcode] : Name2Opcode) {
+      auto ID = Context.getOrInsertSyncScopeID(Name);
+      ID2Opcode.emplace_or_assign(ID, Opcode);
+    }
+    ID2Opcode.emplace_or_assign(SyncScope::SingleThread, PISA::fence_subgroup);
+    ID2Opcode.emplace_or_assign(SyncScope::System, PISA::fence_global_system);
+    SystemGenericID = Context.getOrInsertSyncScopeID("system-generic");
+  }
+};
+
+const StringMap<unsigned> SyncScopeInfo::Name2Opcode = {
+    {"workitem", PISA::fence_subgroup},
+    {"workitem-shared", PISA::fence_subgroup},
+    {"workitem-global", PISA::fence_subgroup},
+    {"workitem-generic", PISA::fence_subgroup},
+    {"subgroup", PISA::fence_subgroup},
+    {"subgroup-shared", PISA::fence_subgroup},
+    {"subgroup-global", PISA::fence_subgroup},
+    {"subgroup-generic", PISA::fence_subgroup},
+    {"workgroup", PISA::fence_generic_workgroup},
+    {"workgroup-shared", PISA::fence_shared_workgroup},
+    {"workgroup-global", PISA::fence_global_workgroup},
+    {"workgroup-generic", PISA::fence_generic_workgroup},
+    {"gpu", PISA::fence_generic_gpu},
+    {"gpu-shared", PISA::fence_shared_gpu},
+    {"gpu-global", PISA::fence_global_gpu},
+    {"gpu-generic", PISA::fence_generic_gpu},
+    {"system", PISA::fence_global_system},
+    {"system-shared", PISA::fence_shared_gpu},
+    {"system-global", PISA::fence_global_system},
+};
+
+#define GET_GLOBALISEL_PREDICATE_BITSET
+#include "PISAGenGlobalISel.inc"
+#undef GET_GLOBALISEL_PREDICATE_BITSET
+
+class PISAInstructionSelector : public InstructionSelector {
+  const PISASubtarget &STI;
+  const PISAInstrInfo &TII;
+  const PISARegisterInfo &TRI;
+  const RegisterBankInfo &RBI;
+  MachineRegisterInfo *MRI = nullptr;
+  SyncScopeInfo SSI;
+  MachineFrameInfo *MFI = nullptr;
+
+public:
+  PISAInstructionSelector(const PISATargetMachine &TM, const PISASubtarget &ST,
+                          const RegisterBankInfo &RBI);
+  void setupMF(MachineFunction &MF, GISelValueTracking *KB,
+               CodeGenCoverage *CoverageInfo, ProfileSummaryInfo *PSI,
+               BlockFrequencyInfo *BFI) override;
+  bool select(MachineInstr &I) override;
+  static const char *getName() { return DEBUG_TYPE; }
+
+#define GET_GLOBALISEL_PREDICATES_DECL
+#include "PISAGenGlobalISel.inc"
+#undef GET_GLOBALISEL_PREDICATES_DECL
+
+#define GET_GLOBALISEL_TEMPORARIES_DECL
+#include "PISAGenGlobalISel.inc"
+#undef GET_GLOBALISEL_TEMPORARIES_DECL
+
+private:
+  // tblgen-erated 'select' implementation, used as the initial selector for
+  // the patterns that don't require complex C++.
+  bool selectImpl(MachineInstr &I, CodeGenCoverage &CoverageInfo) const;
+  bool selectFrameIndex(MachineInstr &I) const;
+  bool selectGlobalValue(MachineInstr &I) const;
+  bool selectPhiOrImplicitDef(MachineInstr &I) const;
+  bool selectAddSubWithOverflow(MachineInstr &I) const;
+  bool selectSignedAddSubWithOverflow(MachineInstr &I) const;
+  bool selectBitcast(MachineInstr &I) const;
+  // NOLINTBEGIN(readability-identifier-naming)
+  bool selectG_BUILD_VECTOR(MachineInstr &I) const;
+  bool selectConvPtrInt(MachineInstr &I) const;
+  bool selectCopy(MachineInstr &I) const;
+  bool selectG_INTRINSIC(MachineInstr &I) const;
+  bool selectG_INTRINSIC_WITH_SIDEFFECTS(MachineInstr &I) const;
+  bool selectDbgValue(MachineInstr &I) const;
+  bool selectG_FCANONICALIZE(MachineInstr &I) const;
+  bool selectG_LROUND(MachineInstr &I) const;
+  bool selectG_MERGE_VALUES(MachineInstr &MI) const;
+  bool selectG_UNMERGE_VALUES(MachineInstr &MI) const;
+  bool selectG_BFX(MachineInstr &I, bool Signed) const;
+  bool selectG_CONSTANT(MachineInstr &I) const;
+  bool selectG_FENCE(MachineInstr &I) const;
+  bool selectG_INSERT_SUBVECTOR(MachineInstr &MI) const;
+  bool selectG_EXTRACT_SUBVECTOR(MachineInstr &MI) const;
+  bool selectG_INSERT_VECTOR_ELT(MachineInstr &MI) const;
+  bool selectG_EXTRACT_VECTOR_ELT(MachineInstr &MI) const;
+  bool selectG_ADDRSPACE_CAST(llvm::MachineInstr &I) const;
+  MachineInstr *findPtrBaseDefinition(MachineInstr *StartDef,
+                                      const MachineRegisterInfo &MRI) const;
+
+  InstructionSelector::ComplexRendererFns
+  SelectAddr_rr(MachineOperand &Root) const;
+  InstructionSelector::ComplexRendererFns
+  SelectAddr_ri(MachineOperand &Root) const;
+  InstructionSelector::ComplexRendererFns
+  selectParamSlot_ii(MachineOperand &Root) const;
+  InstructionSelector::ComplexRendererFns
+  selectParamSlot_ir(MachineOperand &Root) const;
+
+  // NOLINTEND(readability-identifier-naming)
+
+  bool emitBuildVector(MachineInstr &InsertPt, Register DstReg,
+                       ArrayRef<Register> Srcs) const;
+
+  bool useFastFP(const MachineInstr &I) const {
+    return I.getFlag(MachineInstr::MIFlag::FmAfn);
+  }
+};
+
+} // namespace
+
+#define GET_GLOBALISEL_IMPL
+#include "PISAGenGlobalISel.inc"
+#undef GET_GLOBALISEL_IMPL
+
+PISAInstructionSelector::PISAInstructionSelector(const PISATargetMachine &TM,
+                                                 const PISASubtarget &ST,
+                                                 const RegisterBankInfo &RBI)
+    : InstructionSelector(), STI(ST), TII(*ST.getInstrInfo()),
+      TRI(*ST.getRegisterInfo()), RBI(RBI),
+#define GET_GLOBALISEL_PREDICATES_INIT
+#include "PISAGenGlobalISel.inc"
+#undef GET_GLOBALISEL_PREDICATES_INIT
+#define GET_GLOBALISEL_TEMPORARIES_INIT
+#include "PISAGenGlobalISel.inc"
+#undef GET_GLOBALISEL_TEMPORARIES_INIT
+{
+}
+
+void PISAInstructionSelector::setupMF(MachineFunction &MF,
+                                      GISelValueTracking *KB,
+                                      CodeGenCoverage *CoverageInfo,
+                                      ProfileSummaryInfo *PSI,
+                                      BlockFrequencyInfo *BFI) {
+  constexpr unsigned SharedAS = unsigned(PISAAS::AddressSpace::SHARED);
+
+  SSI = SyncScopeInfo(MF.getFunction().getContext());
+  InstructionSelector::setupMF(MF, KB, CoverageInfo, PSI, BFI);
+  MRI = &MF.getRegInfo();
+  MFI = &MF.getFrameInfo();
+  // Setup the correct stack ID.
+  for (int Idx = MFI->getObjectIndexBegin(), EndIdx = MFI->getObjectIndexEnd();
+       Idx != EndIdx; ++Idx) {
+    if (auto *AI = MFI->getObjectAllocation(Idx)) {
+      const auto AS = AI->getAddressSpace();
+      Type *AllocatedTy = AI->getAllocatedType();
+      if (auto *ATy = dyn_cast<ArrayType>(AllocatedTy))
+        AllocatedTy = ATy->getElementType();
+
+      auto *TExtTy = dyn_cast<TargetExtType>(AllocatedTy);
+
+      if (AS == SharedAS) {
+        assert(MF.getFunction().getCallingConv() == CallingConv::PISA_KERNEL &&
+               "shared variables are not supported in functions!");
+        unsigned StackID = TargetStackID::PISAShared;
+        if (TExtTy)
+          StackID = llvm::StringSwitch<unsigned>(TExtTy->getName())
+                        .Default(TargetStackID::PISAShared);
+        MFI->setStackID(Idx, StackID);
+      }
+    }
+  }
+}
+
+bool PISAInstructionSelector::select(MachineInstr &I) {
+  assert(I.getParent() && "Instruction should be in a basic block!");
+  assert(I.getParent()->getParent() && "Instruction should be in a function!");
+
+  unsigned Opcode = I.getOpcode();
+
+  // If it's not a GMIR instruction, we've selected it already.
+  if (!I.isPreISelOpcode() && Opcode != TargetOpcode::DBG_VALUE) {
+    if (I.isCopy())
+      return selectCopy(I);
+    constrainSelectedInstRegOperands(I, TII, TRI, RBI);
+    return true;
+  }
+
+  switch (Opcode) {
+  default:
+    break;
+  case TargetOpcode::G_ADDRSPACE_CAST:
+    if (selectG_ADDRSPACE_CAST(I))
+      return true;
+    break;
+  }
+
+  if (selectImpl(I, *CoverageInfo))
+    return true;
+
+  if (I.getNumOperands() != I.getNumExplicitOperands()) {
+    LLVM_DEBUG(errs() << "Generic instr has unexpected implicit operands\n");
+    return false;
+  }
+
+  switch (Opcode) {
+  case TargetOpcode::G_FRAME_INDEX:
+    return selectFrameIndex(I);
+  case TargetOpcode::G_GLOBAL_VALUE:
+    return selectGlobalValue(I);
+  case TargetOpcode::G_PHI:
+  case TargetOpcode::G_IMPLICIT_DEF:
+    return selectPhiOrImplicitDef(I);
+  case TargetOpcode::G_UADDO:
+  case TargetOpcode::G_USUBO:
+  case TargetOpcode::G_UADDE:
+  case TargetOpcode::G_USUBE:
+    return selectAddSubWithOverflow(I);
+  case TargetOpcode::G_SADDO:
+  case TargetOpcode::G_SSUBO:
+  case TargetOpcode::G_SADDE:
+  case TargetOpcode::G_SSUBE:
+    return selectSignedAddSubWithOverflow(I);
+  case TargetOpcode::G_BITCAST:
+    return selectBitcast(I);
+  case TargetOpcode::G_UNMERGE_VALUES:
+    return selectG_UNMERGE_VALUES(I);
+  case TargetOpcode::G_CONCAT_VECTORS:
+  case TargetOpcode::G_MERGE_VALUES:
+    return selectG_MERGE_VALUES(I);
+  case TargetOpcode::G_BUILD_VECTOR:
+    return selectG_BUILD_VECTOR(I);
+  case TargetOpcode::G_PTRTOINT:
+  case TargetOpcode::G_INTTOPTR:
+    return selectConvPtrInt(I);
+  case TargetOpcode::G_FREEZE:
+    return selectCopy(I);
+  case TargetOpcode::G_INTRINSIC:
+  case TargetOpcode::G_INTRINSIC_CONVERGENT:
+    return selectG_INTRINSIC(I);
+  case TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS:
+  case TargetOpcode::G_INTRINSIC_CONVERGENT_W_SIDE_EFFECTS:
+    return selectG_INTRINSIC_WITH_SIDEFFECTS(I);
+  case TargetOpcode::DBG_VALUE:
+    return selectDbgValue(I);
+  case TargetOpcode::G_FCANONICALIZE:
+    return selectG_FCANONICALIZE(I);
+  case TargetOpcode::G_LROUND:
+  case TargetOpcode::G_LLROUND:
+    return selectG_LROUND(I);
+  case TargetOpcode::G_UBFX:
+    return selectG_BFX(I, false);
+  case TargetOpcode::G_SBFX:
+    return selectG_BFX(I, true);
+  case TargetOpcode::G_CONSTANT:
+    return selectG_CONSTANT(I);
+  case TargetOpcode::G_FENCE:
+    return selectG_FENCE(I);
+  case TargetOpcode::G_INSERT_SUBVECTOR:
+    return selectG_INSERT_SUBVECTOR(I);
+  case TargetOpcode::G_EXTRACT_SUBVECTOR:
+    return selectG_EXTRACT_SUBVECTOR(I);
+  case TargetOpcode::G_INSERT_VECTOR_ELT:
+    return selectG_INSERT_VECTOR_ELT(I);
+  case TargetOpcode::G_EXTRACT_VECTOR_ELT:
+    return selectG_EXTRACT_VECTOR_ELT(I);
+  default:
+    return false;
+  }
+}
+
+MachineInstr *PISAInstructionSelector::findPtrBaseDefinition(
+    MachineInstr *StartDef, const MachineRegisterInfo &MRI) const {
+  if (!StartDef)
+    return nullptr;
+
+  SmallVector<MachineInstr *, 8> WorkList;
+  SmallPtrSet<MachineInstr *, 8> Visited;
+  MachineInstr *DefMI = StartDef;
+  WorkList.push_back(DefMI);
+
+  while (!WorkList.empty()) {
+    DefMI = WorkList.pop_back_val();
+    if (!Visited.insert(DefMI).second)
+      continue;
+
+    auto Opcode = DefMI->getOpcode();
+    switch (Opcode) {
+    default:
+      // If we see an instruction that we don't recognize, stop looking and try
+      // to select the G_ADDRSPACE_CAST as is.
+      WorkList.clear();
+      break;
+    case TargetOpcode::G_PTR_ADD: {
+      MachineInstr *BaseDef =
+          getDefIgnoringCopies(DefMI->getOperand(1).getReg(), MRI);
+      if (BaseDef)
+        WorkList.push_back(BaseDef);
+    } break;
+    case TargetOpcode::G_PHI:
+      for (unsigned I = 1, E = DefMI->getNumOperands(); I < E; I += 2) {
+        MachineInstr *PhiDef =
+            getDefIgnoringCopies(DefMI->getOperand(I).getReg(), MRI);
+        if (PhiDef)
+          WorkList.push_back(PhiDef);
+      }
+      break;
+    case TargetOpcode::G_SELECT:
+      for (unsigned I = 2, E = DefMI->getNumOperands(); I < E; I++) {
+        MachineInstr *SelectDef =
+            getDefIgnoringCopies(DefMI->getOperand(I).getReg(), MRI);
+        if (SelectDef)
+          WorkList.push_back(SelectDef);
+      }
+      break;
+    }
+  }
+
+  return DefMI;
+}
+
+bool PISAInstructionSelector::selectG_ADDRSPACE_CAST(
+    llvm::MachineInstr &I) const {
+  constexpr unsigned GenericAS =
+      static_cast<unsigned>(PISAAS::AddressSpace::GENERIC);
+  [[maybe_unused]] constexpr unsigned SharedAS =
+      static_cast<unsigned>(PISAAS::AddressSpace::SHARED);
+  [[maybe_unused]] constexpr unsigned PrivateAS =
+      static_cast<unsigned>(PISAAS::AddressSpace::PRIVATE);
+
+  auto [Dst, DstTy, Src, SrcTy] = I.getFirst2RegLLTs();
+  const unsigned DstAS = DstTy.getAddressSpace();
+  [[maybe_unused]] const unsigned SrcAS = SrcTy.getAddressSpace();
+
+  if (DstAS != GenericAS)
+    return false;
+
+  MachineInstr *DefMI =
+      findPtrBaseDefinition(getDefIgnoringCopies(Src, *MRI), *MRI);
+
+  if (!DefMI || DefMI->getOpcode() != TargetOpcode::G_FRAME_INDEX)
+    return false;
+
+  int FI = DefMI->getOperand(1).getIndex();
+  switch (MFI->getStackID(FI)) {
+  default: // No custom selection needed
+    break;
+  }
+
+  return false;
+}
+
+bool PISAInstructionSelector::selectG_INTRINSIC(MachineInstr &I) const {
+  // While we want to select special reg intrinsics in tablegen using
+  // SpecialRegPat<>, there is an issue in llvm that prevents us from doing
+  // so when the output value is a pointer. It fails in:
+  // GlobalISelEmitter::createAndImportSelDAGMatcher()
+  // because isOutOperandAPointer() will always return false for G_INTRINSIC
+  // as its OutOperandList = (outs). This then causes addTypeCheckPredicate()
+  // to not generate a pointer matcher, so tablegen fails saying that we have
+  // an unsupported type (even though it is an iPTR). Until this is fixed,
+  // manually select these cases here.
+
+  switch (cast<GIntrinsic>(I).getIntrinsicID()) {
+  default:
+    return false;
+  }
+}
+
+bool PISAInstructionSelector::selectG_INTRINSIC_WITH_SIDEFFECTS(
+    MachineInstr &I) const {
+  // Find the intrinsic ID.
+  unsigned IntrinID = cast<GIntrinsic>(I).getIntrinsicID();
+  switch (IntrinID) {
+  default:
+    return false;
+  }
+}
+
+bool PISAInstructionSelector::selectFrameIndex(MachineInstr &I) const {
+  I.setDesc(TII.get(PISA::addrof_32b));
+  // need to set dst reg-class
+  const Register DefReg = I.getOperand(0).getReg();
+  const LLT DefTy = MRI->getType(DefReg);
+
+  const RegClassOrRegBank &RegClassOrBank = MRI->getRegClassOrRegBank(DefReg);
+  const TargetRegisterClass *DefRC =
+      RegClassOrBank.dyn_cast<const TargetRegisterClass *>();
+  if (!DefRC)
+    DefRC = TRI.getRegClassFromLLT(DefTy);
+  return RBI.constrainGenericRegister(DefReg, *DefRC, *MRI);
+}
+
+bool PISAInstructionSelector::selectGlobalValue(MachineInstr &I) const {
+  I.setDesc(TII.get(PISA::addrof_64b));
+
+  Register DefReg = I.getOperand(0).getReg();
+  const LLT DefTy = MRI->getType(DefReg);
+
+  const RegClassOrRegBank &RegClassOrBank = MRI->getRegClassOrRegBank(DefReg);
+  const TargetRegisterClass *DefRC =
+      RegClassOrBank.dyn_cast<const TargetRegisterClass *>();
+  if (!DefRC)
+    DefRC = TRI.getRegClassFromLLT(DefTy);
+
+  return RBI.constrainGenericRegister(DefReg, *DefRC, *MRI);
+}
+
+bool PISAInstructionSelector::selectPhiOrImplicitDef(MachineInstr &I) const {
+  MachineBasicBlock *BB = I.getParent();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  const Register DefReg = I.getOperand(0).getReg();
+  const LLT DefTy = MRI.getType(DefReg);
+
+  const RegClassOrRegBank &RegClassOrBank = MRI.getRegClassOrRegBank(DefReg);
+
+  const TargetRegisterClass *DefRC =
+      RegClassOrBank.dyn_cast<const TargetRegisterClass *>();
+
+  if (!DefRC)
+    DefRC = TRI.getRegClassFromLLT(DefTy);
+
+  unsigned Opcode = (I.getOpcode() == TargetOpcode::G_PHI)
+                        ? TargetOpcode::PHI
+                        : TargetOpcode::IMPLICIT_DEF;
+  I.setDesc(TII.get(Opcode));
+
+  return RBI.constrainGenericRegister(DefReg, *DefRC, MRI);
+}
+
+bool PISAInstructionSelector::selectAddSubWithOverflow(MachineInstr &I) const {
+  MachineBasicBlock *BB = I.getParent();
+  const DebugLoc &DL = I.getDebugLoc();
+
+  const bool IsAdd = I.getOpcode() == TargetOpcode::G_UADDO ||
+                     I.getOpcode() == TargetOpcode::G_UADDE;
+  const bool HasCarryIn = I.getOpcode() == TargetOpcode::G_UADDE ||
+                          I.getOpcode() == TargetOpcode::G_USUBE;
+
+  auto [DstReg, CarryOutReg, Src0Reg, Src1Reg] = I.getFirst4Regs();
+  const auto *DstRC = TRI.getRegClassFromLLT(MRI->getType(DstReg));
+  const auto *PredRC = &PISA::PredRegClass;
+
+  // Is there any reason to support 16b addc/subb?
+  assert(DstRC == &PISA::Reg32bRegClass);
+
+  auto Src0C = getIConstantVRegValWithLookThrough(Src0Reg, *MRI);
+  auto Src1C = getIConstantVRegValWithLookThrough(Src1Reg, *MRI);
+  unsigned RegReg = Src0C ? 0 : (Src1C ? 1 : 2);
+
+  const bool HasCarryOut = !HasCarryIn || !MRI->use_nodbg_empty(CarryOutReg);
+
+  unsigned PISAOps[] = {PISA::uaddc_co_32b_ir,    PISA::uaddc_co_32b_ri,
+                        PISA::uaddc_co_32b_rr,    PISA::usubb_co_32b_ir,
+                        PISA::usubb_co_32b_ri,    PISA::usubb_co_32b_rr,
+                        PISA::uaddc_ci_32b_ir,    PISA::uaddc_ci_32b_ri,
+                        PISA::uaddc_ci_32b_rr,    PISA::usubb_ci_32b_ir,
+                        PISA::usubb_ci_32b_ri,    PISA::usubb_ci_32b_rr,
+                        PISA::uaddc_ci_co_32b_ir, PISA::uaddc_ci_co_32b_ri,
+                        PISA::uaddc_ci_co_32b_rr, PISA::usubb_ci_co_32b_ir,
+                        PISA::usubb_ci_co_32b_ri, PISA::usubb_ci_co_32b_rr};
+
+  unsigned CarryOutOpc = PISAOps[(IsAdd ? 0 : 3) + RegReg];
+  unsigned CarryInOpc = PISAOps[6 + (IsAdd ? 0 : 3) + RegReg];
+  unsigned CarryInOutOpc = PISAOps[12 + (IsAdd ? 0 : 3) + RegReg];
+
+  unsigned Opc = (HasCarryIn && HasCarryOut)
+                     ? CarryInOutOpc
+                     : (HasCarryOut ? CarryOutOpc : CarryInOpc);
+
+  auto MIB = BuildMI(*BB, &I, DL, TII.get(Opc), DstReg);
+  if (HasCarryOut)
+    MIB.addDef(CarryOutReg);
+  if (Src0C)
+    MIB.addImm(Src0C->Value.getSExtValue());
+  else
+    MIB.add(I.getOperand(2));
+  if (Src1C && !Src0C)
+    MIB.addImm(Src1C->Value.getSExtValue());
+  else
+    MIB.add(I.getOperand(3));
+  if (HasCarryIn)
+    MIB.add(I.getOperand(4));
+
+  if (!RBI.constrainGenericRegister(DstReg, *DstRC, *MRI) ||
+      !RBI.constrainGenericRegister(CarryOutReg, *PredRC, *MRI) ||
+      !RBI.constrainGenericRegister(Src0Reg, *DstRC, *MRI) ||
+      !RBI.constrainGenericRegister(Src1Reg, *DstRC, *MRI))
+    return false;
+  if (HasCarryIn &&
+      !RBI.constrainGenericRegister(I.getOperand(4).getReg(), *PredRC, *MRI))
+    return false;
+
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectSignedAddSubWithOverflow(
+    MachineInstr &I) const {
+  MachineBasicBlock *BB = I.getParent();
+  const DebugLoc &DL = I.getDebugLoc();
+
+  const bool IsAdd = I.getOpcode() == TargetOpcode::G_SADDO ||
+                     I.getOpcode() == TargetOpcode::G_SADDE;
+  const bool HasCarryIn = I.getOpcode() == TargetOpcode::G_SADDE ||
+                          I.getOpcode() == TargetOpcode::G_SSUBE;
+
+  auto [DstReg, CarryOutReg, Src0Reg, Src1Reg] = I.getFirst4Regs();
+  const auto *DstRC = TRI.getRegClassFromLLT(MRI->getType(DstReg));
+  const auto *PredRC = &PISA::PredRegClass;
+
+  assert(DstRC == &PISA::Reg32bRegClass);
+
+  auto Src0C = getIConstantVRegValWithLookThrough(Src0Reg, *MRI);
+  auto Src1C = getIConstantVRegValWithLookThrough(Src1Reg, *MRI);
+  unsigned RegReg = Src0C ? 0 : (Src1C ? 1 : 2);
+
+  unsigned PISAOps[] = {PISA::uaddc_co_32b_ir,    PISA::uaddc_co_32b_ri,
+                        PISA::uaddc_co_32b_rr,    PISA::usubb_co_32b_ir,
+                        PISA::usubb_co_32b_ri,    PISA::usubb_co_32b_rr,
+                        PISA::uaddc_ci_co_32b_ir, PISA::uaddc_ci_co_32b_ri,
+                        PISA::uaddc_ci_co_32b_rr, PISA::usubb_ci_co_32b_ir,
+                        PISA::usubb_ci_co_32b_ri, PISA::usubb_ci_co_32b_rr};
+  unsigned NoCarryOpc = PISAOps[(IsAdd ? 0 : 3) + RegReg];
+  unsigned CarryOpc = PISAOps[6 + (IsAdd ? 0 : 3) + RegReg];
+
+  auto CarryOutPredReg = MRI->createVirtualRegister(PredRC);
+  const auto *IntRC = TRI.getRegClassFromLLT(LLT::integer(16));
+  auto CarryOutIntReg = MRI->createVirtualRegister(IntRC);
+  auto CarryInIntReg = MRI->createVirtualRegister(IntRC);
+  auto MIB =
+      BuildMI(*BB, &I, DL, TII.get(HasCarryIn ? CarryOpc : NoCarryOpc), DstReg)
+          .addDef(CarryOutPredReg);
+  if (Src0C)
+    MIB.addImm(Src0C->Value.getSExtValue());
+  else
+    MIB.add(I.getOperand(2));
+  if (Src1C && !Src0C)
+    MIB.addImm(Src1C->Value.getSExtValue());
+  else
+    MIB.add(I.getOperand(3));
+  if (HasCarryIn)
+    MIB.add(I.getOperand(4));
+
+  // overflow is produced when MSB of arguments are same and
+  // result of add/sub operation changes MSB value. Overflow
+  // will occur if carry-in != carry-out for unsigned add/sub.
+  BuildMI(*BB, &I, DL, TII.get(PISA::sel_16_iip), CarryOutIntReg)
+      .addReg(CarryOutPredReg)
+      .addImm(1)
+      .addImm(0);
+  if (HasCarryIn) {
+    BuildMI(*BB, &I, DL, TII.get(PISA::sel_16_iip), CarryInIntReg)
+        .add(I.getOperand(4))
+        .addImm(1)
+        .addImm(0);
+    BuildMI(*BB, &I, DL, TII.get(PISA::ucmp_eq_16b_prr), CarryOutReg)
+        .addReg(CarryOutIntReg)
+        .addReg(CarryInIntReg);
+  } else {
+    BuildMI(*BB, &I, DL, TII.get(PISA::ucmp_eq_16b_pri), CarryOutReg)
+        .addReg(CarryOutIntReg)
+        .addImm(0);
+  }
+
+  if (!RBI.constrainGenericRegister(DstReg, *DstRC, *MRI) ||
+      !RBI.constrainGenericRegister(CarryOutReg, *PredRC, *MRI) ||
+      !RBI.constrainGenericRegister(Src0Reg, *DstRC, *MRI) ||
+      !RBI.constrainGenericRegister(Src1Reg, *DstRC, *MRI) ||
+      !RBI.constrainGenericRegister(CarryOutPredReg, *PredRC, *MRI) ||
+      !RBI.constrainGenericRegister(CarryOutIntReg, *IntRC, *MRI) ||
+      !RBI.constrainGenericRegister(CarryInIntReg, *IntRC, *MRI))
+    return false;
+  if (HasCarryIn &&
+      !RBI.constrainGenericRegister(I.getOperand(4).getReg(), *PredRC, *MRI))
+    return false;
+
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectBitcast(MachineInstr &I) const {
+  MachineBasicBlock *BB = I.getParent();
+  DebugLoc DL = I.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  Register DstReg = I.getOperand(0).getReg();
+  Register SrcReg = I.getOperand(1).getReg();
+  LLT DstTy = MRI.getType(DstReg);
+  LLT SrcTy = MRI.getType(SrcReg);
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+
+  if (DstTy.getSizeInBits() != SrcTy.getSizeInBits()) {
+    assert(false && "Wrong bitcast operands!");
+    return false;
+  }
+
+  BuildMI(*BB, &I, DL, TII.get(TargetOpcode::COPY))
+      .addDef(DstReg)
+      .addReg(SrcReg);
+
+  if (!RBI.constrainGenericRegister(SrcReg, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(DstReg, *DstRC, MRI))
+    return false;
+
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_MERGE_VALUES(MachineInstr &MI) const {
+  MachineBasicBlock *BB = MI.getParent();
+  const DebugLoc &DL = MI.getDebugLoc();
+  const int NumSrc = MI.getNumOperands() - 1;
+
+  auto [DstReg, DstTy, SrcReg, SrcTy] = MI.getFirst2RegLLTs();
+  const unsigned SrcNumElts = SrcTy.isVector() ? SrcTy.getNumElements() : 1;
+  const unsigned SrcEltSize = SrcTy.getScalarSizeInBits();
+
+  // Subregister code below can only operate on vector destination.
+  // - create bitcasts in case of scalar destination (G_MERGE_VALUES)
+  if (!DstTy.isVector()) {
+    const TargetRegisterClass *DstRC = TRI.getRegClassFromLLT(DstTy);
+    if (!DstRC || !RBI.constrainGenericRegister(DstReg, *DstRC, *MRI))
+      return false;
+
+    auto DstNumElts = DstTy.getSizeInBits() / SrcTy.getScalarSizeInBits();
+    DstTy = LLT::fixed_vector(DstNumElts, SrcTy.getScalarType());
+    DstReg = MRI->createGenericVirtualRegister(DstTy);
+  }
+
+  assert(DstTy.isVector() && "expecting vector destination in merge_values");
+  assert((NumSrc <= 4) && "expecting vector of 4 or less in merge_values");
+
+  const TargetRegisterClass *DstRC = TRI.getRegClassFromLLT(DstTy);
+  if (!DstRC || !RBI.constrainGenericRegister(DstReg, *DstRC, *MRI))
+    return false;
+
+  // G_CONCAT_VECTORS of sub-vectors that each map to a nameable composite
+  // sub-register (.xy / .zw): write each source directly into its slice via
+  // INSERT_SUBREG, instead of unpacking to scalars and rebuilding. Register
+  // coalescing then folds each source's producer into the destination slice
+  // (the write-side counterpart of the .xy/.zw read optimization).
+  if (MI.getOpcode() == TargetOpcode::G_CONCAT_VECTORS && SrcTy.isVector()) {
+    bool AllComposite = SrcNumElts > 0;
+    for (int I = 0; I < NumSrc && AllComposite; ++I) {
+      if (MRI->getType(MI.getOperand(I + 1).getReg()) != SrcTy)
+        AllComposite = false;
+      else if (unsigned Comp = TRI.getCompositeSubRegIdx(
+                   SrcEltSize, I * SrcNumElts, SrcNumElts)) {
+        if (!TRI.getSubClassWithSubReg(DstRC, Comp))
+          AllComposite = false;
+      } else
+        AllComposite = false;
+    }
+    if (AllComposite) {
+      // If all sources are consecutive outputs of a single G_UNMERGE_VALUES
+      // whose source has the same type as our destination, the concat just
+      // rebuilds the original register -- emit a COPY instead of INSERT_SUBREGs
+      // to avoid a self-copy round-trip the coalescer cannot eliminate.
+      bool IsIdentityConcat = false;
+      if (NumSrc >= 2) {
+        MachineInstr *FirstDef = MRI->getVRegDef(MI.getOperand(1).getReg());
+        if (FirstDef &&
+            FirstDef->getOpcode() == TargetOpcode::G_UNMERGE_VALUES) {
+          unsigned NumUnmergeOuts = FirstDef->getNumOperands() - 1;
+          Register UnmergeSrc = FirstDef->getOperand(NumUnmergeOuts).getReg();
+          if ((int)NumUnmergeOuts == NumSrc &&
+              MRI->getType(UnmergeSrc) == DstTy) {
+            IsIdentityConcat = true;
+            for (int I = 0; I < NumSrc && IsIdentityConcat; ++I) {
+              MachineInstr *Def =
+                  MRI->getVRegDef(MI.getOperand(I + 1).getReg());
+              if (Def != FirstDef || MI.getOperand(I + 1).getReg() !=
+                                         FirstDef->getOperand(I).getReg())
+                IsIdentityConcat = false;
+            }
+          }
+        }
+      }
+      if (IsIdentityConcat) {
+        MachineInstr *FirstDef = MRI->getVRegDef(MI.getOperand(1).getReg());
+        unsigned NumUnmergeOuts = FirstDef->getNumOperands() - 1;
+        Register UnmergeSrc = FirstDef->getOperand(NumUnmergeOuts).getReg();
+        BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::COPY), DstReg)
+            .addReg(UnmergeSrc);
+        if (!RBI.constrainGenericRegister(UnmergeSrc, *DstRC, *MRI))
+          return false;
+        MI.eraseFromParent();
+        return true;
+      }
+
+      auto *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+      Register CurVec = MRI->createVirtualRegister(DstRC);
+      BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::IMPLICIT_DEF), CurVec);
+      if (!RBI.constrainGenericRegister(CurVec, *DstRC, *MRI))
+        return false;
+      for (int I = 0; I < NumSrc; ++I) {
+        unsigned Comp =
+            TRI.getCompositeSubRegIdx(SrcEltSize, I * SrcNumElts, SrcNumElts);
+        Register SrcReg = MI.getOperand(I + 1).getReg();
+        if (!SrcRC || !RBI.constrainGenericRegister(SrcReg, *SrcRC, *MRI))
+          return false;
+        Register CurDst =
+            (I + 1 == NumSrc) ? DstReg : MRI->createVirtualRegister(DstRC);
+        BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::INSERT_SUBREG), CurDst)
+            .addReg(CurVec)
+            .addReg(SrcReg)
+            .addImm(Comp);
+        if (!RBI.constrainGenericRegister(CurDst, *DstRC, *MRI))
+          return false;
+        CurVec = CurDst;
+      }
+      MI.eraseFromParent();
+      return true;
+    }
+  }
+
+  SmallVector<Register> Regs;
+  for (int I = 0, E = NumSrc; I != E; ++I) {
+    MachineOperand &Src = MI.getOperand(I + 1);
+    Register SrcReg = Src.getReg();
+
+    if (SrcNumElts == 1) {
+      const TargetRegisterClass *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+      if (!SrcRC || !RBI.constrainGenericRegister(SrcReg, *SrcRC, *MRI))
+        return false;
+      Regs.push_back(SrcReg);
+
+    } else {
+      auto *SubRC = TRI.getRegClassFromLLT(SrcTy.getScalarType());
+      for (unsigned J = 0; J < SrcNumElts; J++) {
+        auto SubRegIdx = TRI.getSubRegIdx(SrcEltSize, J);
+        // Constrain SrcReg to the subclass supporting this subreg index
+        // (needed when RC contains physicals without the subreg, e.g.
+        // RegV2_16b with Reg32b members that lack sub16_N). Sources may not
+        // have an RC set yet if their defining instruction hasn't been
+        // selected (selection order is bottom-up), so derive from LLT.
+        auto *SrcRC = MRI->getRegClassOrNull(SrcReg);
+        if (!SrcRC)
+          SrcRC = TRI.getRegClassFromLLT(SrcTy);
+        if (SrcRC) {
+          if (auto *NarrowRC = TRI.getSubClassWithSubReg(SrcRC, SubRegIdx))
+            RBI.constrainGenericRegister(SrcReg, *NarrowRC, *MRI);
+        }
+        auto SubSrcReg =
+            MRI->createGenericVirtualRegister(SrcTy.getScalarType());
+        BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::COPY), SubSrcReg)
+            .addReg(SrcReg, {}, SubRegIdx);
+        if (!SubRC || !RBI.constrainGenericRegister(SubSrcReg, *SubRC, *MRI))
+          return false;
+        Regs.push_back(SubSrcReg);
+      }
+    }
+  }
+  if (!emitBuildVector(MI, DstReg, Regs))
+    return false;
+
+  // copy back the value in case of scalar destination
+  auto OrigDstReg = MI.getOperand(0).getReg();
+  if (!MRI->getType(OrigDstReg).isVector()) {
+    BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::COPY), OrigDstReg)
+        .addReg(DstReg);
+  }
+
+  MI.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_UNMERGE_VALUES(MachineInstr &MI) const {
+  MachineBasicBlock *BB = MI.getParent();
+  const DebugLoc &DL = MI.getDebugLoc();
+  const int NumDst = MI.getNumOperands() - 1;
+
+  Register SrcReg = MI.getOperand(NumDst).getReg();
+  LLT SrcTy = MRI->getType(SrcReg);
+
+  Register DstReg = MI.getOperand(0).getReg();
+  LLT DstTy = MRI->getType(DstReg);
+  const unsigned DstNumElts = DstTy.isVector() ? DstTy.getNumElements() : 1;
+  const unsigned DstEltSize = DstTy.getScalarSizeInBits();
+
+  // Subregister code below can only operate on vector sources.
+  // - create bitcasts in case of scalar sources
+  if (!SrcTy.isVector()) {
+    const TargetRegisterClass *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+    if (!SrcRC || !RBI.constrainGenericRegister(SrcReg, *SrcRC, *MRI))
+      return false;
+
+    auto SrcNumElts = SrcTy.getSizeInBits() / DstTy.getScalarSizeInBits();
+    SrcTy = LLT::fixed_vector(SrcNumElts, DstTy.getScalarType());
+    auto NewSrcReg = MRI->createGenericVirtualRegister(SrcTy);
+    BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::COPY), NewSrcReg)
+        .addReg(SrcReg);
+    SrcReg = NewSrcReg;
+  }
+
+  assert(SrcTy.isVector() && "expecting vector source in unmerge_values");
+  assert((NumDst <= 4) && "expecting vector of 4 or less in unmerge_values");
+
+  const TargetRegisterClass *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+  if (!SrcRC || !RBI.constrainGenericRegister(SrcReg, *SrcRC, *MRI))
+    return false;
+
+  for (int I = 0, E = NumDst; I != E; ++I) {
+    Register DstReg = MI.getOperand(I).getReg();
+    SmallVector<Register> Regs;
+    for (unsigned J = 0; J < DstNumElts; J++) {
+      auto SubRegIdx = TRI.getSubRegIdx(DstEltSize, I * DstNumElts + J);
+      // Constrain SrcReg to the subclass supporting this subreg index.
+      if (auto *CurSrcRC = MRI->getRegClassOrNull(SrcReg)) {
+        if (auto *NarrowRC = TRI.getSubClassWithSubReg(CurSrcRC, SubRegIdx))
+          MRI->constrainRegClass(SrcReg, NarrowRC);
+      }
+      auto SubDstReg =
+          (DstNumElts == 1)
+              ? DstReg
+              : MRI->createGenericVirtualRegister(DstTy.getScalarType());
+      BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::COPY), SubDstReg)
+          .addReg(SrcReg, {}, SubRegIdx);
+      Regs.push_back(SubDstReg);
+    }
+
+    if (DstNumElts > 1) {
+      auto *SubRC = TRI.getRegClassFromLLT(DstTy.getScalarType());
+      for (Register R : Regs) {
+        if (!RBI.constrainGenericRegister(R, *SubRC, *MRI))
+          return false;
+      }
+      if (!emitBuildVector(MI, DstReg, Regs))
+        return false;
+    }
+
+    const TargetRegisterClass *DstRC = TRI.getRegClassFromLLT(DstTy);
+    if (DstRC && !RBI.constrainGenericRegister(DstReg, *DstRC, *MRI))
+      return false;
+  }
+
+  MI.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectConvPtrInt(MachineInstr &I) const {
+  // Custom select G_PTRTOINT and G_INTTOPTR
+  MachineBasicBlock *BB = I.getParent();
+  DebugLoc DL = I.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  Register DstReg = I.getOperand(0).getReg();
+  Register SrcReg = I.getOperand(1).getReg();
+
+  LLT DstTy = MRI.getType(DstReg);
+  LLT SrcTy = MRI.getType(SrcReg);
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+
+  assert(DstTy.getSizeInBits() == SrcTy.getSizeInBits() &&
+         "mismatch in int2ptr src/dst sizes");
+  BuildMI(*BB, &I, DL, TII.get(TargetOpcode::COPY))
+      .addDef(DstReg)
+      .addReg(SrcReg);
+
+  if (!RBI.constrainGenericRegister(SrcReg, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(DstReg, *DstRC, MRI))
+    return false;
+
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectCopy(MachineInstr &I) const {
+  MachineBasicBlock *BB = I.getParent();
+  DebugLoc DL = I.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  Register DstReg = I.getOperand(0).getReg();
+  Register SrcReg = I.getOperand(1).getReg();
+  LLT DstTy = MRI.getType(DstReg);
+  LLT SrcTy = MRI.getType(SrcReg);
+
+  if (auto *SrcDefI = MRI.getVRegDef(SrcReg);
+      SrcDefI && SrcDefI->isInlineAsm() && !SrcTy.isValid()) {
+    assert(DstTy.isValid());
+    SrcTy = DstTy;
+  } else if (MRI.hasOneNonDBGUse(DstReg) &&
+             MRI.use_instr_nodbg_begin(DstReg)->isInlineAsm()) {
+    assert(SrcTy.isValid());
+    DstTy = SrcTy;
+  }
+
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+
+  if (DstTy.getSizeInBits() != SrcTy.getSizeInBits()) {
+    assert(false && "Operands with different bit size!");
+    return false;
+  }
+
+  BuildMI(*BB, &I, DL, TII.get(TargetOpcode::COPY))
+      .addDef(DstReg)
+      .addReg(SrcReg);
+
+  if (!RBI.constrainGenericRegister(SrcReg, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(DstReg, *DstRC, MRI))
+    return false;
+
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::emitBuildVector(MachineInstr &InsertPt,
+                                              Register DstReg,
+                                              ArrayRef<Register> Srcs) const {
+  assert(!Srcs.empty());
+  MachineBasicBlock *BB = InsertPt.getParent();
+  DebugLoc DL = InsertPt.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  // A sequence of INSERT_SUBREG's that are later lowered
+  // to COPY's is generated.
+
+  const uint32_t NumSrcs = Srcs.size();
+
+  LLT DstTy = MRI.getType(DstReg);
+  LLT EltTy = MRI.getType(Srcs[0]);
+  const unsigned SrcSize = EltTy.getSizeInBits();
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+
+  Register CurVecReg = MRI.createVirtualRegister(DstRC);
+  BuildMI(*BB, &InsertPt, InsertPt.getDebugLoc(),
+          TII.get(TargetOpcode::IMPLICIT_DEF), CurVecReg);
+
+  if (!RBI.constrainGenericRegister(CurVecReg, *DstRC, MRI))
+    return false;
+
+  // Use the large-vector insert path when the class has no per-element
+  // sub-registers (e.g. RegV64_32b).
+  bool HasSubRegSupport =
+      TRI.getSubClassWithSubReg(DstRC, TRI.getSubRegIdx(SrcSize, 0));
+  if (!HasSubRegSupport || Srcs.size() > 4) {
+    assert(EltTy.getScalarSizeInBits() == 32);
+    for (auto [Index, EltReg] : llvm::enumerate(Srcs)) {
+      // lookup target opcode
+      std::string OpcodeName = "insert_" + std::to_string(Index) + "_v" +
+                               std::to_string(DstTy.getNumElements()) + "i" +
+                               std::to_string(DstTy.getScalarSizeInBits()) +
+                               "_i32_r";
+      auto *Entry = PISA::lookupName2InstrOpEntry(OpcodeName);
+      assert(Entry && "unable to find insert instruction");
+      auto Opcode = Entry->Opcode;
+
+      Register CurDstReg =
+          (Index + 1 == NumSrcs) ? DstReg : MRI.createVirtualRegister(DstRC);
+      BuildMI(*BB, &InsertPt, DL, TII.get(Opcode), CurDstReg)
+          .addReg(CurVecReg)
+          .addReg(EltReg);
+
+      if (!RBI.constrainGenericRegister(CurDstReg, *DstRC, MRI))
+        return false;
+      CurVecReg = CurDstReg;
+    }
+  } else {
+    // Narrow DstRC to the subclass supporting the subreg indices we'll use
+    // (needed when RC contains physicals without those subregs, e.g.
+    // RegV2_16b with Reg32b members that lack sub16_N).
+    if (NumSrcs > 0) {
+      unsigned FirstSubRegIdx = TRI.getSubRegIdx(SrcSize, 0);
+      if (auto *NarrowRC = TRI.getSubClassWithSubReg(DstRC, FirstSubRegIdx)) {
+        DstRC = NarrowRC;
+        MRI.constrainRegClass(CurVecReg, DstRC);
+      }
+    }
+    for (auto [Idx, EltReg] : llvm::enumerate(Srcs)) {
+      unsigned SubRegIdx = TRI.getSubRegIdx(SrcSize, Idx);
+      Register CurDstReg =
+          (Idx + 1 == NumSrcs) ? DstReg : MRI.createVirtualRegister(DstRC);
+
+      // skip building INSERT_SUBREG if element is undef, since element
+      // is already undef in current vector
+      if (!getOpcodeDef(TargetOpcode::G_IMPLICIT_DEF, EltReg, MRI)) {
+        BuildMI(*BB, &InsertPt, DL, TII.get(TargetOpcode::INSERT_SUBREG),
+                CurDstReg)
+            .addReg(CurVecReg)
+            .addReg(EltReg)
+            .addImm(SubRegIdx);
+      } else if (CurDstReg == DstReg) {
+        BuildMI(*BB, &InsertPt, DL, TII.get(TargetOpcode::COPY))
+            .addDef(CurDstReg)
+            .addReg(CurVecReg);
+      } else {
+        continue;
+      }
+
+      if (!RBI.constrainGenericRegister(CurDstReg, *DstRC, MRI))
+        return false;
+
+      CurVecReg = CurDstReg;
+    }
+  }
+
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_BUILD_VECTOR(MachineInstr &I) const {
+  MachineBasicBlock *BB = I.getParent();
+  DebugLoc DL = I.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+  bool AllUndef = true;
+
+  SmallVector<Register> SrcRegs;
+  uint32_t LastDefinedIdx = 1;
+  for (uint32_t It = 1; It < I.getNumOperands(); It++) {
+    auto SrcReg = I.getOperand(It).getReg();
+    MachineInstr *Def = getDefIgnoringCopies(SrcReg, MRI);
+    if (Def->getOpcode() != TargetOpcode::G_IMPLICIT_DEF) {
+      AllUndef = false;
+      LastDefinedIdx = It;
+    }
+    SrcRegs.push_back(SrcReg);
+  }
+
+  if (SrcRegs.size() > LastDefinedIdx)
+    SrcRegs.resize(LastDefinedIdx);
+
+  Register DstReg = I.getOperand(0).getReg();
+  if (AllUndef) {
+    // replace vector of IMPLICIT_DEFs with a single IMPLICIT_DEF
+    BuildMI(*BB, &I, DL, TII.get(TargetOpcode::IMPLICIT_DEF)).addDef(DstReg);
+    auto *DstRC = TRI.getRegClassFromLLT(MRI.getType(DstReg));
+    if (!RBI.constrainGenericRegister(DstReg, *DstRC, MRI))
+      return false;
+  } else if (!emitBuildVector(I, DstReg, SrcRegs))
+    return false;
+
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectDbgValue(MachineInstr &I) const {
+  // Retain DBG_VALUE MIR opcode, but make it target specific by
+  // adding a phony operand. This way, AsmPrinter won't print it
+  // as target independent instruction. If we don't add the phony
+  // operand, AsmPrinter thinks it's the machine independent
+  // version so it print it itself and never invokes PISAAsmPrinter.
+  // If we select DBG_VALUE to a custom pseudo op then other
+  // passes need to be taught to not mark the pseudo op as "dead".
+  // To keep it simple, therefore, we keep DBG_VALUE operation
+  // but attach a phony operand. This is still legal because
+  // DBG_VALUE is treated as a variadic operation.
+  I.addOperand(*I.getParent()->getParent(), MachineOperand::CreateImm(1));
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_FCANONICALIZE(MachineInstr &I) const {
+  MachineBasicBlock *BB = I.getParent();
+  DebugLoc DL = I.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  Register DstReg = I.getOperand(0).getReg();
+  Register SrcReg = I.getOperand(1).getReg();
+  LLT DstTy = MRI.getType(DstReg);
+  LLT SrcTy = MRI.getType(SrcReg);
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+  auto BitSize = DstTy.getScalarSizeInBits();
+
+  // https://llvm.org/docs/LangRef.html#i-intr-llvm-canonicalize
+  auto &Ctx = I.getMF()->getFunction().getContext();
+  auto *CFP = ConstantFP::get(Ctx, getAPFloatFromSize(1.0, BitSize));
+
+  unsigned Opcode;
+  switch (BitSize) {
+  case 16:
+    Opcode = PISA::fmul_hf_ri;
+    break;
+  case 32:
+    Opcode = PISA::fmul_f_ri;
+    break;
+  case 64:
+    Opcode = PISA::fmul_df_ri;
+    break;
+  default:
+    llvm_unreachable("illegal dst type for fcanonicalize");
+  }
+  BuildMI(*BB, &I, DL, TII.get(Opcode))
+      .addDef(DstReg)
+      .addReg(SrcReg)
+      .addFPImm(CFP);
+
+  if (!RBI.constrainGenericRegister(SrcReg, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(DstReg, *DstRC, MRI))
+    return false;
+
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_LROUND(MachineInstr &I) const {
+  MachineBasicBlock *BB = I.getParent();
+  DebugLoc DL = I.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  Register DstReg = I.getOperand(0).getReg();
+  Register SrcReg = I.getOperand(1).getReg();
+
+  LLT DstTy = MRI.getType(DstReg);
+  LLT SrcTy = MRI.getType(SrcReg);
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+  auto SrcSize = SrcTy.getScalarSizeInBits();
+  auto DstSize = DstTy.getScalarSizeInBits();
+  assert((SrcSize == 32 || SrcSize == 64) && "illegal Src type for lround");
+  assert((DstSize == 32 || DstSize == 64) && "illegal Dst type for lround");
+
+  auto &Ctx = I.getMF()->getFunction().getContext();
+  auto *FPHalf = ConstantFP::get(Ctx, getAPFloatFromSize(0.5, SrcSize));
+  auto *FPZero = ConstantFP::get(Ctx, getAPFloatFromSize(0.0, SrcSize));
+
+  unsigned F2i[/*src/64*/ 2][/*dst/64*/ 2] = {
+      {PISA::f2i_u32_f_rz_r, PISA::f2i_u64_f_rz_r},
+      {PISA::f2i_u32_df_rz_r, PISA::f2i_u64_df_rz_r},
+  };
+
+  // https://en.cppreference.com/w/cpp/numeric/math/round
+  // => lrint(copysign(0.5 + fabs(x), x));
+  Register FAbsReg = MRI.createVirtualRegister(SrcRC);
+  Register FAddReg = MRI.createVirtualRegister(SrcRC);
+  Register FNegReg = MRI.createVirtualRegister(SrcRC);
+  Register FSelReg = MRI.createVirtualRegister(SrcRC);
+
+  auto *CmpRC = TRI.getRegClassFromLLT(LLT::integer(1));
+  Register CmpReg = MRI.createVirtualRegister(CmpRC);
+
+  unsigned Opcode;
+  Opcode = (SrcSize == 64) ? PISA::fabs_df_r : PISA::fabs_f_r;
+  BuildMI(*BB, &I, DL, TII.get(Opcode)).addDef(FAbsReg).addReg(SrcReg);
+  Opcode = (SrcSize == 64) ? PISA::fadd_df_ri : PISA::fadd_f_ri;
+  BuildMI(*BB, &I, DL, TII.get(Opcode))
+      .addDef(FAddReg)
+      .addReg(FAbsReg)
+      .addFPImm(FPHalf);
+  Opcode = (SrcSize == 64) ? PISA::fneg_df_r : PISA::fneg_f_r;
+  BuildMI(*BB, &I, DL, TII.get(Opcode)).addDef(FNegReg).addReg(FAddReg);
+  Opcode = (SrcSize == 64) ? PISA::fcmp_lt_df_pri : PISA::fcmp_lt_f_pri;
+  BuildMI(*BB, &I, DL, TII.get(Opcode))
+      .addDef(CmpReg)
+      .addReg(SrcReg)
+      .addFPImm(FPZero);
+  Opcode = (SrcSize == 64) ? PISA::sel_df_rrp : PISA::sel_f_rrp;
+  BuildMI(*BB, &I, DL, TII.get(Opcode))
+      .addDef(FSelReg)
+      .addReg(CmpReg)
+      .addReg(FNegReg)
+      .addReg(FAddReg);
+  Opcode = F2i[SrcSize / 64][DstSize / 64];
+  BuildMI(*BB, &I, DL, TII.get(Opcode)).addDef(DstReg).addReg(FSelReg);
+
+  if (!RBI.constrainGenericRegister(SrcReg, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(DstReg, *DstRC, MRI))
+    return false;
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_BFX(MachineInstr &I, bool Signed) const {
+  MachineBasicBlock *BB = I.getParent();
+  DebugLoc DL = I.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  auto [Dst, Src0, Src1, Src2] = I.getFirst4Regs();
+  LLT DstTy = MRI.getType(Dst);
+  LLT SrcTy = MRI.getType(Src0);
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+  assert((SrcTy.getScalarSizeInBits() == 32) && "illegal Src type for bfe");
+  assert((DstTy.getScalarSizeInBits() == 32) && "illegal Dst type for bfe");
+
+  // Res = G_UBFX (Base,LSB,Width)
+  // Res = bfe (Base,Width,Offset)
+  unsigned OpcodesU[] = {PISA::ubfe_32b_rrr, PISA::ubfe_32b_rri,
+                         PISA::ubfe_32b_rir, PISA::ubfe_32b_rii,
+                         PISA::ubfe_32b_irr, PISA::ubfe_32b_iri,
+                         PISA::ubfe_32b_iir, PISA::ubfe_32b_iii};
+  unsigned OpcodesS[] = {PISA::sbfe_32b_rrr, PISA::sbfe_32b_rri,
+                         PISA::sbfe_32b_rir, PISA::sbfe_32b_rii,
+                         PISA::sbfe_32b_irr, PISA::sbfe_32b_iri,
+                         PISA::sbfe_32b_iir, PISA::sbfe_32b_iii};
+  auto &Opcodes = Signed ? OpcodesS : OpcodesU;
+  auto WidthImm = getIConstantVRegValWithLookThrough(Src2, MRI);
+  auto OffsetImm = getIConstantVRegValWithLookThrough(Src1, MRI);
+  auto SourceImm = getIConstantVRegValWithLookThrough(Src0, MRI);
+  unsigned OpIdx = SourceImm ? 4 : 0;
+  if (WidthImm)
+    OpIdx += 2;
+  if (OffsetImm)
+    OpIdx += 1;
+  auto MIB = BuildMI(*BB, &I, DL, TII.get(Opcodes[OpIdx])).addDef(Dst);
+  if (SourceImm) {
+    auto SourceImmValue = Signed ? SourceImm->Value.getSExtValue()
+                                 : SourceImm->Value.getZExtValue();
+    MIB.addImm(SourceImmValue);
+  } else {
+    MIB.addReg(Src0);
+  }
+  if (WidthImm) {
+    auto WidthImmValue = Signed ? WidthImm->Value.getSExtValue()
+                                : WidthImm->Value.getZExtValue();
+    MIB.addImm(WidthImmValue);
+  } else {
+    MIB.addReg(Src2);
+  }
+  if (OffsetImm) {
+    auto OffsetImmValue = Signed ? OffsetImm->Value.getSExtValue()
+                                 : OffsetImm->Value.getZExtValue();
+    MIB.addImm(OffsetImmValue);
+  } else {
+    MIB.addReg(Src1);
+  }
+
+  if (!RBI.constrainGenericRegister(Src0, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(Src1, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(Src2, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(Dst, *DstRC, MRI))
+    return false;
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_CONSTANT(MachineInstr &I) const {
+  MachineBasicBlock *BB = I.getParent();
+  DebugLoc DL = I.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  auto Dst = I.getOperand(0).getReg();
+  auto Src = I.getOperand(1).getCImm()->getZExtValue();
+  LLT DstTy = MRI.getType(Dst);
+  unsigned DstSize = DstTy.getScalarSizeInBits();
+
+  // For non-boolean constants (e.g. G_CONSTANT with a float dest type that
+  // selectImpl couldn't match), emit a MOV immediate directly.
+  if (DstSize != 1) {
+    unsigned MovOpc;
+    switch (DstSize) {
+    case 8:
+      MovOpc = PISA::mov_i8_i;
+      break;
+    case 16:
+      MovOpc = PISA::mov_i16_i;
+      break;
+    case 32:
+      MovOpc = PISA::mov_i32_i;
+      break;
+    case 64:
+      MovOpc = PISA::mov_i64_i;
+      break;
+    default:
+      LLVM_DEBUG(errs() << "Unsupported G_CONSTANT size: " << DstSize << "\n");
+      return false;
+    }
+    auto *DstRC = TRI.getRegClassFromLLT(LLT::integer(DstSize));
+    BuildMI(*BB, &I, DL, TII.get(MovOpc), Dst).addImm(Src);
+    if (!RBI.constrainGenericRegister(Dst, *DstRC, MRI))
+      return false;
+    I.eraseFromParent();
+    return true;
+  }
+
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+
+  auto *ConstRC = TRI.getRegClassFromLLT(LLT::integer(32));
+  Register ConstReg = MRI.createVirtualRegister(ConstRC);
+  BuildMI(*BB, &I, DL, TII.get(PISA::mov_i32_i), ConstReg).addImm(0);
+
+  unsigned Opcodes[] = {PISA::ucmp_ne_32b_prr, PISA::ucmp_eq_32b_prr};
+  BuildMI(*BB, &I, DL, TII.get(Opcodes[Src & 1]), Dst)
+      .addReg(ConstReg)
+      .addReg(ConstReg);
+
+  if (!RBI.constrainGenericRegister(ConstReg, *ConstRC, MRI) ||
+      !RBI.constrainGenericRegister(Dst, *DstRC, MRI))
+    return false;
+  I.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_FENCE(MachineInstr &MI) const {
+  MachineBasicBlock *BB = MI.getParent();
+  DebugLoc DL = MI.getDebugLoc();
+
+  // G_FENCE has two operands:
+  // Operand 0: AtomicOrdering
+  // Operand 1: SyncScope
+  unsigned MemOrder = MI.getOperand(0).getImm();
+  SyncScope::ID Ord = SyncScope::ID(MI.getOperand(1).getImm());
+
+  // HW has no system-generic fence; split into shared + global fences
+  if (Ord == SSI.SystemGenericID) {
+    BuildMI(*BB, &MI, DL, TII.get(PISA::fence_shared_gpu)).addImm(MemOrder);
+    BuildMI(*BB, &MI, DL, TII.get(PISA::fence_global_system)).addImm(MemOrder);
+    MI.eraseFromParent();
+    return true;
+  }
+
+  auto Entry = SSI.ID2Opcode.find(Ord);
+  if (Entry == SSI.ID2Opcode.end())
+    llvm_unreachable("unimplemented fence syncscope");
+
+  if (Entry->second == PISA::fence_subgroup)
+    BuildMI(*BB, &MI, DL, TII.get(Entry->second));
+  else
+    BuildMI(*BB, &MI, DL, TII.get(Entry->second)).addImm(MemOrder);
+  MI.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_INSERT_SUBVECTOR(MachineInstr &MI) const {
+  MachineBasicBlock *BB = MI.getParent();
+  DebugLoc DL = MI.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  auto [Dst, DstTy, Src0, Src0Ty, Src1, Src1Ty] = MI.getFirst3RegLLTs();
+  assert(DstTy.getScalarSizeInBits() == 32);
+  auto Index = MI.getOperand(3).getImm();
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *Src1RC = TRI.getRegClassFromLLT(Src1Ty);
+
+  // lookup target opcode
+  std::string OpcodeName = "insert_" + std::to_string(Index) + "_v" +
+                           std::to_string(DstTy.getNumElements()) + "i" +
+                           std::to_string(DstTy.getScalarSizeInBits()) + "_v" +
+                           std::to_string(Src1Ty.getNumElements()) + "i" +
+                           std::to_string(Src1Ty.getScalarSizeInBits()) + "_r";
+  auto *Entry = PISA::lookupName2InstrOpEntry(OpcodeName);
+  assert(Entry && "unable to find insert instruction");
+  auto Opcode = Entry->Opcode;
+
+  BuildMI(*BB, &MI, DL, TII.get(Opcode), Dst).addReg(Src0).addReg(Src1);
+
+  if (!RBI.constrainGenericRegister(Src0, *DstRC, MRI) ||
+      !RBI.constrainGenericRegister(Src1, *Src1RC, MRI) ||
+      !RBI.constrainGenericRegister(Dst, *DstRC, MRI))
+    return false;
+
+  MI.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_EXTRACT_SUBVECTOR(
+    MachineInstr &MI) const {
+  MachineBasicBlock *BB = MI.getParent();
+  DebugLoc DL = MI.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  auto [Dst, DstTy, Src, SrcTy] = MI.getFirst2RegLLTs();
+  auto Index = MI.getOperand(2).getImm();
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+
+  // If the extracted sub-vector is a nameable composite sub-register slice of
+  // the source (e.g. lanes 0,1 -> .xy or lanes 2,3 -> .zw of a 4-lane vector),
+  // emit a single sub-register COPY. Register coalescing folds it so the
+  // consumer addresses the slice directly, without gather copies. This mirrors
+  // how the low .xy half is already handled and extends it to .zw.
+  // The .xy/.zw composites are only defined on 4-lane classes (Reg*bx4); on
+  // wider sources (v5-v8, ...) they are not real sub-registers and must not be
+  // used, so restrict to <=4-lane sources.
+  unsigned CompIdx =
+      (SrcTy.isVector() && SrcTy.getNumElements() <= 4)
+          ? TRI.getCompositeSubRegIdx(DstTy.getScalarSizeInBits(), Index,
+                                      DstTy.getNumElements())
+          : 0;
+  if (CompIdx && TRI.getSubClassWithSubReg(SrcRC, CompIdx)) {
+    BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::COPY), Dst)
+        .addReg(Src, {}, CompIdx);
+    if (!RBI.constrainGenericRegister(Src, *SrcRC, MRI) ||
+        !RBI.constrainGenericRegister(Dst, *DstRC, MRI))
+      return false;
+    MI.eraseFromParent();
+    return true;
+  }
+
+  assert(DstTy.getScalarSizeInBits() == 32);
+
+  // lookup target opcode
+  std::string OpcodeName = "extract_" + std::to_string(Index) + "_v" +
+                           std::to_string(DstTy.getNumElements()) + "i" +
+                           std::to_string(DstTy.getScalarSizeInBits()) + "_v" +
+                           std::to_string(SrcTy.getNumElements()) + "i" +
+                           std::to_string(SrcTy.getScalarSizeInBits()) + "_r";
+  auto *Entry = PISA::lookupName2InstrOpEntry(OpcodeName);
+  assert(Entry && "unable to find insert instruction");
+  auto Opcode = Entry->Opcode;
+
+  BuildMI(*BB, &MI, DL, TII.get(Opcode), Dst).addReg(Src);
+
+  if (!RBI.constrainGenericRegister(Src, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(Dst, *DstRC, MRI))
+    return false;
+
+  MI.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_INSERT_VECTOR_ELT(
+    MachineInstr &MI) const {
+  MachineBasicBlock *BB = MI.getParent();
+  DebugLoc DL = MI.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  auto [Dst, DstTy, Src0, Src0Ty, Src1, Src1Ty, Idx, IdxTy] =
+      MI.getFirst4RegLLTs();
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *Src1RC = TRI.getRegClassFromLLT(Src1Ty);
+  auto *IdxRC = TRI.getRegClassFromLLT(IdxTy);
+
+  // Check if Index is a constant
+  auto IndexConst = getIConstantVRegValWithLookThrough(Idx, MRI);
+
+  if (IndexConst) {
+    // Handle constant index case
+    auto Index = IndexConst->Value.getZExtValue();
+
+    // Use insert instruction for index >= 4 or when the dest register
+    // class lacks per-element sub-register structure (e.g. v64).
+    if (Index >= 4 || DstTy.getNumElements() > 32) {
+      assert(DstTy.getScalarSizeInBits() == 32);
+      std::string OpcodeName =
+          "insert_" + std::to_string(Index) + "_v" +
+          std::to_string(DstTy.getNumElements()) + "i" +
+          std::to_string(DstTy.getScalarSizeInBits()) + "_i" +
+          std::to_string(Src1Ty.getScalarSizeInBits()) + "_r";
+      auto *Entry = PISA::lookupName2InstrOpEntry(OpcodeName);
+      assert(Entry && "unable to find insert instruction");
+      auto Opcode = Entry->Opcode;
+
+      BuildMI(*BB, &MI, DL, TII.get(Opcode), Dst).addReg(Src0).addReg(Src1);
+    } else { // use swizzle
+      auto SubRegIdx = TRI.getSubRegIdx(DstTy.getScalarSizeInBits(), Index);
+      // Constrain Src0 (the vector input) and Dst to support the subreg.
+      if (auto *NarrowRC = TRI.getSubClassWithSubReg(DstRC, SubRegIdx))
+        DstRC = NarrowRC;
+      BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::INSERT_SUBREG), Dst)
+          .addReg(Src0)
+          .addReg(Src1)
+          .addImm(SubRegIdx);
+    }
+  } else {
+    // Handle dynamic index case - use insert.dynamic instruction
+    assert(DstTy.getScalarSizeInBits() == 32);
+    std::string OpcodeName = "insert_dynamic_v" +
+                             std::to_string(DstTy.getNumElements()) + "i" +
+                             std::to_string(DstTy.getScalarSizeInBits());
+    auto *Entry = PISA::lookupName2InstrOpEntry(OpcodeName);
+    assert(Entry && "unable to find insert.dynamic instruction");
+    auto Opcode = Entry->Opcode;
+
+    BuildMI(*BB, &MI, DL, TII.get(Opcode), Dst)
+        .addReg(Src0)
+        .addReg(Src1)
+        .addReg(Idx);
+  }
+
+  if (!RBI.constrainGenericRegister(Src0, *DstRC, MRI) ||
+      !RBI.constrainGenericRegister(Src1, *Src1RC, MRI) ||
+      !RBI.constrainGenericRegister(Dst, *DstRC, MRI) ||
+      !RBI.constrainGenericRegister(Idx, *IdxRC, MRI))
+    return false;
+  MI.eraseFromParent();
+  return true;
+}
+
+bool PISAInstructionSelector::selectG_EXTRACT_VECTOR_ELT(
+    MachineInstr &MI) const {
+  MachineBasicBlock *BB = MI.getParent();
+  DebugLoc DL = MI.getDebugLoc();
+  MachineRegisterInfo &MRI = BB->getParent()->getRegInfo();
+
+  auto [Dst, DstTy, Src, SrcTy, Idx, IdxTy] = MI.getFirst3RegLLTs();
+  auto *DstRC = TRI.getRegClassFromLLT(DstTy);
+  auto *SrcRC = TRI.getRegClassFromLLT(SrcTy);
+  auto *IdxRC = TRI.getRegClassFromLLT(IdxTy);
+
+  // Check if Index is a constant
+  auto IndexConst = getIConstantVRegValWithLookThrough(Idx, MRI);
+
+  if (IndexConst) {
+    // Handle constant index case
+    auto Index = IndexConst->Value.getZExtValue();
+
+    // Use extract instruction for index >= 4 or when the source register
+    // class lacks per-element sub-register structure (e.g. v64).
+    if (Index >= 4 || SrcTy.getNumElements() > 32) {
+      assert(DstTy.getScalarSizeInBits() == 32);
+      std::string OpcodeName =
+          "extract_" + std::to_string(Index) + "_i" +
+          std::to_string(DstTy.getScalarSizeInBits()) + "_v" +
+          std::to_string(SrcTy.getNumElements()) + "i" +
+          std::to_string(SrcTy.getScalarSizeInBits()) + "_r";
+      auto *Entry = PISA::lookupName2InstrOpEntry(OpcodeName);
+      assert(Entry && "unable to find insert instruction");
+      auto Opcode = Entry->Opcode;
+
+      BuildMI(*BB, &MI, DL, TII.get(Opcode), Dst).addReg(Src);
+    } else { // use swizzle
+      auto SubRegIdx = TRI.getSubRegIdx(SrcTy.getScalarSizeInBits(), Index);
+      // Constrain Src to the subclass supporting this subreg index.
+      if (auto *CurSrcRC = MRI.getRegClassOrNull(Src)) {
+        if (auto *NarrowRC = TRI.getSubClassWithSubReg(CurSrcRC, SubRegIdx))
+          MRI.constrainRegClass(Src, NarrowRC);
+      }
+      BuildMI(*BB, &MI, DL, TII.get(TargetOpcode::COPY))
+          .addDef(Dst)
+          .addReg(Src, {}, SubRegIdx);
+    }
+  } else {
+    std::string OpcodeName = "extract_dynamic_v" +
+                             std::to_string(SrcTy.getNumElements()) + "i" +
+                             std::to_string(SrcTy.getScalarSizeInBits());
+    auto *Entry = PISA::lookupName2InstrOpEntry(OpcodeName);
+    assert(Entry && "unable to find extract.dynamic instruction");
+    auto Opcode = Entry->Opcode;
+
+    BuildMI(*BB, &MI, DL, TII.get(Opcode), Dst).addReg(Src).addReg(Idx);
+  }
+
+  if (!RBI.constrainGenericRegister(Src, *SrcRC, MRI) ||
+      !RBI.constrainGenericRegister(Dst, *DstRC, MRI) ||
+      !RBI.constrainGenericRegister(Idx, *IdxRC, MRI))
+    return false;
+  MI.eraseFromParent();
+  return true;
+}
+
+InstructionSelector::ComplexRendererFns
+PISAInstructionSelector::SelectAddr_ri(MachineOperand &Root) const {
+  Register Addr = Root.getReg();
+  Register Base;
+  int64_t Offset;
+  if (!mi_match(Addr, *MRI, m_GPtrAdd(m_Reg(Base), m_ICst(Offset)))) {
+    Base = Addr;
+    Offset = 0;
+  }
+  MachineInstr *BaseDef = getDefIgnoringCopies(Base, *MRI);
+  assert(BaseDef && "unexpected no definition for base register");
+
+  // If the first operand of the def is not a register, it means it's not a
+  // G_PTR_ADD. We can directly use the Base and Offset to render the address.
+  if (!BaseDef->getOperand(0).isReg())
+    return {{[=](MachineInstrBuilder &MIB) { MIB.addReg(Base); },
+             [=](MachineInstrBuilder &MIB) { MIB.addImm(Offset); }}};
+
+  if (BaseDef->getOpcode() == TargetOpcode::G_FRAME_INDEX)
+    return {{[=](MachineInstrBuilder &MIB) { MIB.add(BaseDef->getOperand(1)); },
+             [=](MachineInstrBuilder &MIB) { MIB.addImm(Offset); }}};
+
+  // %7  = G_CONSTANT i32 4
+  // %6  = G_PTR_ADD %0, %7
+  // %18 = G_CONSTANT i32 2
+  // %17 = G_PTR_ADD %6, %18
+  // => %17 = G_PTR_ADD %0, (%7 + %18)
+  {
+    MachineOperand &Op = BaseDef->getOperand(0);
+    assert(Op.isReg() && "unexpected non-register operand for G_PTR_ADD");
+    Register OAddr = Op.getReg();
+    Register OBase = Base;
+    int64_t OOffset = Offset;
+
+    while (mi_match(OAddr, *MRI, m_GPtrAdd(m_Reg(OBase), m_ICst(OOffset)))) {
+      BaseDef = getDefIgnoringCopies(OBase, *MRI);
+      assert(BaseDef && "unexpected no definition for base register");
+      if (BaseDef->getOpcode() == TargetOpcode::G_FRAME_INDEX)
+        break;
+      Base = OBase;
+      Offset += OOffset;
+      OAddr = BaseDef->getOperand(0).getReg();
+    }
+  }
+
+  // Assume that _rr is selected when the offset doesn't fit in 32-bit
+  assert(isInt<32>(Offset) && "unexpected offset that doesn't fit in 32-bit");
+
+  if (BaseDef->getOpcode() == TargetOpcode::G_FRAME_INDEX)
+    return {{[=](MachineInstrBuilder &MIB) { MIB.add(BaseDef->getOperand(1)); },
+             [=](MachineInstrBuilder &MIB) { MIB.addImm(Offset); }}};
+
+  return {{[=](MachineInstrBuilder &MIB) { MIB.addReg(Base); },
+           [=](MachineInstrBuilder &MIB) { MIB.addImm(Offset); }}};
+}
+
+InstructionSelector::ComplexRendererFns
+PISAInstructionSelector::SelectAddr_rr(MachineOperand &Root) const {
+  Register BaseReg;
+  int64_t ImmOff;
+  // Skip base-plus-immediate chains that fit the signed 32-bit displacement
+  // accepted by memory operands. Larger constants are matched here so the
+  // offset remains in a register instead of being expanded into an add chain.
+  Register Addr = Root.getReg();
+  int64_t TotalImmOff = 0;
+  while (mi_match(Addr, *MRI, m_GPtrAdd(m_Reg(BaseReg), m_ICst(ImmOff)))) {
+    TotalImmOff += ImmOff;
+    Addr = BaseReg;
+  }
+  if (TotalImmOff != 0 && isInt<32>(TotalImmOff))
+    return std::nullopt;
+
+  MachineInstr *MI = getOpcodeDef(TargetOpcode::G_PTR_ADD, Root.getReg(), *MRI);
+  if (!MI)
+    return std::nullopt;
+
+  Register Base = MI->getOperand(1).getReg();
+  Register Offset = MI->getOperand(2).getReg();
+  MachineInstr *Def = getDefIgnoringCopies(Base, *MRI);
+
+  if (Def->getOpcode() == TargetOpcode::G_FRAME_INDEX) {
+    return {{
+        [=](MachineInstrBuilder &MIB) { MIB.add(Def->getOperand(1)); },
+        [=](MachineInstrBuilder &MIB) {
+          MIB.addReg(Offset);
+        } // [FrameIndex + $Offset]
+    }};
+  }
+  return {{
+      [=](MachineInstrBuilder &MIB) { MIB.addReg(Base); },
+      [=](MachineInstrBuilder &MIB) { MIB.addReg(Offset); } // [$Base + $Offset]
+  }};
+}
+
+InstructionSelector::ComplexRendererFns
+PISAInstructionSelector::selectParamSlot_ii(MachineOperand &Root) const {
+  Register Addr = Root.getReg();
+  Register Base;
+  MachineInstr *BaseDef = getDefIgnoringCopies(Addr, *MRI);
+  int64_t Offset = 0, Cst;
+  while (mi_match(Addr, *MRI, m_GPtrAdd(m_Reg(Base), m_ICst(Cst)))) {
+    BaseDef = getDefIgnoringCopies(Base, *MRI);
+    Offset += Cst;
+    if (!BaseDef || BaseDef->getOpcode() == PISA::G_PISA_PARAM_SLOT)
+      break;
+    if (BaseDef->getOpcode() != TargetOpcode::G_PTR_ADD)
+      return std::nullopt;
+    Addr = BaseDef->getOperand(0).getReg();
+  }
+
+  if (!BaseDef || BaseDef->getOpcode() != PISA::G_PISA_PARAM_SLOT)
+    return std::nullopt;
+
+  unsigned Slot = BaseDef->getOperand(1).getImm();
+  const char *ArgName = nullptr;
+  if (BaseDef->getNumOperands() > 3 && BaseDef->getOperand(3).isSymbol())
+    ArgName = BaseDef->getOperand(3).getSymbolName();
+  return {{[=](MachineInstrBuilder &MIB) { MIB.addImm(Slot); },
+           [=](MachineInstrBuilder &MIB) {
+             MIB.addImm(Offset);
+             if (ArgName)
+               MIB.addExternalSymbol(ArgName);
+           }}};
+}
+
+InstructionSelector::ComplexRendererFns
+PISAInstructionSelector::selectParamSlot_ir(MachineOperand &Root) const {
+  MachineInstr *MI = Root.getParent();
+  MachineBasicBlock *BB = MI->getParent();
+  DebugLoc DL = MI->getDebugLoc();
+  Register Addr = getSrcRegIgnoringCopies(Root.getReg(), *MRI);
+  Register Base, OffsetReg;
+  MachineInstr *BaseDef = nullptr;
+  // Support matching a chain of G_PTR_ADD to retrieve the base. The offset is
+  // also calculated when visiting a G_PTR_ADD in the chain.
+  // TODO: Support matching other possible translated getelementptr sequence.
+  SmallVector<Register> OffsetRegs;
+  while (mi_match(Addr, *MRI, m_GPtrAdd(m_Reg(Base), m_Reg(OffsetReg)))) {
+    OffsetRegs.push_back(OffsetReg);
+    BaseDef = getDefIgnoringCopies(Base, *MRI);
+    if (!BaseDef || BaseDef->getOpcode() == PISA::G_PISA_PARAM_SLOT)
+      break;
+    if (BaseDef->getOpcode() != TargetOpcode::G_PTR_ADD)
+      return std::nullopt;
+    Addr = BaseDef->getOperand(0).getReg();
+  }
+  assert(!BaseDef || BaseDef->getOpcode() == PISA::G_PISA_PARAM_SLOT);
+  if (!BaseDef)
+    return std::nullopt;
+
+  // Create instructions to re-calculate offset after a match is found.
+  assert(!OffsetRegs.empty());
+  OffsetReg = OffsetRegs[0];
+  unsigned AddOpcodes[] = {PISA::iadd_64b_rr, PISA::iadd_64b_ri,
+                           PISA::iadd_64b_ir, PISA::iadd_64b_ii};
+  for (unsigned I = 1, E = OffsetRegs.size(); I != E; ++I) {
+    Register CurOffReg = OffsetRegs[I];
+    auto *RC = &PISA::Reg64bRegClass;
+    assert(MRI->getRegClassOrNull(OffsetReg) == RC ||
+           TRI.getRegClassFromLLT(MRI->getType(OffsetReg)) == RC ||
+           TRI.getRegClassFromLLT(MRI->getType(CurOffReg)) == RC);
+    auto OffsetC = getIConstantVRegValWithLookThrough(OffsetReg, *MRI);
+    auto CurOffC = getIConstantVRegValWithLookThrough(CurOffReg, *MRI);
+    Register Tmp = MRI->createVirtualRegister(RC);
+    unsigned OpIdx = OffsetC.has_value() << 1 | CurOffC.has_value();
+    auto MIB = BuildMI(*BB, MI, DL, TII.get(AddOpcodes[OpIdx]), Tmp);
+    if (OffsetC)
+      MIB.addImm(OffsetC->Value.getSExtValue());
+    else
+      MIB.addReg(OffsetReg);
+    if (CurOffC)
+      MIB.addImm(CurOffC->Value.getSExtValue());
+    else
+      MIB.addReg(CurOffReg);
+    OffsetReg = Tmp;
+  }
+
+  // Per PISA spec, register offset in ld.param address operand must be 32-bit.
+  // Truncate the 64-bit offset to 32-bit.
+  Register TruncReg = MRI->createVirtualRegister(&PISA::Reg32bRegClass);
+  BuildMI(*BB, MI, DL, TII.get(PISA::trunc_32b_64b_r), TruncReg)
+      .addReg(OffsetReg);
+  OffsetReg = TruncReg;
+
+  unsigned Slot = BaseDef->getOperand(1).getImm();
+  const char *ArgName = nullptr;
+  if (BaseDef->getNumOperands() > 3 && BaseDef->getOperand(3).isSymbol())
+    ArgName = BaseDef->getOperand(3).getSymbolName();
+  return {{[=](MachineInstrBuilder &MIB) { MIB.addImm(Slot); },
+           [=](MachineInstrBuilder &MIB) {
+             MIB.addReg(OffsetReg);
+             if (ArgName)
+               MIB.addExternalSymbol(ArgName);
+           }}};
+}
+
+namespace llvm {
+InstructionSelector *
+createPISAInstructionSelector(const PISATargetMachine &TM,
+                              const PISASubtarget &Subtarget,
+                              const RegisterBankInfo &RBI) {
+  return new PISAInstructionSelector(TM, Subtarget, RBI);
+}
+} // namespace llvm
diff --git a/llvm/lib/Target/PISA/PISASubtarget.cpp b/llvm/lib/Target/PISA/PISASubtarget.cpp
index bfc7c7390c2cc..b4a3c2f6cb4db 100644
--- a/llvm/lib/Target/PISA/PISASubtarget.cpp
+++ b/llvm/lib/Target/PISA/PISASubtarget.cpp
@@ -32,7 +32,8 @@ PISASubtarget::PISASubtarget(const Triple &TT, const std::string &CPU,
   Legalizer = std::make_unique<PISALegalizerInfo>(*this);
   RegBankInfo = std::make_unique<PISARegisterBankInfo>();
   LLT::setUseExtended(true); // enable bfloat support
-  // The instruction selector is created in a subsequent change.
+  InstSelector.reset(
+      createPISAInstructionSelector(TM, *this, *RegBankInfo.get()));
 }
 
 PISASubtarget &PISASubtarget::initSubtargetDependencies(StringRef CPU,
diff --git a/llvm/lib/Target/PISA/PISATargetMachine.cpp b/llvm/lib/Target/PISA/PISATargetMachine.cpp
index 4d68136a85ff1..9f1d150394055 100644
--- a/llvm/lib/Target/PISA/PISATargetMachine.cpp
+++ b/llvm/lib/Target/PISA/PISATargetMachine.cpp
@@ -196,8 +196,14 @@ class PISAPassConfig : public TargetPassConfig {
     return false;
   }
 
-  // Instruction selection (addGlobalInstructionSelect) is added in a
-  // subsequent change together with the PISA instruction selector.
+  bool addGlobalInstructionSelect() override {
+    if (getOptLevel() != CodeGenOptLevel::None)
+      addPass(&MachineCSELegacyID);
+    addPass(new InstructionSelect());
+    if (getOptLevel() == CodeGenOptLevel::None)
+      addPass(&ProcessImplicitDefsID);
+    return false;
+  }
 
   // PISA does not allocate physical registers.
   FunctionPass *createTargetRegisterAllocator(bool) override { return nullptr; }
diff --git a/llvm/test/CodeGen/PISA/GlobalISel/inst-select-build-vector-v64.mir b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-build-vector-v64.mir
new file mode 100644
index 0000000000000..211321dce80bc
--- /dev/null
+++ b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-build-vector-v64.mir
@@ -0,0 +1,24 @@
+# RUN: llc -march=pisa -run-pass=instruction-select %s -o - | FileCheck %s
+
+# Selecting a G_BUILD_VECTOR whose destination class (RegV64_32b) has no
+# per-element sub-registers must use the large-vector insert path, not
+# INSERT_SUBREG.
+
+---
+name:            build_vector_v64i32_undef
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: build_vector_v64i32_undef
+    ; CHECK: [[ELT:%[0-9]+]]:reg32b = functionParameter_i32 0
+    ; CHECK-NEXT: [[UNDEF:%[0-9]+]]:regv64_32b = IMPLICIT_DEF
+    ; CHECK-NEXT: [[RES:%[0-9]+]]:regv64_32b = insert_0_v64i32_i32_r [[UNDEF]], [[ELT]]
+    ; CHECK-NEXT: retValue_v64i32_r [[RES]]
+    %0:reg32b(i32) = functionParameter_i32 0
+    %5:registers(i32) = G_IMPLICIT_DEF
+    %1:registers(<64 x i32>) = G_BUILD_VECTOR %0(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32), %5(i32)
+    %4:regv64_32b(<64 x i32>) = COPY %1(<64 x i32>)
+    retValue_v64i32_r %4(<64 x i32>)
+...
diff --git a/llvm/test/CodeGen/PISA/GlobalISel/inst-select-constant-float-type.mir b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-constant-float-type.mir
new file mode 100644
index 0000000000000..58a3fca5dbcc1
--- /dev/null
+++ b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-constant-float-type.mir
@@ -0,0 +1,36 @@
+# RUN: llc -march=pisa -run-pass=instruction-select %s -o - | FileCheck %s
+
+# Verify that G_CONSTANT with an integer type bitcast to a floating-point type
+# (e.g. f64, f32) is selected correctly. This can occur when the post-legalizer
+# combiner folds a constant expression that feeds a float-typed G_BUILD_VECTOR.
+
+---
+name: test_select_constant_f64
+legalized: true
+regBankSelected: true
+tracksRegLiveness: true
+body: |
+  bb.0:
+    ; CHECK-LABEL: name: test_select_constant_f64
+    ; CHECK: %0:reg64b = mov_i64_i 0
+    ; CHECK-NEXT: retValue_i64_r %0
+    %0:registers(i64) = G_CONSTANT i64 0
+    %1:registers(f64) = G_BITCAST %0(i64)
+    %2:reg64b(f64) = COPY %1(f64)
+    retValue_i64_r %2(f64)
+...
+---
+name: test_select_constant_f32
+legalized: true
+regBankSelected: true
+tracksRegLiveness: true
+body: |
+  bb.0:
+    ; CHECK-LABEL: name: test_select_constant_f32
+    ; CHECK: %0:reg32b = mov_i32_i 1078523331
+    ; CHECK-NEXT: retValue_i32_r %0
+    %0:registers(i32) = G_CONSTANT i32 1078523331
+    %1:registers(f32) = G_BITCAST %0(i32)
+    %2:reg32b(f32) = COPY %1(f32)
+    retValue_i32_r %2(f32)
+...
diff --git a/llvm/test/CodeGen/PISA/GlobalISel/inst-select-ctlz.mir b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-ctlz.mir
new file mode 100644
index 0000000000000..821d4ca665ba7
--- /dev/null
+++ b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-ctlz.mir
@@ -0,0 +1,103 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 5
+# RUN: llc -march=pisa -run-pass=instruction-select -verify-machineinstrs %s -o - | FileCheck %s
+
+--- |
+  target triple = "pisa"
+
+  define i16 @test_ctlz_i16(i16 %a) {
+  entry:
+    %0 = tail call range(i16 0, 17) i16 @llvm.ctlz.i16(i16 %a, i1 false)
+    ret i16 %0
+  }
+
+  define i16 @test_ctlz_i16_constant() {
+  entry:
+    %0 = call range(i16 0, 17) i16 @llvm.ctlz.i16(i16 1, i1 false)
+    ret i16 %0
+  }
+
+  define i32 @test_ctlz_i32(i32 %a) {
+  entry:
+    %0 = tail call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 %a, i1 false)
+    ret i32 %0
+  }
+
+  define i32 @test_ctlz_i32_constant() {
+  entry:
+    %0 = call range(i32 0, 33) i32 @llvm.ctlz.i32(i32 1, i1 false)
+    ret i32 %0
+  }
+
+  declare i16 @llvm.ctlz.i16(i16, i1 immarg) #0
+
+  declare i32 @llvm.ctlz.i32(i32, i1 immarg) #0
+
+  attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
+...
+---
+name:            test_ctlz_i16
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.1.entry:
+
+    ; CHECK-LABEL: name: test_ctlz_i16
+    ; CHECK: %0:reg16b = functionParameter_i16 0
+    ; CHECK-NEXT: %3:reg16b = fbh_16b_r %0
+    ; CHECK-NEXT: %1:reg16b = umin_16b_ri %3, 16
+    ; CHECK-NEXT: retValue_i16_r %1
+    %0:reg16b(i16) = functionParameter_i16 0
+    %1:registers(i16) = G_CTLZ %0(i16)
+    %2:reg16b(i16) = COPY %1(i16)
+    retValue_i16_r %2(i16)
+...
+---
+name:            test_ctlz_i16_constant
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.1.entry:
+    ; CHECK-LABEL: name: test_ctlz_i16_constant
+    ; CHECK: %3:reg16b = fbh_16b_i 1
+    ; CHECK-NEXT: %1:reg16b = umin_16b_ri %3, 16
+    ; CHECK-NEXT: retValue_i16_r %1
+    %0:registers(i16) = G_CONSTANT i16 1
+    %1:registers(i16) = G_CTLZ %0(i16)
+    %2:reg16b(i16) = COPY %1(i16)
+    retValue_i16_r %2(i16)
+...
+---
+name:            test_ctlz_i32
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.1.entry:
+    ; CHECK-LABEL: name: test_ctlz_i32
+    ; CHECK: %0:reg32b = functionParameter_i32 0
+    ; CHECK-NEXT: %3:reg32b = fbh_32b_r %0
+    ; CHECK-NEXT: %1:reg32b = umin_32b_ri %3, 32
+    ; CHECK-NEXT: retValue_i32_r %1
+    %0:reg32b(i32) = functionParameter_i32 0
+    %1:registers(i32) = G_CTLZ %0(i32)
+    %2:reg32b(i32) = COPY %1(i32)
+    retValue_i32_r %2(i32)
+...
+---
+name:            test_ctlz_i32_constant
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.1.entry:
+    ; CHECK-LABEL: name: test_ctlz_i32_constant
+    ; CHECK: %3:reg32b = fbh_32b_i 1
+    ; CHECK-NEXT: %1:reg32b = umin_32b_ri %3, 32
+    ; CHECK-NEXT: retValue_i32_r %1
+    %0:registers(i32) = G_CONSTANT i32 1
+    %1:registers(i32) = G_CTLZ %0(i32)
+    %2:reg32b(i32) = COPY %1(i32)
+    retValue_i32_r %2(i32)
+...
diff --git a/llvm/test/CodeGen/PISA/GlobalISel/inst-select-cttz.mir b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-cttz.mir
new file mode 100644
index 0000000000000..dd38e4f70dc84
--- /dev/null
+++ b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-cttz.mir
@@ -0,0 +1,103 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 5
+# RUN: llc -march=pisa -run-pass=instruction-select -verify-machineinstrs %s -o - | FileCheck %s
+
+--- |
+  target triple = "pisa"
+
+  define i16 @test_cttz_i16(i16 %a) {
+  entry:
+    %0 = tail call range(i16 0, 17) i16 @llvm.cttz.i16(i16 %a, i1 false)
+    ret i16 %0
+  }
+
+  define i16 @test_cttz_i16_constant() {
+  entry:
+    %0 = call range(i16 0, 17) i16 @llvm.cttz.i16(i16 1, i1 false)
+    ret i16 %0
+  }
+
+  define i32 @test_cttz_i32(i32 %a) {
+  entry:
+    %0 = tail call range(i32 0, 33) i32 @llvm.cttz.i32(i32 %a, i1 false)
+    ret i32 %0
+  }
+
+  define i32 @test_cttz_i32_constant() {
+  entry:
+    %0 = call range(i32 0, 33) i32 @llvm.cttz.i32(i32 1, i1 false)
+    ret i32 %0
+  }
+
+  declare i16 @llvm.cttz.i16(i16, i1 immarg) #0
+
+  declare i32 @llvm.cttz.i32(i32, i1 immarg) #0
+
+  attributes #0 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
+...
+---
+name:            test_cttz_i16
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.1.entry:
+
+    ; CHECK-LABEL: name: test_cttz_i16
+    ; CHECK: %0:reg16b = functionParameter_i16 0
+    ; CHECK-NEXT: %3:reg16b = fbl_16b_r %0
+    ; CHECK-NEXT: %1:reg16b = umin_16b_ri %3, 16
+    ; CHECK-NEXT: retValue_i16_r %1
+    %0:reg16b(i16) = functionParameter_i16 0
+    %1:registers(i16) = G_CTTZ %0(i16)
+    %2:reg16b(i16) = COPY %1(i16)
+    retValue_i16_r %2(i16)
+...
+---
+name:            test_cttz_i16_constant
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.1.entry:
+    ; CHECK-LABEL: name: test_cttz_i16_constant
+    ; CHECK: %3:reg16b = fbl_16b_i 1
+    ; CHECK-NEXT: %1:reg16b = umin_16b_ri %3, 16
+    ; CHECK-NEXT: retValue_i16_r %1
+    %0:registers(i16) = G_CONSTANT i16 1
+    %1:registers(i16) = G_CTTZ %0(i16)
+    %2:reg16b(i16) = COPY %1(i16)
+    retValue_i16_r %2(i16)
+...
+---
+name:            test_cttz_i32
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.1.entry:
+    ; CHECK-LABEL: name: test_cttz_i32
+    ; CHECK: %0:reg32b = functionParameter_i32 0
+    ; CHECK-NEXT: %3:reg32b = fbl_32b_r %0
+    ; CHECK-NEXT: %1:reg32b = umin_32b_ri %3, 32
+    ; CHECK-NEXT: retValue_i32_r %1
+    %0:reg32b(i32) = functionParameter_i32 0
+    %1:registers(i32) = G_CTTZ %0(i32)
+    %2:reg32b(i32) = COPY %1(i32)
+    retValue_i32_r %2(i32)
+...
+---
+name:            test_cttz_i32_constant
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.1.entry:
+    ; CHECK-LABEL: name: test_cttz_i32_constant
+    ; CHECK: %3:reg32b = fbl_32b_i 1
+    ; CHECK-NEXT: %1:reg32b = umin_32b_ri %3, 32
+    ; CHECK-NEXT: retValue_i32_r %1
+    %0:registers(i32) = G_CONSTANT i32 1
+    %1:registers(i32) = G_CTTZ %0(i32)
+    %2:reg32b(i32) = COPY %1(i32)
+    retValue_i32_r %2(i32)
+...
diff --git a/llvm/test/CodeGen/PISA/GlobalISel/inst-select-debug-trace.mir b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-debug-trace.mir
new file mode 100644
index 0000000000000..f8a1ac1ab15c6
--- /dev/null
+++ b/llvm/test/CodeGen/PISA/GlobalISel/inst-select-debug-trace.mir
@@ -0,0 +1,41 @@
+# REQUIRES: asserts
+# RUN: llc -march=pisa -run-pass=instruction-select -verify-machineinstrs %s -o - | FileCheck %s
+# RUN: llc -march=pisa -run-pass=instruction-select -debug-only=pisa-isel %s -o /dev/null 2>&1 | FileCheck %s --check-prefix=TRACE
+
+# Selecting a G_ANYEXT (i32 -> i64) drives the generated PISA match table. This
+# test pins down two things at once:
+#
+#   1. The concrete selection result (default CHECK prefix): the G_ANYEXT is
+#      selected to the PISA 64-bit zero-extend "zext_64b_32b_r", with the source
+#      staying in a reg32b and the result in a reg64b.
+#
+#   2. The match-table executor trace (TRACE prefix). The executor emits its
+#      per-rule trace through DEBUG_WITH_TYPE(TgtExecutor::getName(), ...); for
+#      the PISA selector getName() returns the "pisa-isel" DEBUG_TYPE, so the
+#      trace only appears under -debug-only=pisa-isel. The trace must show the
+#      table switching on the incoming opcode and then, on the matching rule,
+#      mutating the opcode in place and constraining the selected operands.
+
+--- |
+  define i64 @sel_debug_trace(i32 %0) { ret i64 0 }
+...
+---
+name:            sel_debug_trace
+legalized:       true
+regBankSelected: true
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    ; CHECK-LABEL: name: sel_debug_trace
+    ; CHECK: %0:reg32b = functionParameter_i32 0
+    ; CHECK-NEXT: %1:reg64b = zext_64b_32b_r %0
+    ; CHECK-NEXT: retValue_i64_r %1
+
+    ; TRACE: GIM_SwitchOpcode(MIs[0]
+    ; TRACE: GIR_MutateOpcode(OutMIs[0], MIs[0],
+    ; TRACE-NEXT: GIR_ConstrainSelectedInstOperands(OutMIs[0])
+    ; TRACE-NEXT: GIR_Done
+    %0:reg32b(i32) = functionParameter_i32 0
+    %1:reg64b(i64) = G_ANYEXT %0(i32)
+    retValue_i64_r %1(i64)
+...



More information about the llvm-branch-commits mailing list