[llvm-branch-commits] [llvm] [6/7][PISA] Add PISA codegen pipeline and lowering passes (PR #214559)
Michal Paszkowski via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Aug 20 08:47:17 PDT 2026
https://github.com/michalpaszkowski updated https://github.com/llvm/llvm-project/pull/214559
>From 9c504125ed7cadf4899e9a1c9127c24878008ce4 Mon Sep 17 00:00:00 2001
From: Michal Paszkowski <michal.paszkowski at intel.com>
Date: Mon, 3 Aug 2026 04:11:27 -0700
Subject: [PATCH 1/3] Add PISA codegen pipeline and lowering passes
Add the IR- and MIR-level passes that make up the PISA code generation
pipeline and wire them into PISAPassConfig in PISATargetMachine:
- Intrinsic handling: PISAEmitIntrinsics, PISAExpandIntrinsics,
PISAReplaceIntrinsics
- Call/ABI lowering: PISALegalizeCalls, PISAKernelByValArgsLowering
- IR preparation: PISAPropagateNullPointers, PISALayout, PISAConstProp
- Legalization/verification: PISALegalizePredicates, PISAVerifyTypes,
PISAVerifier
- Post-selection: PISAScopeSelector, PISACacheHintSelector,
PISALegalizeSubregAccess, PISAOptimizeSubregAccess,
PISAOptimizeRedundantCopies, PISAMarkConvergentNoMerge,
PISAInsertLifetimeStart
PISAPassConfig now overrides the GlobalISel and pre/post-regalloc hooks
to run these passes, disables the machine passes that assume physical
registers, and runs the load/store vectorizer and atomic expansion in
the IR stage.
AsmPrinter is added in a follow-up change, so this commit is tested with
MIR-level -run-pass/-stop-after tests.
---
llvm/lib/Target/PISA/CMakeLists.txt | 22 +
llvm/lib/Target/PISA/PISA.h | 8 +-
.../lib/Target/PISA/PISACacheHintSelector.cpp | 104 ++
llvm/lib/Target/PISA/PISAConstProp.cpp | 58 +
llvm/lib/Target/PISA/PISAConstProp.h | 31 +
llvm/lib/Target/PISA/PISAEmitIntrinsics.cpp | 565 ++++++++++
llvm/lib/Target/PISA/PISAExpandIntrinsics.cpp | 106 ++
.../Target/PISA/PISAInsertLifetimeStart.cpp | 751 +++++++++++++
.../PISA/PISAKernelByValArgsLowering.cpp | 294 ++++++
.../Target/PISA/PISAKernelByValArgsLowering.h | 40 +
llvm/lib/Target/PISA/PISALayout.cpp | 581 ++++++++++
llvm/lib/Target/PISA/PISALegalizeCalls.cpp | 505 +++++++++
.../Target/PISA/PISALegalizePredicates.cpp | 994 ++++++++++++++++++
.../Target/PISA/PISALegalizeSubregAccess.cpp | 435 ++++++++
.../Target/PISA/PISAMarkConvergentNoMerge.cpp | 64 ++
.../PISA/PISAOptimizeRedundantCopies.cpp | 171 +++
.../Target/PISA/PISAOptimizeSubregAccess.cpp | 195 ++++
.../Target/PISA/PISAPropagateNullPointers.cpp | 171 +++
.../lib/Target/PISA/PISAReplaceIntrinsics.cpp | 99 ++
llvm/lib/Target/PISA/PISAScopeSelector.cpp | 112 ++
llvm/lib/Target/PISA/PISATargetMachine.cpp | 208 +++-
llvm/lib/Target/PISA/PISAVerifier.cpp | 301 ++++++
llvm/lib/Target/PISA/PISAVerifyTypes.cpp | 87 ++
.../CodeGen/PISA/mark-convergent-no-merge.mir | 56 +
.../test/CodeGen/PISA/pisa-scope-selector.mir | 105 ++
llvm/test/CodeGen/PISA/propagate-null.ll | 173 +++
llvm/test/CodeGen/PISA/verify-types.mir | 35 +
27 files changed, 6211 insertions(+), 60 deletions(-)
create mode 100644 llvm/lib/Target/PISA/PISACacheHintSelector.cpp
create mode 100644 llvm/lib/Target/PISA/PISAConstProp.cpp
create mode 100644 llvm/lib/Target/PISA/PISAConstProp.h
create mode 100644 llvm/lib/Target/PISA/PISAEmitIntrinsics.cpp
create mode 100644 llvm/lib/Target/PISA/PISAExpandIntrinsics.cpp
create mode 100644 llvm/lib/Target/PISA/PISAInsertLifetimeStart.cpp
create mode 100644 llvm/lib/Target/PISA/PISAKernelByValArgsLowering.cpp
create mode 100644 llvm/lib/Target/PISA/PISAKernelByValArgsLowering.h
create mode 100644 llvm/lib/Target/PISA/PISALayout.cpp
create mode 100644 llvm/lib/Target/PISA/PISALegalizeCalls.cpp
create mode 100644 llvm/lib/Target/PISA/PISALegalizePredicates.cpp
create mode 100644 llvm/lib/Target/PISA/PISALegalizeSubregAccess.cpp
create mode 100644 llvm/lib/Target/PISA/PISAMarkConvergentNoMerge.cpp
create mode 100644 llvm/lib/Target/PISA/PISAOptimizeRedundantCopies.cpp
create mode 100644 llvm/lib/Target/PISA/PISAOptimizeSubregAccess.cpp
create mode 100644 llvm/lib/Target/PISA/PISAPropagateNullPointers.cpp
create mode 100644 llvm/lib/Target/PISA/PISAReplaceIntrinsics.cpp
create mode 100644 llvm/lib/Target/PISA/PISAScopeSelector.cpp
create mode 100644 llvm/lib/Target/PISA/PISAVerifier.cpp
create mode 100644 llvm/lib/Target/PISA/PISAVerifyTypes.cpp
create mode 100644 llvm/test/CodeGen/PISA/mark-convergent-no-merge.mir
create mode 100644 llvm/test/CodeGen/PISA/pisa-scope-selector.mir
create mode 100644 llvm/test/CodeGen/PISA/propagate-null.ll
create mode 100644 llvm/test/CodeGen/PISA/verify-types.mir
diff --git a/llvm/lib/Target/PISA/CMakeLists.txt b/llvm/lib/Target/PISA/CMakeLists.txt
index a51c8cfafc728..a790f0abe2312 100644
--- a/llvm/lib/Target/PISA/CMakeLists.txt
+++ b/llvm/lib/Target/PISA/CMakeLists.txt
@@ -18,30 +18,51 @@ tablegen(LLVM PISAGenPostLegalizeGICombiner.inc -gen-global-isel-combiner
add_public_tablegen_target(PISACommonTableGen)
add_llvm_target(PISACodeGen
+ PISACacheHintSelector.cpp
PISACallLowering.cpp
+ PISAConstProp.cpp
+ PISAEmitIntrinsics.cpp
+ PISAExpandIntrinsics.cpp
PISAISelLowering.cpp
+ PISAInsertLifetimeStart.cpp
PISAInstrInfo.cpp
PISAInstructionSelector.cpp
+ PISAKernelByValArgsLowering.cpp
+ PISALegalizeCalls.cpp
+ PISALegalizePredicates.cpp
+ PISALegalizeSubregAccess.cpp
PISALegalizerInfo.cpp
PISAMCInstLower.cpp
PISAMachineFunctionInfo.cpp
+ PISAMarkConvergentNoMerge.cpp
+ PISAOptimizeRedundantCopies.cpp
+ PISAOptimizeSubregAccess.cpp
PISAPostLegalizerCombiner.cpp
PISAPreLegalizerCombiner.cpp
+ PISAPropagateNullPointers.cpp
PISARegManager.cpp
PISARegisterBankInfo.cpp
PISARegisterInfo.cpp
+ PISAReplaceIntrinsics.cpp
+ PISAScopeSelector.cpp
PISASubtarget.cpp
PISATargetMachine.cpp
PISAUtils.cpp
+ PISAVerifier.cpp
+ PISAVerifyTypes.cpp
+ PISALayout.cpp
LINK_COMPONENTS
Analysis
AsmPrinter
+ BitReader
CodeGen
CodeGenTypes
Core
Demangle
GlobalISel
+ IPO
+ Linker
MC
Passes
Scalar
@@ -52,6 +73,7 @@ add_llvm_target(PISACodeGen
TransformUtils
PISADesc
PISAInfo
+ Vectorize
ADD_TO_COMPONENT
PISA
diff --git a/llvm/lib/Target/PISA/PISA.h b/llvm/lib/Target/PISA/PISA.h
index 375208bf28350..78916267e4708 100644
--- a/llvm/lib/Target/PISA/PISA.h
+++ b/llvm/lib/Target/PISA/PISA.h
@@ -37,7 +37,7 @@ FunctionPass *createPISAPreLegalizerCombiner();
FunctionPass *createPISAPostLegalizerCombiner();
FunctionPass *createPISAReplaceIntrinsicsPass();
MachineFunctionPass *createPISALegalizePredicatesPass();
-MachineFunctionPass *createCacheHintSelectorPass();
+MachineFunctionPass *createPISACacheHintSelectorPass();
MachineFunctionPass *createPISAScopeSelectorPass();
InstructionSelector *
@@ -45,20 +45,16 @@ createPISAInstructionSelector(const PISATargetMachine &TM,
const PISASubtarget &Subtarget,
const RegisterBankInfo &RBI);
-MachineFunctionPass *
-createPISAMachineFunctionPrinterPass(const std::string &Banner,
- unsigned Counter);
FunctionPass *createPISALayoutPass();
MachineFunctionPass *createPISAVerifyTypesPass();
-void initializeCacheHintSelectorPass(PassRegistry &);
+void initializePISACacheHintSelectorPass(PassRegistry &);
void initializePISAEmitIntrinsicsPass(PassRegistry &);
void initializePISAExpandIntrinsicsPass(PassRegistry &);
void initializePISAInsertLifetimeStartPass(PassRegistry &);
void initializePISAKernelByValArgsLoweringLegacyPass(PassRegistry &);
void initializePISALegalizeCallsPass(PassRegistry &);
void initializePISALegalizeSubregAccessPass(PassRegistry &);
-void initializePISAMachineFunctionPrinterPass(PassRegistry &);
void initializePISAMarkConvergentNoMergePass(PassRegistry &);
void initializePISAOptimizeRedundantCopiesPass(PassRegistry &);
void initializePISAOptimizeSubregAccessPass(PassRegistry &);
diff --git a/llvm/lib/Target/PISA/PISACacheHintSelector.cpp b/llvm/lib/Target/PISA/PISACacheHintSelector.cpp
new file mode 100644
index 0000000000000..859ecc68160c4
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISACacheHintSelector.cpp
@@ -0,0 +1,104 @@
+//===-- PISACacheHintSelector.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
+//
+//===----------------------------------------------------------------------===//
+
+// cachehint (.cc) support
+// - LLVM IR specifies cache hints via the 'pisa.cache.ctrl' MMRA tag
+// (carried on the instruction's !mmra metadata)
+// - that value is encoded within MI's target-specific flags by
+// PISA TargetLowering::getTargetMMOFlags()
+// - PISA instructions define $cachehint input used in instruction printing
+// - this pass extract target-specific flags from MI and encodes $cachehint
+
+#include "MCTargetDesc/PISAInstPrinter.h"
+#include "PISA.h"
+
+#define GET_INSTRINFO_OPERAND_ENUM
+#include "PISAGenInstrInfo.inc"
+
+#include "llvm/CodeGen/MachineMemOperand.h"
+#include "llvm/Support/PISAAddrSpace.h"
+
+#define DEBUG_TYPE "pisa-cache-hint-selector"
+#define DEBUG_NAME "PISA Cache Hint Selector"
+
+using namespace llvm;
+using namespace llvm::PISA;
+
+namespace {
+class PISACacheHintSelector : public llvm::MachineFunctionPass {
+public:
+ static char ID;
+
+ PISACacheHintSelector() : MachineFunctionPass(ID) {}
+ StringRef getPassName() const override { return DEBUG_NAME; }
+ void getAnalysisUsage(AnalysisUsage &AU) const override;
+ bool runOnMachineFunction(MachineFunction &MF) override;
+};
+} // namespace
+
+char PISACacheHintSelector::ID = 0;
+INITIALIZE_PASS(PISACacheHintSelector, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+MachineFunctionPass *llvm::createPISACacheHintSelectorPass() {
+ return new PISACacheHintSelector();
+}
+
+void PISACacheHintSelector::getAnalysisUsage(AnalysisUsage &AU) const {
+ AU.setPreservesCFG();
+ MachineFunctionPass::getAnalysisUsage(AU);
+}
+
+static unsigned getLoadCacheHintsFromFlags(MachineMemOperand::Flags F) {
+ static_assert(MachineMemOperand::MOTargetFlag1 == 1u << 6,
+ "Unexpected flag value");
+
+ if (F & MachineMemOperand::MONonTemporal)
+ return LoadCacheControl_L1UC_L2UC_L3UC;
+
+ return (static_cast<unsigned>(F) >> 6) & 0xFU;
+}
+
+bool PISACacheHintSelector::runOnMachineFunction(MachineFunction &MF) {
+ LLVM_DEBUG(dbgs() << "Running PISA Cache Hint Selector\n");
+ for (MachineBasicBlock &MBB : MF) {
+ for (MachineInstr &MI : MBB) {
+ if (MI.memoperands_empty())
+ continue;
+
+ LLVM_DEBUG(dbgs() << "MI: " << MI);
+
+ const int CacheHintIdx =
+ PISA::getNamedOperandIdx(MI.getOpcode(), PISA::OpName::cachehint);
+ if (CacheHintIdx == -1)
+ continue;
+
+ auto *MMO = *MI.memoperands_begin();
+ if (!(MMO->isLoad() || MMO->isStore()))
+ continue;
+
+ PISAAS::AddressSpace AS =
+ static_cast<PISAAS::AddressSpace>(MMO->getAddrSpace());
+ // Shared address space exists in local memory only and
+ // cache hints make no sense for it.
+ if (AS == PISAAS::AddressSpace::SHARED)
+ continue;
+
+ // If the cache hint operand is already set, skip it.
+ if (MI.getOperand(CacheHintIdx).getImm() != 0)
+ continue;
+
+ unsigned int CacheHint = getLoadCacheHintsFromFlags(MMO->getFlags());
+ if (CacheHint != 1 && (MMO->isStore() || CacheHint != 15))
+ MI.getOperand(CacheHintIdx).setImm(CacheHint);
+
+ LLVM_DEBUG(dbgs() << "Updated MI: " << MI);
+ }
+ }
+
+ return false;
+}
diff --git a/llvm/lib/Target/PISA/PISAConstProp.cpp b/llvm/lib/Target/PISA/PISAConstProp.cpp
new file mode 100644
index 0000000000000..35c7b7b0c680a
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAConstProp.cpp
@@ -0,0 +1,58 @@
+//===-- PISAConstProp.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 "PISAConstProp.h"
+#include "llvm/IR/Constants.h"
+
+#include <cmath>
+
+namespace llvm {
+namespace PISA {
+namespace ConstProp {
+
+Constant *foldFrcp(ConstantFP *C0) {
+ auto APF = C0->getValueAPF();
+ double C0value = C0->getType()->isFloatTy()
+ ? static_cast<double>(APF.convertToFloat())
+ : APF.convertToDouble();
+ if (C0->isNaN())
+ return ConstantFP::getQNaN(C0->getType(), C0->isNegative());
+ if (!C0value)
+ return ConstantFP::getInfinity(C0->getType(), C0->isNegative());
+
+ return ConstantFP::get(C0->getType(), 1. / C0value);
+}
+
+Constant *foldFrsqrt(ConstantFP *C0) {
+ auto APF = C0->getValueAPF();
+ double C0value = C0->getType()->isFloatTy()
+ ? static_cast<double>(APF.convertToFloat())
+ : APF.convertToDouble();
+ if (C0->isNaN() || C0value < 0)
+ return ConstantFP::getQNaN(C0->getType(), C0->isNegative());
+ if (!C0value)
+ return ConstantFP::getInfinity(C0->getType());
+
+ return ConstantFP::get(C0->getType(), sqrt(1. / C0value));
+}
+
+Constant *foldFtanh(ConstantFP *C0) {
+ auto APF = C0->getValueAPF();
+ double C0value = C0->getType()->isFloatTy()
+ ? static_cast<double>(APF.convertToFloat())
+ : APF.convertToDouble();
+ if (C0->isNaN())
+ return ConstantFP::getQNaN(C0->getType(), C0->isNegative());
+
+ const double Th = tanh(C0value);
+ return ConstantFP::get(C0->getType(), Th);
+}
+
+} // namespace ConstProp
+} // namespace PISA
+} // namespace llvm
diff --git a/llvm/lib/Target/PISA/PISAConstProp.h b/llvm/lib/Target/PISA/PISAConstProp.h
new file mode 100644
index 0000000000000..e978732874a0e
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAConstProp.h
@@ -0,0 +1,31 @@
+//===-- PISAConstProp.h ---------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_PISA_PISACONSTPROP_H
+#define LLVM_LIB_TARGET_PISA_PISACONSTPROP_H
+
+#include "llvm/IR/Constants.h"
+
+namespace llvm {
+namespace PISA {
+///
+/// Namespace implementing constant folding/propagation (PISA own additional
+/// functionality to common LLVM constant folding/propagation).
+///
+namespace ConstProp {
+// Frcp (1/x)
+Constant *foldFrcp(ConstantFP *C0);
+// Frsqrt (1/sqrt(x))
+Constant *foldFrsqrt(ConstantFP *C0);
+// Hyperbolic tangent
+Constant *foldFtanh(ConstantFP *C0);
+} // namespace ConstProp
+} // namespace PISA
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_PISA_PISACONSTPROP_H
diff --git a/llvm/lib/Target/PISA/PISAEmitIntrinsics.cpp b/llvm/lib/Target/PISA/PISAEmitIntrinsics.cpp
new file mode 100644
index 0000000000000..8ee055a35be63
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAEmitIntrinsics.cpp
@@ -0,0 +1,565 @@
+//===-- PISAEmitIntrinsics.cpp - emit PISA intrinsics ---------------------===//
+//
+// 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 "PISA.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstVisitor.h"
+#include "llvm/IR/IntrinsicsPISA.h"
+
+#define DEBUG_TYPE "pisa-emit-intrinsics"
+#define DEBUG_NAME "PISA emit intrinsics"
+
+using namespace llvm;
+
+namespace llvm {
+void initializePISAEmitIntrinsicsPass(PassRegistry &);
+} // namespace llvm
+
+// Helper that answers two related questions about the SNaN/QNaN payload of
+// a value, both used to decide when @llvm.fabs is safe to lower to the
+// PISA hardware fabs (which quiets SNaN inputs):
+//
+// * cannotBeSNaN(V) - def-side walk: is V's bit pattern provably never a
+// signaling NaN? True for IEEE arithmetic results (always produce QNaN),
+// integer-to-FP conversions, nnan-flagged producers, non-signaling
+// constants, nofpclass(snan) arguments / call results, and sign-only
+// recursion through fabs / fneg / copysign.
+//
+// * allUsesAreSNaNInsensitive(V) - use-side walk: do every transitive
+// consumer of V treat an SNaN payload the same as a QNaN payload?
+// True when all uses reach fcmp (boolean result), maxnum / minnum /
+// maximumnum / minimumnum (which quiet SNaN internally per IEEE
+// 754-2019), or further sign-only / lane-shuffle ops feeding the same
+// set.
+//
+// Either condition alone is sufficient to rewrite @llvm.fabs to
+// @llvm.pisa.fabs. The two queries are conceptually dual (def-side vs
+// use-side) and share the same recursion infrastructure, depth limit and
+// cache, so they live in a single class. Per-Value caching is important:
+// fabs calls in straight-line code often share common subexpressions, and
+// extractelement / lane-shuffle chains naturally revisit the same value.
+//
+// Other operations (arithmetic-as-a-consumer, bitcast-to-int, store, ret,
+// opaque calls, llvm.maximum / llvm.minimum, etc.) are conservatively
+// rejected. IEEE arithmetic does in fact quiet SNaN at the output, but
+// keeping the use-side rule narrow protects the optimisation under
+// constrained FP environments and integer-bit observers.
+class SNaNPayloadAnalysis {
+ static constexpr unsigned MaxDepth = 8;
+
+ // One cache entry per Value, holding the result of either or both queries
+ // once they've been computed. std::optional makes the "not yet computed"
+ // state explicit, so a Value asked only the def-side question never
+ // populates the use-side answer (and vice versa).
+ struct CachedResult {
+ std::optional<bool> CannotBeSNaN;
+ std::optional<bool> AllUsesInsensitive;
+ };
+ DenseMap<const Value *, CachedResult> Cache;
+
+ // --- Def-side computation -------------------------------------------------
+ bool computeCannotBeSNaN(const Value *V, unsigned Depth) {
+ if (Depth > MaxDepth)
+ return false;
+
+ // Constants: check the bit pattern directly.
+ if (const auto *CFP = dyn_cast<ConstantFP>(V))
+ return !CFP->getValueAPF().isSignaling();
+
+ if (const auto *C = dyn_cast<Constant>(V)) {
+ if (isa<UndefValue>(C) || isa<PoisonValue>(C))
+ return true;
+ if (auto *VTy = dyn_cast<FixedVectorType>(C->getType())) {
+ for (unsigned I = 0, E = VTy->getNumElements(); I != E; ++I) {
+ const Constant *Elt = C->getAggregateElement(I);
+ // getAggregateElement may return null for constants it can't split
+ // (e.g. some ConstantExpr forms). Be conservative.
+ if (!Elt || !computeCannotBeSNaN(Elt, Depth + 1))
+ return false;
+ }
+ return true;
+ }
+ // Other constants (ConstantExpr, etc.); be conservative.
+ return false;
+ }
+
+ // Function arguments / call results with a `nofpclass(snan)` attribute.
+ if (const auto *Arg = dyn_cast<Argument>(V))
+ return (Arg->getNoFPClass() & fcSNan) == fcSNan;
+ if (const auto *CB = dyn_cast<CallBase>(V))
+ if ((CB->getRetNoFPClass() & fcSNan) == fcSNan)
+ return true;
+
+ // nnan implies no NaN at all, hence no signaling NaN.
+ if (const auto *FPOp = dyn_cast<FPMathOperator>(V))
+ if (FPOp->hasNoNaNs())
+ return true;
+
+ const auto *I = dyn_cast<Instruction>(V);
+ if (!I)
+ return false;
+
+ switch (I->getOpcode()) {
+ // IEEE 754 arithmetic ops produce QNaN, never SNaN.
+ case Instruction::FAdd:
+ case Instruction::FSub:
+ case Instruction::FMul:
+ case Instruction::FDiv:
+ case Instruction::FRem:
+ // PISA frem is legalized to fdiv+fma+selects, all IEEE arithmetic that
+ // produces QNaN whenever it produces a NaN.
+ return true;
+ // Integer-to-FP conversions can never produce a NaN.
+ case Instruction::SIToFP:
+ case Instruction::UIToFP:
+ return true;
+ // FNeg only flips the sign bit; SNaN-ness is preserved.
+ case Instruction::FNeg:
+ return cannotBeSNaN(I->getOperand(0), Depth + 1);
+ case Instruction::Select:
+ return cannotBeSNaN(I->getOperand(1), Depth + 1) &&
+ cannotBeSNaN(I->getOperand(2), Depth + 1);
+ case Instruction::ExtractElement:
+ return cannotBeSNaN(I->getOperand(0), Depth + 1);
+ case Instruction::InsertElement:
+ return cannotBeSNaN(I->getOperand(0), Depth + 1) &&
+ cannotBeSNaN(I->getOperand(1), Depth + 1);
+ case Instruction::Call: {
+ const auto *II = dyn_cast<IntrinsicInst>(I);
+ if (!II)
+ return false;
+ switch (II->getIntrinsicID()) {
+ // Generic LLVM math intrinsics produce QNaN per IEEE 754.
+ case Intrinsic::fma:
+ case Intrinsic::fmuladd:
+ case Intrinsic::sqrt:
+ case Intrinsic::sin:
+ case Intrinsic::cos:
+ case Intrinsic::tan:
+ case Intrinsic::exp:
+ case Intrinsic::exp2:
+ case Intrinsic::log:
+ case Intrinsic::log2:
+ case Intrinsic::log10:
+ case Intrinsic::pow:
+ case Intrinsic::powi:
+ case Intrinsic::minnum:
+ case Intrinsic::maxnum:
+ case Intrinsic::minimum:
+ case Intrinsic::maximum:
+ case Intrinsic::canonicalize:
+ case Intrinsic::pisa_fabs:
+ case Intrinsic::pisa_fadd:
+ case Intrinsic::pisa_fsub:
+ case Intrinsic::pisa_fmul:
+ case Intrinsic::pisa_fma:
+ case Intrinsic::pisa_fdiv_rnd:
+ case Intrinsic::pisa_pow_rnd:
+ case Intrinsic::pisa_fsqrt_rnd:
+ case Intrinsic::pisa_frnd_rnd:
+ case Intrinsic::pisa_frcp:
+ case Intrinsic::pisa_frcp_rnd:
+ case Intrinsic::pisa_frsqrt:
+ case Intrinsic::pisa_sin_rnd:
+ case Intrinsic::pisa_cos_rnd:
+ case Intrinsic::pisa_tanh_rnd:
+ case Intrinsic::pisa_exp_rnd:
+ case Intrinsic::pisa_exp2_rnd:
+ case Intrinsic::pisa_log_rnd:
+ case Intrinsic::pisa_log2_rnd:
+ case Intrinsic::pisa_log10_rnd:
+ case Intrinsic::pisa_fmin_sat:
+ case Intrinsic::pisa_fmax_sat:
+ case Intrinsic::pisa_ftrunc:
+ case Intrinsic::pisa_sitofp:
+ case Intrinsic::pisa_uitofp:
+ return true;
+ // IR fabs / copysign preserve the magnitude operand's NaN payload, so
+ // SNaN-ness is preserved; recurse into the source.
+ case Intrinsic::fabs:
+ case Intrinsic::copysign:
+ return cannotBeSNaN(II->getArgOperand(0), Depth + 1);
+ default:
+ return false;
+ }
+ }
+ default:
+ return false;
+ }
+ }
+
+ // --- Use-side computation -------------------------------------------------
+ bool computeAllUsesInsensitive(const Value *V, unsigned Depth) {
+ if (Depth > MaxDepth)
+ return false;
+ if (V->use_empty())
+ // No observable users at all - vacuously insensitive.
+ return true;
+ for (const User *U : V->users()) {
+ const auto *I = dyn_cast<Instruction>(U);
+ if (!I || !isUserInsensitive(*I, Depth + 1))
+ return false;
+ }
+ return true;
+ }
+
+ bool isUserInsensitive(const Instruction &I, unsigned Depth) {
+ switch (I.getOpcode()) {
+ case Instruction::FCmp:
+ return true;
+ case Instruction::ExtractElement:
+ case Instruction::InsertElement:
+ case Instruction::ShuffleVector:
+ return allUsesAreSNaNInsensitive(&I, Depth);
+ case Instruction::Call: {
+ const auto *II = dyn_cast<IntrinsicInst>(&I);
+ if (!II)
+ return false;
+ switch (II->getIntrinsicID()) {
+ // Per IEEE 754-2019 maxnum/minnum treat SNaN as missing data, quieting
+ // it internally. The distinction in the result is gone.
+ case Intrinsic::maxnum:
+ case Intrinsic::minnum:
+ case Intrinsic::maximumnum:
+ case Intrinsic::minimumnum:
+ return true;
+ // Constrained maxnum/minnum and non-signaling fcmp: the data result
+ // is insensitive to SNaN vs QNaN, but under fpexcept.strict /
+ // fpexcept.maytrap the exception flags differ (IEEE 754 mandates
+ // FE_INVALID for SNaN operands). Only safe when exceptions are
+ // ignored.
+ case Intrinsic::experimental_constrained_maxnum:
+ case Intrinsic::experimental_constrained_minnum:
+ case Intrinsic::experimental_constrained_fcmp: {
+ const auto *CFP = cast<ConstrainedFPIntrinsic>(II);
+ auto EB = CFP->getExceptionBehavior();
+ return EB && *EB == fp::ebIgnore;
+ }
+ // Sign-only operations are bit-preserving on the payload, so the
+ // distinction propagates through; recurse to the consumers.
+ case Intrinsic::fabs:
+ case Intrinsic::pisa_fabs:
+ case Intrinsic::copysign:
+ return allUsesAreSNaNInsensitive(II, Depth);
+ // experimental.constrained.fcmps - the signaling comparison signals
+ // FE_INVALID on *any* NaN (SNaN or QNaN), so quieting makes no
+ // difference to exception behavior.
+ case Intrinsic::experimental_constrained_fcmps:
+ return true;
+ default:
+ return false;
+ }
+ }
+ default:
+ return false;
+ }
+ }
+
+public:
+ // Query whether V is provably never a signaling NaN. Result is cached.
+ bool cannotBeSNaN(const Value *V, unsigned Depth = 0) {
+ auto &Slot = Cache[V];
+ if (Slot.CannotBeSNaN)
+ return *Slot.CannotBeSNaN;
+ bool Result = computeCannotBeSNaN(V, Depth);
+ // Re-fetch: recursion may have inserted intermediate entries and
+ // invalidated the earlier reference.
+ Cache[V].CannotBeSNaN = Result;
+ return Result;
+ }
+
+ // Query whether all transitive users of V are SNaN-payload-insensitive.
+ // Result is cached.
+ bool allUsesAreSNaNInsensitive(const Value *V, unsigned Depth = 0) {
+ auto &Slot = Cache[V];
+ if (Slot.AllUsesInsensitive)
+ return *Slot.AllUsesInsensitive;
+ bool Result = computeAllUsesInsensitive(V, Depth);
+ // Re-fetch: recursion may have inserted intermediate entries and
+ // invalidated the earlier reference.
+ Cache[V].AllUsesInsensitive = Result;
+ return Result;
+ }
+};
+
+namespace {
+class PISAEmitIntrinsics : public FunctionPass,
+ public InstVisitor<PISAEmitIntrinsics> {
+
+ IRBuilder<> *IRB = nullptr;
+ bool Changed = false;
+ SNaNPayloadAnalysis SNaNAnalysis;
+
+public:
+ static char ID;
+ PISAEmitIntrinsics() : FunctionPass(ID) {
+ initializePISAEmitIntrinsicsPass(*PassRegistry::getPassRegistry());
+ }
+
+ bool runOnFunction(Function &F) override;
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.setPreservesCFG();
+ }
+
+ void visitIntrinsicInst(IntrinsicInst &I);
+};
+} // namespace
+
+char PISAEmitIntrinsics::ID = 0;
+INITIALIZE_PASS(PISAEmitIntrinsics, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+// given a metadata node, return CmpInst::Predicate representation
+static CmpInst::Predicate getPredicateFromName(StringRef Name) {
+ CmpInst::Predicate Pred;
+ Pred = StringSwitch<CmpInst::Predicate>(Name)
+ .Case("false", CmpInst::FCMP_FALSE)
+ .Case("oeq", CmpInst::FCMP_OEQ)
+ .Case("ogt", CmpInst::FCMP_OGT)
+ .Case("oge", CmpInst::FCMP_OGE)
+ .Case("olt", CmpInst::FCMP_OLT)
+ .Case("ole", CmpInst::FCMP_OLE)
+ .Case("one", CmpInst::FCMP_ONE)
+ .Case("ord", CmpInst::FCMP_ORD)
+ .Case("uno", CmpInst::FCMP_UNO)
+ .Case("ueq", CmpInst::FCMP_UEQ)
+ .Case("ugt", CmpInst::FCMP_UGT)
+ .Case("uge", CmpInst::FCMP_UGE)
+ .Case("ult", CmpInst::FCMP_ULT)
+ .Case("ule", CmpInst::FCMP_ULE)
+ .Case("une", CmpInst::FCMP_UNE)
+ .Case("true", CmpInst::FCMP_TRUE)
+ .Default(CmpInst::BAD_FCMP_PREDICATE);
+ return Pred;
+}
+
+// replace one intrinsic with another, stripping some args
+static void replaceIntrinsicWith(IntrinsicInst *II, Intrinsic::ID IID,
+ unsigned IgnoreLast) {
+ IRBuilder<> IRB(II);
+
+ SmallVector<Value *, 4> Args;
+ // NOLINTNEXTLINE(llvm-qualified-auto)
+ for (auto It = II->arg_begin(); It != (II->arg_end() - IgnoreLast); ++It)
+ Args.push_back(*It);
+ auto *NewI = IRB.CreateIntrinsic(II->getType(), IID, Args,
+ isa<FPMathOperator>(II) ? II : nullptr);
+ II->replaceAllUsesWith(NewI);
+ II->eraseFromParent();
+}
+
+// replace metadata with equivalent numerical representation
+static void replaceRoundingModeMD(IntrinsicInst *II, Intrinsic::ID IID,
+ bool IsConstrained) {
+ IRBuilder<> IRB(II);
+ auto *ImmTy = Type::getInt8Ty(II->getContext());
+
+ // constrained have roundmode, exception on the end
+ // non-constrained only have roundmode on the end
+ auto IgnoreLast = IsConstrained ? 2 : 1;
+ auto *MD =
+ cast<MetadataAsValue>(II->getArgOperand(II->arg_size() - IgnoreLast))
+ ->getMetadata();
+ auto RoundMode = convertStrToRoundingMode(cast<MDString>(MD)->getString());
+ // PISA has no runtime rounding-mode control. Map round.dynamic to the
+ // IEEE 754 default (round-to-nearest-even).
+ if (!RoundMode || *RoundMode == RoundingMode::Dynamic)
+ RoundMode = RoundingMode::NearestTiesToEven;
+ Constant *RoundVal = ConstantInt::get(ImmTy, (unsigned)*RoundMode);
+
+ SmallVector<Value *, 4> Args;
+ // NOLINTNEXTLINE(llvm-qualified-auto)
+ for (auto It = II->arg_begin(); It != (II->arg_end() - IgnoreLast); ++It)
+ Args.push_back(*It);
+ Args.push_back(RoundVal);
+ switch (IID) {
+ default:
+ break;
+ case Intrinsic::pisa_fadd:
+ case Intrinsic::pisa_fsub:
+ case Intrinsic::pisa_fmul:
+ case Intrinsic::pisa_fma:
+ case Intrinsic::pisa_ftrunc:
+ case Intrinsic::pisa_uitofp:
+ case Intrinsic::pisa_sitofp:
+ Args.push_back(ConstantInt::getFalse(II->getContext())); // saturation
+ break;
+ }
+ auto *NewI = IRB.CreateIntrinsic(II->getType(), IID, Args,
+ isa<FPMathOperator>(II) ? II : nullptr);
+ II->replaceAllUsesWith(NewI);
+ II->eraseFromParent();
+}
+
+void PISAEmitIntrinsics::visitIntrinsicInst(IntrinsicInst &I) {
+ IRB->SetInsertPoint(&I);
+ auto II = I.getIntrinsicID();
+ switch (II) {
+// intrinsics with rounding mode (e.g. llvm.experimental.constrained*) are
+// mapped into PISA equivalents, with rounding mode MD being mapped to an
+// equivalent immediate values. ISel maps these to proper instructions.
+#define REPLACE_ROUNDMODE(from, to, constrained) \
+ case from: { \
+ replaceRoundingModeMD(&I, to, constrained); \
+ Changed = true; \
+ break; \
+ }
+ REPLACE_ROUNDMODE(Intrinsic::pisa_fptoui_md, Intrinsic::pisa_fptoui_rnd,
+ false)
+ REPLACE_ROUNDMODE(Intrinsic::pisa_fptosi_md, Intrinsic::pisa_fptosi_rnd,
+ false)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_fadd,
+ Intrinsic::pisa_fadd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_fsub,
+ Intrinsic::pisa_fsub, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_fmul,
+ Intrinsic::pisa_fmul, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_fdiv,
+ Intrinsic::pisa_fdiv_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_uitofp,
+ Intrinsic::pisa_uitofp, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_sitofp,
+ Intrinsic::pisa_sitofp, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_sqrt,
+ Intrinsic::pisa_fsqrt_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_fma,
+ Intrinsic::pisa_fma, true)
+ // TODO: check if we need to split into mul+add
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_fmuladd,
+ Intrinsic::pisa_fma, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_fptrunc,
+ Intrinsic::pisa_ftrunc, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_rint,
+ Intrinsic::pisa_frnd_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_log2,
+ Intrinsic::pisa_log2_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_sin,
+ Intrinsic::pisa_sin_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_cos,
+ Intrinsic::pisa_cos_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_tanh,
+ Intrinsic::pisa_tanh_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_exp,
+ Intrinsic::pisa_exp_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_exp2,
+ Intrinsic::pisa_exp2_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_log,
+ Intrinsic::pisa_log_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_log10,
+ Intrinsic::pisa_log10_rnd, true)
+ REPLACE_ROUNDMODE(Intrinsic::experimental_constrained_pow,
+ Intrinsic::pisa_pow_rnd, true)
+#undef REPLACE_ROUNDMODE
+#define REPLACE_INTRINSIC(from, to, strip) \
+ case from: { \
+ replaceIntrinsicWith(&I, to, strip); \
+ Changed = true; \
+ break; \
+ }
+ REPLACE_INTRINSIC(Intrinsic::experimental_constrained_minnum,
+ Intrinsic::minnum, 1)
+ REPLACE_INTRINSIC(Intrinsic::experimental_constrained_maxnum,
+ Intrinsic::maxnum, 1)
+ REPLACE_INTRINSIC(Intrinsic::experimental_constrained_floor,
+ Intrinsic::floor, 1)
+ REPLACE_INTRINSIC(Intrinsic::experimental_constrained_ceil, Intrinsic::ceil,
+ 1)
+ REPLACE_INTRINSIC(Intrinsic::experimental_constrained_round,
+ Intrinsic::round, 1)
+ REPLACE_INTRINSIC(Intrinsic::experimental_constrained_trunc,
+ Intrinsic::trunc, 1)
+#undef REPLACE_INTRINSIC
+ case Intrinsic::experimental_constrained_fptosi: {
+ // exceptions are not supported
+ auto *NewI = IRB->CreateFPToSI(I.getOperand(0), I.getType());
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ Changed = true;
+ } break;
+ case Intrinsic::experimental_constrained_fptoui: {
+ // exceptions are not supported
+ auto *NewI = IRB->CreateFPToUI(I.getOperand(0), I.getType());
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ Changed = true;
+ } break;
+ case Intrinsic::experimental_constrained_fpext: {
+ // exceptions are not supported
+ auto *NewI = IRB->CreateFPExt(I.getOperand(0), I.getType());
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ Changed = true;
+ } break;
+ case Intrinsic::experimental_constrained_fcmp: {
+ // exceptions are not supported
+ auto *MD = cast<MetadataAsValue>(I.getOperand(2))->getMetadata();
+ auto Predicate = getPredicateFromName(cast<MDString>(MD)->getString());
+ auto *NewI = IRB->CreateFCmp(Predicate, I.getOperand(0), I.getOperand(1));
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ Changed = true;
+ } break;
+ case Intrinsic::experimental_constrained_fcmps: {
+ // exceptions are not supported
+ auto *MD = cast<MetadataAsValue>(I.getOperand(2))->getMetadata();
+ auto Predicate = getPredicateFromName(cast<MDString>(MD)->getString());
+ auto *NewI = IRB->CreateFCmpS(Predicate, I.getOperand(0), I.getOperand(1));
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ Changed = true;
+ } break;
+ case Intrinsic::experimental_constrained_frem: {
+ // https://llvm.org/docs/LangRef.html#llvm-experimental-constrained-frem-intrinsic
+ // ... rounding mode argument has no effect ...
+ auto *NewI = IRB->CreateFRem(I.getOperand(0), I.getOperand(1));
+ if (auto *CastedNewI = dyn_cast<Instruction>(NewI))
+ CastedNewI->setFastMathFlags(I.getFastMathFlags());
+ I.replaceAllUsesWith(NewI);
+ I.eraseFromParent();
+ Changed = true;
+ } break;
+ case Intrinsic::fabs: {
+ // PISA fabs and IEEE fabs differ only on signaling NaN inputs: PISA fabs
+ // quiets the NaN, while IEEE fabs preserves the payload. The cheaper
+ // PISA fabs is safe whenever the SNaN-vs-QNaN distinction cannot affect
+ // any observable result at this call site. Two sufficient conditions:
+ //
+ // * cannotBeSNaN(&I) - the fabs result is provably never an SNaN.
+ // For a fabs call this folds in the nnan flag on the call itself
+ // (FPMathOperator::hasNoNaNs) and recurses into the operand's
+ // def chain (IEEE arithmetic, sitofp, nnan producers, ...).
+ // * allUsesAreSNaNInsensitive(&I) - no consumer of the fabs result
+ // observes the SNaN payload (uses bottom out in maxnum/minnum,
+ // fcmp, or lane-shuffle/sign-only chains into the same).
+ if (SNaNAnalysis.cannotBeSNaN(&I) ||
+ SNaNAnalysis.allUsesAreSNaNInsensitive(&I)) {
+ replaceIntrinsicWith(&I, Intrinsic::pisa_fabs, 0);
+ Changed = true;
+ }
+ } break;
+ default:
+ break;
+ }
+}
+
+bool PISAEmitIntrinsics::runOnFunction(Function &Func) {
+ SNaNAnalysis = SNaNPayloadAnalysis();
+ IRBuilder<> LocalIRB(Func.getContext());
+
+ IRB = &LocalIRB;
+ Changed = false;
+
+ visit(Func);
+
+ return Changed;
+}
+
+FunctionPass *llvm::createPISAEmitIntrinsicsPass() {
+ return new PISAEmitIntrinsics();
+}
diff --git a/llvm/lib/Target/PISA/PISAExpandIntrinsics.cpp b/llvm/lib/Target/PISA/PISAExpandIntrinsics.cpp
new file mode 100644
index 0000000000000..1b6572e349e4b
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAExpandIntrinsics.cpp
@@ -0,0 +1,106 @@
+//===-- PISAExpandIntrinsics.cpp - modify function signatures -------------===//
+//
+// 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 "PISA.h"
+#include "PISASubtarget.h"
+#include "PISATargetMachine.h"
+#include "llvm/Analysis/TargetTransformInfo.h"
+#include "llvm/CodeGen/TargetPassConfig.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Target/TargetMachine.h"
+#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
+
+#define DEBUG_TYPE "pisa-expand-intrinsics"
+#define DEBUG_NAME "PISA expand intrinsics"
+
+using namespace llvm;
+
+namespace {
+
+class PISAExpandIntrinsics : public FunctionPass {
+public:
+ static char ID;
+
+ PISAExpandIntrinsics() : FunctionPass(ID) {}
+ StringRef getPassName() const override { return DEBUG_NAME; }
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.addRequired<TargetPassConfig>();
+ AU.addRequired<TargetTransformInfoWrapperPass>();
+ }
+ bool runOnFunction(Function &F) override;
+};
+
+} // namespace
+
+char PISAExpandIntrinsics::ID = 0;
+
+INITIALIZE_PASS_BEGIN(PISAExpandIntrinsics, DEBUG_TYPE, DEBUG_NAME, false,
+ false)
+INITIALIZE_PASS_DEPENDENCY(TargetPassConfig)
+INITIALIZE_PASS_DEPENDENCY(TargetTransformInfoWrapperPass)
+INITIALIZE_PASS_END(PISAExpandIntrinsics, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+bool PISAExpandIntrinsics::runOnFunction(Function &F) {
+ auto &TPC = getAnalysis<TargetPassConfig>();
+ auto &TM = TPC.getTM<TargetMachine>();
+ const auto *ST = TM.getSubtargetImpl(F);
+ const auto *TLI = ST->getTargetLowering();
+
+ SmallVector<MemIntrinsic *> MemIntrs;
+ for (auto &I : instructions(F)) {
+ auto *II = dyn_cast<IntrinsicInst>(&I);
+ if (!II)
+ continue;
+ if (II->getIntrinsicID() == Intrinsic::memset ||
+ II->getIntrinsicID() == Intrinsic::memcpy ||
+ II->getIntrinsicID() == Intrinsic::memmove) {
+ MemIntrinsic *MI = cast<MemIntrinsic>(II);
+ uint64_t Len = ~0, Limit = 0;
+ if (ConstantInt *LenCI = dyn_cast<ConstantInt>(MI->getLength()))
+ Len = LenCI->getZExtValue();
+ switch (II->getIntrinsicID()) {
+ case Intrinsic::memset:
+ Limit = TLI->getMaxStoresPerMemset(F.hasOptSize());
+ break;
+ case Intrinsic::memcpy:
+ Limit = TLI->getMaxStoresPerMemcpy(F.hasOptSize());
+ break;
+ case Intrinsic::memmove:
+ Limit = TLI->getMaxStoresPerMemmove(F.hasOptSize());
+ break;
+ }
+ if (Len > Limit)
+ MemIntrs.push_back(MI);
+ }
+ }
+
+ bool Changed = false;
+
+ // Expand llvm.mem* intrinsics to a loop
+ const TargetTransformInfo &TTI =
+ getAnalysis<TargetTransformInfoWrapperPass>().getTTI(F);
+ for (MemIntrinsic *MemCall : MemIntrs) {
+ if (auto *Memcpy = dyn_cast<MemCpyInst>(MemCall))
+ expandMemCpyAsLoop(Memcpy, TTI);
+ else if (auto *Memmove = dyn_cast<MemMoveInst>(MemCall))
+ expandMemMoveAsLoop(Memmove, TTI);
+ else if (auto *Memset = dyn_cast<MemSetInst>(MemCall))
+ expandMemSetAsLoop(Memset);
+ Changed = true;
+ MemCall->eraseFromParent();
+ }
+
+ return Changed;
+}
+
+FunctionPass *llvm::createPISAExpandIntrinsicsPass() {
+ return new PISAExpandIntrinsics();
+}
diff --git a/llvm/lib/Target/PISA/PISAInsertLifetimeStart.cpp b/llvm/lib/Target/PISA/PISAInsertLifetimeStart.cpp
new file mode 100644
index 0000000000000..a6290af9a9225
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAInsertLifetimeStart.cpp
@@ -0,0 +1,751 @@
+//=== PISAInsertLifetimeStart.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Inserts "lifetime.start %R;" markers at loop headers.
+//
+// What the marker means
+// ---------------------
+// A "lifetime.start %R;" at a loop header H asserts that the value register R
+// holds on entry to the loop from the back-edge (the value carried from the
+// previous iteration) is never used. In other words R is NOT loop-carried: each
+// iteration produces a fresh R before reading it, so no value flows from one
+// iteration to the next through R. Without the marker R looks alive across
+// iterations and must be conservatively preserved; with it, R may be treated as
+// undefined on entry to the loop.
+//
+// The pattern we detect
+// ---------------------
+// We look for a register R with:
+// - an undefined seed before the loop (an IMPLICIT_DEF, so R starts the loop
+// with no real value), and
+// - inside the loop, a real definition D that always runs before any use U of
+// R in the same iteration.
+// When D always precedes U, every U reads the value D just produced this
+// iteration, never the value carried across the back-edge -- so marking R is
+// sound.
+//
+// Why this needs more than dominance
+// ----------------------------------
+// D need not dominate U. A common shape guards the definition block and the use
+// block with the SAME predicate, so the loop header can branch around the
+// definition block:
+//
+// H: goto.cond p -> M ; p true skips the definition
+// D: R = ... ; runs only when p is false
+// M: goto.cond p -> L ; the same p skips the use
+// U: ... = R ; runs only when p is false
+// L: latch
+//
+// Here the path H -> M -> U skips D, so D does not dominate U; but that path
+// needs p both true (to skip D) and false (to reach U), which is impossible.
+// So whenever U runs, D ran first. We capture this by comparing the predicate
+// guards of D and U: U is covered by D when D runs before U and every predicate
+// guarding D also guards U (so U running implies D ran). Values may reach U
+// through pure repacks (copy / insert / extract / mov); we follow R forward
+// through those to its real uses.
+//
+// Detection flow (per register R, per loop L with header H)
+// ---------------------------------------------------------
+// 1. R has an IMPLICIT_DEF seed.
+// 2. R is live across the back-edge (live-in to H, live-out of a latch).
+// 3. R's value does not escape the loop: neither R nor a value repacked from
+// it is observed after the loop (its carried value must not leak out).
+// 4. The value reaching H from outside L is the IMPLICIT_DEF seed, not a real
+// value (an IMPLICIT_DEF dominates H, no real def dominates H, L has a
+// unique preheader).
+// 5. Following R forward through repack ops, every real use U is inside L and
+// is covered by a real def D of R that runs before U (by dominance, or by
+// a shared predicate guard with D ordered before U).
+// If all hold, emit the marker at H.
+//
+// Pass placement and liveness
+// ---------------------------
+// Runs in addPreEmitPass(), after register coalescing, so the marked vreg name
+// matches the emitted name; gated to -O != none. LiveVariables cannot run here
+// (the MIR is non-SSA at this slot), so liveness is recomputed by a small
+// backward block-level dataflow. MachineLoopInfo / MachineDominatorTree /
+// MachinePostDominatorTree are pure-CFG and valid here.
+//===----------------------------------------------------------------------===//
+
+#include "PISA.h"
+#include "PISAInstrInfo.h"
+#include "PISARegisterInfo.h"
+#include "llvm/ADT/BitVector.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/CodeGen/MachineDominators.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineInstrBuilder.h"
+#include "llvm/CodeGen/MachineLoopInfo.h"
+#include "llvm/CodeGen/MachinePostDominators.h"
+#include "llvm/CodeGen/MachineRegisterInfo.h"
+#include "llvm/CodeGen/TargetInstrInfo.h"
+#include "llvm/CodeGen/TargetSubtargetInfo.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Debug.h"
+
+using namespace llvm;
+
+// Generated register-class -> lifetime.start opcode lookup.
+namespace llvm {
+namespace PISA {
+struct LifetimeStartEntry {
+ // Since upstream commit 92f01b267efe ([TableGen] Use StringTable for
+ // searchable tables), string columns in SearchableTables are emitted as
+ // StringTable offsets (unsigned) rather than const char* pointers.
+ unsigned RegClassName;
+ unsigned Opcode;
+};
+
+#define GET_LifetimeStartTable_DECL
+#define GET_LifetimeStartTable_IMPL
+#include "PISAGenSearchableTables.inc"
+
+} // namespace PISA
+} // namespace llvm
+
+#define DEBUG_TYPE "pisa-insert-lifetime-start"
+
+STATISTIC(NumLifetimeMarkers, "Number of lifetime.start markers inserted");
+
+// NB: the option name must differ from the pass registration name
+// ("pisa-insert-lifetime-start", used by -run-pass / -start-after /
+// -print-after and the DEBUG_TYPE). The legacy PassNameParser registers every
+// pass's arg name as a CLI literal, so a cl::opt of the same name aborts every
+// tool at startup ("registered more than once"). Hence the "-enable" suffix.
+static cl::opt<bool> LifetimeStartOpt(
+ "pisa-insert-lifetime-start-enable",
+ cl::desc("Insert lifetime.start liveness markers at loop headers"),
+ cl::init(true), cl::Hidden);
+
+namespace {
+
+// Per-block liveness as a BitVector indexed by Register::virtReg2Index (only
+// virtual registers are tracked).
+using LiveMap = DenseMap<const MachineBasicBlock *, BitVector>;
+
+// A predicated branch (predgoto) "goto.cond <negate><cond>, <label>". The block
+// ending in it has two CFG successors: the label target (taken when cond equals
+// !negate) and the fall-through (taken when cond equals negate). PredCtrl
+// records what is needed to attribute each outgoing edge to a (predicate,
+// polarity) pair.
+struct PredCtrl {
+ MachineBasicBlock *Block; // block containing predgoto (source block)
+ MachineBasicBlock *Label; // the explicit branch target operand
+ Register Pred; // the predicate register (cond)
+ bool Negate; // the negate flag (operand 0)
+};
+
+// One packed (predicate vreg, polarity) guard key, used for set membership.
+using PredKey = uint64_t;
+
+// Pack (predicate vreg, polarity) into one key for set membership.
+inline PredKey predKey(Register P, bool Pol) {
+ return (static_cast<PredKey>(P.id()) << 1) | (Pol ? 1u : 0u);
+}
+
+class PISAInsertLifetimeStart : public MachineFunctionPass {
+public:
+ static char ID;
+
+ PISAInsertLifetimeStart();
+
+ StringRef getPassName() const override {
+ return "PISA Insert lifetime.start";
+ }
+
+ bool runOnMachineFunction(MachineFunction &MF) override;
+ void getAnalysisUsage(AnalysisUsage &AU) const override;
+
+private:
+ MachineRegisterInfo *MRI = nullptr;
+ const TargetInstrInfo *TII = nullptr;
+ MachineDominatorTree *MDT = nullptr;
+ MachinePostDominatorTree *MPDT = nullptr;
+ // Every predicated branch in the function. Control-dependence is a whole-CFG
+ // property, so this is gathered once and reused for all candidates.
+ SmallVector<PredCtrl, 16> PredCtrls;
+
+ void computeBlockLiveness(MachineFunction &MF, LiveMap &LiveIn,
+ LiveMap &LiveOut) const;
+
+ bool isBackEdgeLive(Register R, const MachineLoop *L, const LiveMap &LiveIn,
+ const LiveMap &LiveOut) const;
+
+ bool isLiveOutOfLoop(Register R, const MachineLoop *L,
+ const LiveMap &LiveIn) const;
+
+ bool implicitDefSeedReachesHeader(const MachineRegisterInfo &MRI, Register R,
+ const MachineLoop *L,
+ const MachineDominatorTree &MDT) const;
+
+ SmallVector<std::pair<const MachineBasicBlock *, PredKey>, 8>
+ directCtrlDeps(const MachineBasicBlock *B) const;
+
+ DenseSet<PredKey> transitiveCtrlSet(const MachineBasicBlock *B) const;
+
+ bool isRealFullDef(const MachineInstr &MI, Register R) const;
+
+ bool defRunsBeforeUse(const MachineInstr &D, const MachineInstr &U) const;
+
+ bool reachesWithinIteration(const MachineBasicBlock *From,
+ const MachineBasicBlock *To,
+ const MachineLoop *L) const;
+
+ bool hasTransitiveControlDependence(Register R, const MachineLoop *L) const;
+
+ // Returns true iff any marker was inserted.
+ bool run(MachineFunction &MF, MachineLoopInfo &MLI);
+};
+
+} // namespace
+
+// Pick the typed marker variant for R's register class; 0 if none (then the
+// caller skips R). The register-class -> opcode mapping is generated from
+// VTs.LifetimeTypes (see LifetimeStartTable in PISAInstrInfo.td).
+static unsigned pickLifetimeStartOpcode(const TargetRegisterInfo &TRI,
+ const TargetRegisterClass *RC) {
+ const auto *Entry =
+ PISA::lookupLifetimeStartByRegClass(TRI.getRegClassName(RC));
+ return Entry ? Entry->Opcode : 0;
+}
+
+// True if MI only repacks its operand bits -- copy / undef / insert / extract /
+// mov -- so the value flows through it without being observed.
+static bool isForwardingOpcode(const MachineInstr &MI,
+ const TargetInstrInfo &TII) {
+ if (MI.isCopy() || MI.isImplicitDef())
+ return true;
+ StringRef Name = TII.getName(MI.getOpcode());
+ return Name.starts_with("insert_") || Name.starts_with("extract_") ||
+ Name.starts_with("mov_");
+}
+
+void PISAInsertLifetimeStart::getAnalysisUsage(AnalysisUsage &AU) const {
+ // Both are pure-CFG / SSA-independent and valid at addPreEmitPass.
+ // LiveVariables is deliberately NOT required: it rejects the non-SSA
+ // MIR present at this slot (see file header) -- liveness is recomputed
+ // manually.
+ AU.addRequired<MachineLoopInfoWrapperPass>();
+ AU.addRequired<MachineDominatorTreeWrapperPass>();
+ // Post-dominators back the control-dependence test.
+ AU.addRequired<MachinePostDominatorTreeWrapperPass>();
+ AU.setPreservesCFG(); // we only ever insert marker MIs
+ MachineFunctionPass::getAnalysisUsage(AU);
+}
+
+PISAInsertLifetimeStart::PISAInsertLifetimeStart() : MachineFunctionPass(ID) {
+ initializePISAInsertLifetimeStartPass(*PassRegistry::getPassRegistry());
+}
+
+// Manual block-level liveness over virtual registers (NOT LiveVariables --
+// invalid at this slot, see file header). Fills LiveIn/LiveOut, each
+// BitVector sized to MRI.getNumVirtRegs() and indexed by
+// Register::virtReg2Index.
+void PISAInsertLifetimeStart::computeBlockLiveness(MachineFunction &MF,
+ LiveMap &LiveIn,
+ LiveMap &LiveOut) const {
+ const MachineRegisterInfo &MRI = MF.getRegInfo();
+ const unsigned NumV = MRI.getNumVirtRegs();
+
+ // Local transfer functions, computed once per block:
+ // Def[B] = registers fully defined somewhere in B.
+ // UpUse[B] = upward-exposed uses (read in B before any def in B).
+ LiveMap Def, UpUse;
+ for (MachineBasicBlock &MBB : MF) {
+ BitVector DefB(NumV), UseB(NumV), DefSoFar(NumV);
+ for (MachineInstr &MI : MBB) {
+ if (MI.isDebugInstr())
+ continue;
+ // Uses first: a use reads the value defined by an EARLIER instruction, so
+ // it is upward-exposed unless this block already (fully) defined it.
+ for (const MachineOperand &MO : MI.operands()) {
+ if (!MO.isReg() || !MO.getReg().isVirtual() || !MO.readsReg())
+ continue;
+ unsigned I = MO.getReg().virtRegIndex();
+ if (!DefSoFar.test(I))
+ UseB.set(I);
+ }
+ // Then defs. A full def (subreg 0) kills upward liveness; a sub-register
+ // def preserves the other bits, so it does not kill (and its read of the
+ // remaining bits was already counted as a use above via readsReg()).
+ for (const MachineOperand &MO : MI.operands()) {
+ if (!MO.isReg() || !MO.isDef() || !MO.getReg().isVirtual())
+ continue;
+ if (MO.getSubReg() != PISA::NoSubRegister)
+ continue; // partial def: not a kill
+ unsigned I = MO.getReg().virtRegIndex();
+ DefB.set(I);
+ DefSoFar.set(I);
+ }
+ }
+ Def[&MBB] = std::move(DefB);
+ UpUse[&MBB] = std::move(UseB);
+ LiveIn[&MBB] = BitVector(NumV);
+ LiveOut[&MBB] = BitVector(NumV);
+ }
+
+ // Backward iterative dataflow to fixpoint:
+ // LiveOut[B] = U_{S in succ(B)} LiveIn[S]
+ // LiveIn[B] = UpUse[B] U (LiveOut[B] \ Def[B])
+ bool Changed = true;
+ while (Changed) {
+ Changed = false;
+ // Reverse layout order converges faster for mostly-forward CFGs.
+ for (MachineBasicBlock &MBB : reverse(MF)) {
+ BitVector Out(NumV);
+ for (const MachineBasicBlock *Succ : MBB.successors())
+ Out |= LiveIn[Succ];
+
+ BitVector In = Out;
+ In.reset(Def[&MBB]); // Out & ~Def
+ In |= UpUse[&MBB];
+
+ if (Out != LiveOut[&MBB]) {
+ LiveOut[&MBB] = std::move(Out);
+ Changed = true;
+ }
+ if (In != LiveIn[&MBB]) {
+ LiveIn[&MBB] = std::move(In);
+ Changed = true;
+ }
+ }
+ }
+}
+
+// True iff R is live across L's back-edge: live-in to the header and live-out
+// of at least one latch.
+bool PISAInsertLifetimeStart::isBackEdgeLive(Register R, const MachineLoop *L,
+ const LiveMap &LiveIn,
+ const LiveMap &LiveOut) const {
+ const unsigned I = R.virtRegIndex();
+ const MachineBasicBlock *H = L->getHeader();
+ auto HIt = LiveIn.find(H);
+ if (HIt == LiveIn.end() || !HIt->second.test(I))
+ return false; // not live-in to the header
+
+ SmallVector<MachineBasicBlock *, 4> Latches;
+ L->getLoopLatches(Latches);
+ for (const MachineBasicBlock *Latch : Latches) {
+ auto LIt = LiveOut.find(Latch);
+ if (LIt != LiveOut.end() && LIt->second.test(I))
+ return true; // live across the latch -> header back-edge
+ }
+ return false;
+}
+
+// True if R's value is observed after the loop (R live-in to a loop-exit
+// block); such a register must not be marked because its carried value
+// escapes.
+bool PISAInsertLifetimeStart::isLiveOutOfLoop(Register R, const MachineLoop *L,
+ const LiveMap &LiveIn) const {
+ const unsigned I = R.virtRegIndex();
+ SmallVector<MachineBasicBlock *, 4> ExitBlocks;
+ L->getExitBlocks(ExitBlocks);
+ for (const MachineBasicBlock *S : ExitBlocks) {
+ auto It = LiveIn.find(S);
+ if (It != LiveIn.end() && It->second.test(I))
+ return true; // value escapes the loop -> observed after the loop
+ }
+ return false;
+}
+
+// True iff it is SOUND to assert R dead at L's header: the value of R that
+// reaches the header from OUTSIDE the loop is the IMPLICIT_DEF undef seed,
+// not a real (live) value. This uses a dominance approximation; it also
+// subsumes the verifier requirement that the inserted header USE be dominated
+// by a def.
+//
+// Requires ALL of:
+// (1) an IMPLICIT_DEF def of R STRICTLY dominates the header -- the undef
+// seed (a def in H itself sits at/after the insertion point at this
+// post-PHIElim slot, so it does not dominate the marker use);
+// (2) NO non-IMPLICIT_DEF def of R dominates the header -- a real
+// dominating def (e.g. a preheader load that seeds a loop-carried
+// reduction) is the live value entering the loop, so "dead at header"
+// would delete a live seed and miscompile; and
+// (3) the loop has a UNIQUE preheader, so the single loop-entry path makes
+// the closest dominating def the reaching def at the header. Without
+// it, a real def on one of several entry edges need not dominate the
+// header, so (2) could pass while a live value still reaches it.
+//
+// Defs INSIDE the loop body (the loop-carried redefinition the marker is
+// entitled to discard) do not dominate the header and are correctly ignored.
+bool PISAInsertLifetimeStart::implicitDefSeedReachesHeader(
+ const MachineRegisterInfo &MRI, Register R, const MachineLoop *L,
+ const MachineDominatorTree &MDT) const {
+ const MachineBasicBlock *H = L->getHeader();
+
+ // (3) Unique preheader -> a single loop-entry path into the header, so the
+ // closest def dominating H is exactly the value reaching it from outside.
+ if (!L->getLoopPreheader())
+ return false;
+
+ bool ImplicitDefDominates = false;
+ for (const MachineInstr &DefMI : MRI.def_instructions(R)) {
+ const MachineBasicBlock *DefBB = DefMI.getParent();
+ // A def in H itself sits at or after the insertion point (this slot is
+ // post-PHIElim / NoPHIs, so getFirstNonPHI() is the block top), so it does
+ // not dominate the inserted use; only STRICT dominators of H carry a value
+ // into the loop along the entry path.
+ if (DefBB == H || !MDT.dominates(DefBB, H))
+ continue;
+ if (DefMI.isImplicitDef())
+ ImplicitDefDominates = true; // (1) the undef seed dominates the header
+ else
+ return false; // (2) a real value reaches the header -> not dead on entry
+ }
+ return ImplicitDefDominates;
+}
+
+// Collect direct (immediate) control dependences of block B: which predicated
+// branches directly decide whether B runs, together with the predicate
+// polarity that forces B to execute. B is control-dependent on edge (A -> C)
+// iff B post-dominates C and B does not (reflexively) post-dominate A.
+SmallVector<std::pair<const MachineBasicBlock *, PredKey>, 8>
+PISAInsertLifetimeStart::directCtrlDeps(const MachineBasicBlock *B) const {
+ SmallVector<std::pair<const MachineBasicBlock *, PredKey>, 8> Deps;
+ for (const PredCtrl &PC : PredCtrls) {
+ if (MPDT->dominates(B, PC.Block))
+ continue; // B post-dominates the branch site -> not control-dependent
+ for (MachineBasicBlock *C : PC.Block->successors()) {
+ if (!MPDT->dominates(B, C))
+ continue;
+ bool ToLabel = (C == PC.Label);
+ // Edge to label is taken when cond == !negate; fall-through when
+ // ==negate.
+ bool Pol = ToLabel ? !PC.Negate : PC.Negate;
+ Deps.push_back({PC.Block, predKey(PC.Pred, Pol)});
+ }
+ }
+ return Deps;
+}
+
+// Transitive guard set of block B: every (pred,pol) that must hold for B to
+// run, following the control-dependence chain. A block inherits the guards of
+// each branch site it is control-dependent on. The Seen set makes the closure
+// terminate even with cyclic control dependence (loops).
+DenseSet<PredKey>
+PISAInsertLifetimeStart::transitiveCtrlSet(const MachineBasicBlock *B) const {
+ DenseSet<PredKey> S;
+ SmallVector<const MachineBasicBlock *, 8> Work{B};
+ DenseSet<const MachineBasicBlock *> Seen{B};
+ while (!Work.empty()) {
+ const MachineBasicBlock *X = Work.pop_back_val();
+ for (auto [Site, Key] : directCtrlDeps(X)) {
+ S.insert(Key);
+ if (Seen.insert(Site).second)
+ Work.push_back(Site);
+ }
+ }
+ return S;
+}
+
+// A real (non-undef, non-debug) full (subreg-0) def of R. A repack op that
+// fully defines R counts; what matters for soundness is ordering, not the
+// opcode.
+bool PISAInsertLifetimeStart::isRealFullDef(const MachineInstr &MI,
+ Register R) const {
+ if (MI.isImplicitDef() || MI.isDebugInstr())
+ return false;
+ for (const MachineOperand &MO : MI.operands())
+ if (MO.isReg() && MO.isDef() && MO.getReg() == R &&
+ MO.getSubReg() == PISA::NoSubRegister)
+ return true;
+ return false;
+}
+
+// True iff D is executed before U within an iteration: D's block dominates
+// U's block, or (same block) D is the earlier instruction. With a shared
+// guard (or on its own) this proves R holds a same-iteration value where U
+// observes it.
+bool PISAInsertLifetimeStart::defRunsBeforeUse(const MachineInstr &D,
+ const MachineInstr &U) const {
+ const MachineBasicBlock *DB = D.getParent(), *UB = U.getParent();
+ if (DB != UB)
+ return MDT->dominates(DB, UB);
+ for (const MachineInstr &MI : *DB) {
+ if (&MI == &D)
+ return true;
+ if (&MI == &U)
+ return false;
+ }
+ return false;
+}
+
+// True if block To is reachable from block From along intra-loop edges
+// without passing through L's header -- i.e. To runs after From in the
+// iteration (the header is the only re-entry point, so excluding it cuts
+// every back-edge of L). Used by the cover below to reject a def that,
+// although it shares the observation's guard, runs after the observation (so
+// the observation still sees the back-edge value).
+bool PISAInsertLifetimeStart::reachesWithinIteration(
+ const MachineBasicBlock *From, const MachineBasicBlock *To,
+ const MachineLoop *L) const {
+ const MachineBasicBlock *H = L->getHeader();
+ SmallVector<const MachineBasicBlock *, 8> Work;
+ DenseSet<const MachineBasicBlock *> Seen;
+ for (const MachineBasicBlock *S : From->successors())
+ if (L->contains(S) && S != H && Seen.insert(S).second)
+ Work.push_back(S);
+ while (!Work.empty()) {
+ const MachineBasicBlock *B = Work.pop_back_val();
+ if (B == To)
+ return true;
+ for (const MachineBasicBlock *S : B->successors())
+ if (L->contains(S) && S != H && Seen.insert(S).second)
+ Work.push_back(S);
+ }
+ return false;
+}
+
+// Per-use cover test. R's value reaches its real observations through
+// forwarding ops (copy/insert/extract/mov) that only repack it, so we follow
+// R forward through them and require that every real use U of a value derived
+// from R is covered: a real def D of R runs before U and D's guards are
+// implied by U's guards, so U always sees this iteration's value rather than
+// the back-edge value. D ordered before U is checked by dominance or, for a
+// shared guard, by D not running after U within the iteration
+// (reachesWithinIteration) -- without that ordering U could read the
+// back-edge value before D rewrites it. Guards are compared with the
+// transitive guard set, so a deep observation links back to the predicate
+// guarding the def even when intervening blocks test other predicates.
+//
+// A register with no real def (pass-through), or an observation no def
+// covers, is rejected. A use OUTSIDE the loop L is always rejected: a value
+// derived from R that is read after the loop is the carried value escaping.
+// This catches escapes through a different register than R itself, which the
+// isLiveOutOfLoop(R) gate -- keyed on R alone -- cannot see.
+bool PISAInsertLifetimeStart::hasTransitiveControlDependence(
+ Register R, const MachineLoop *L) const {
+ SmallVector<const MachineInstr *, 4> RDefs;
+ for (const MachineInstr &MI : MRI->def_instructions(R))
+ if (isRealFullDef(MI, R))
+ RDefs.push_back(&MI);
+ if (RDefs.empty())
+ return false; // pass-through: nothing redefines R in the loop
+
+ SmallVector<Register, 8> Work{R};
+ DenseSet<Register> Visited;
+ bool SawObservation = false;
+ while (!Work.empty()) {
+ Register V = Work.pop_back_val();
+ if (!Visited.insert(V).second)
+ continue; // already processed
+ for (const MachineInstr &U : MRI->use_instructions(V)) {
+ if (U.isDebugInstr())
+ continue;
+ if (isForwardingOpcode(U, *TII)) {
+ // Repack: follow the produced virtual reg(s); it observes nothing.
+ for (const MachineOperand &MO : U.operands())
+ if (MO.isReg() && MO.isDef() && MO.getReg().isVirtual())
+ Work.push_back(MO.getReg());
+ continue;
+ }
+ // Real observation of a value derived from R.
+ SawObservation = true;
+ if (!L->contains(U.getParent()))
+ return false; // observed after the loop -> escaping back-edge value
+ DenseSet<PredKey> CU = transitiveCtrlSet(U.getParent());
+ bool Covered = false;
+ for (const MachineInstr *D : RDefs) {
+ // A def cannot cover its OWN read: a read-modify-write of R (e.g. an
+ // accumulator `R = iadd R, x`) reads the incoming/back-edge value
+ // before this instruction's def, so it must stay an uncovered
+ // observation.
+ if (D == &U)
+ continue;
+ if (defRunsBeforeUse(*D, U)) { // dominated -> fresh, predicate-free
+ Covered = true;
+ break;
+ }
+ // Guard-containment cover: D and U live in different blocks. Only
+ // sound if D is NOT after U within the iteration -- otherwise U reads
+ // the back-edge value before D rewrites it. (Same-block D-after-U was
+ // already settled false by defRunsBeforeUse, and
+ // reachesWithinIteration is block-granular, so skip it there.)
+ const MachineBasicBlock *DB = D->getParent(), *UB = U.getParent();
+ if (DB == UB || reachesWithinIteration(UB, DB, L))
+ continue; // D runs after U -> cannot cover this observation
+ DenseSet<PredKey> CD = transitiveCtrlSet(DB);
+ if (CD.empty())
+ continue; // unconditional D is covered only via the dominance
+ // branch
+ // Containment: every guard of D also guards U, so U running implies D
+ // ran the same iteration.
+ bool Implies = true;
+ for (PredKey K : CD) {
+ // predKey identifies a guard by (predicate vreg, polarity) only. That
+ // is a value identity ONLY when the predicate vreg has a single
+ // reaching def: this MIR is non-SSA (post-PHIElim / coalescing), so a
+ // predicate vreg may be redefined and hold different values at
+ // different branch sites. If D's guard predicate is multiply defined,
+ // a matching key in CU can come from a branch testing a DIFFERENT
+ // value, so "U ran => D ran" no longer follows. Refuse the cover.
+ Register P = Register(static_cast<unsigned>(K >> 1));
+ if (!MRI->hasOneDef(P)) {
+ Implies = false;
+ break;
+ }
+ if (!CU.contains(K)) {
+ Implies = false;
+ break;
+ }
+ }
+ if (Implies)
+ Covered = true;
+ if (Covered)
+ break;
+ }
+ if (!Covered)
+ return false; // observed outside R's def guard -> back-edge value
+ // live
+ }
+ }
+ // SawObservation stays false only for an unobservable register -> reject.
+ return SawObservation;
+}
+
+bool PISAInsertLifetimeStart::run(MachineFunction &MF, MachineLoopInfo &MLI) {
+ const unsigned NumV = MRI->getNumVirtRegs();
+ if (NumV == 0 || MLI.empty())
+ return false;
+
+ // Candidate signal: a vreg with an IMPLICIT_DEF def (undef-on-entry seed).
+ BitVector HasImplicitDef(NumV);
+ for (MachineBasicBlock &MBB : MF)
+ for (MachineInstr &MI : MBB)
+ if (MI.isImplicitDef()) {
+ Register R = MI.getOperand(0).getReg();
+ if (R.isVirtual())
+ HasImplicitDef.set(R.virtRegIndex());
+ }
+ if (HasImplicitDef.none())
+ return false;
+
+ // Collect every predicated branch in the function. Control-dependence is a
+ // whole-CFG property, so this is gathered once and reused for all candidates.
+ PredCtrls.clear();
+ for (MachineBasicBlock &MBB : MF)
+ for (MachineInstr &MI : MBB)
+ if (MI.getOpcode() == PISA::predgoto) {
+ Register P = MI.getOperand(1).getReg();
+ if (!P.isVirtual())
+ continue;
+ PredCtrls.push_back({&MBB, MI.getOperand(2).getMBB(), P,
+ MI.getOperand(0).getImm() != 0});
+ }
+ if (PredCtrls.empty())
+ return false;
+
+ // Visit loops innermost-first, dedup globally.
+ SmallVector<MachineLoop *, 8> Loops;
+ for (MachineLoop *TopL : MLI) {
+ SmallVector<MachineLoop *, 8> WL{TopL};
+ while (!WL.empty()) {
+ MachineLoop *L = WL.pop_back_val();
+ Loops.push_back(L);
+ WL.append(L->begin(), L->end());
+ }
+ }
+ llvm::stable_sort(Loops, [](const MachineLoop *A, const MachineLoop *B) {
+ return A->getLoopDepth() > B->getLoopDepth();
+ });
+
+ LiveMap LiveIn, LiveOut;
+ computeBlockLiveness(MF, LiveIn, LiveOut);
+
+ BitVector Marked(NumV);
+ bool Changed = false;
+
+ for (MachineLoop *L : Loops) {
+ MachineBasicBlock *H = L->getHeader();
+ MachineBasicBlock::iterator InsertPt = H->getFirstNonPHI();
+ for (unsigned I : HasImplicitDef.set_bits()) {
+ if (Marked.test(I))
+ continue; // already processed
+ Register R = Register::index2VirtReg(I);
+ if (!isBackEdgeLive(R, L, LiveIn, LiveOut))
+ continue;
+ if (isLiveOutOfLoop(R, L, LiveIn)) {
+ // R escapes the loop: its exit value is observed after the loop and is
+ // (or depends on) the carried value, so "dead at the header" is
+ // unsound.
+ LLVM_DEBUG(dbgs() << "[" DEBUG_TYPE "] skip " << printReg(R, nullptr)
+ << ": live-out of the loop (escapes to a post-loop "
+ "use)\n");
+ continue;
+ }
+ if (!implicitDefSeedReachesHeader(*MRI, R, L, *MDT)) {
+ // Either the header marker would be a use not dominated by any def (R's
+ // only seed/def is inside the loop body), or a REAL def reaches the
+ // header from outside the loop -- i.e. a live value enters the loop and
+ // "dead at header" would be a miscompile.
+ LLVM_DEBUG(dbgs() << "[" DEBUG_TYPE "] skip " << printReg(R, nullptr)
+ << ": IMPLICIT_DEF seed does not reach the loop "
+ "header (real def reaches it, or no dominating "
+ "seed / no unique preheader)\n");
+ continue;
+ }
+ if (!hasTransitiveControlDependence(R, L)) {
+ LLVM_DEBUG(dbgs() << "[" DEBUG_TYPE "] skip " << printReg(R, nullptr)
+ << ": an observation is not covered by a guarded "
+ "preceding def (transitive)\n");
+ continue;
+ }
+ unsigned Opc = pickLifetimeStartOpcode(*MRI->getTargetRegisterInfo(),
+ MRI->getRegClass(R));
+ if (!Opc) {
+ LLVM_DEBUG(dbgs() << "[" DEBUG_TYPE "] skip " << printReg(R, nullptr)
+ << ": no lifetime.start variant for its reg class\n");
+ continue;
+ }
+ BuildMI(*H, InsertPt, DebugLoc(), TII->get(Opc)).addDef(R);
+ Marked.set(I);
+ ++NumLifetimeMarkers;
+ Changed = true;
+ LLVM_DEBUG(dbgs() << "[" DEBUG_TYPE "] mark " << printReg(R, nullptr)
+ << " at " << printMBBReference(*H) << " (loop depth "
+ << L->getLoopDepth() << ")\n");
+ }
+ }
+
+ LLVM_DEBUG(dbgs() << "[" DEBUG_TYPE "] " << MF.getName() << ": "
+ << Marked.count() << " marker(s) inserted\n");
+ return Changed;
+}
+
+bool PISAInsertLifetimeStart::runOnMachineFunction(MachineFunction &MF) {
+ if (!LifetimeStartOpt)
+ return false; // default: insert nothing
+
+ MRI = &MF.getRegInfo();
+ TII = MF.getSubtarget().getInstrInfo();
+ MDT = &getAnalysis<MachineDominatorTreeWrapperPass>().getDomTree();
+ MPDT = &getAnalysis<MachinePostDominatorTreeWrapperPass>().getPostDomTree();
+ MachineLoopInfo &MLI = getAnalysis<MachineLoopInfoWrapperPass>().getLI();
+ return run(MF, MLI);
+}
+
+char PISAInsertLifetimeStart::ID = 0;
+INITIALIZE_PASS_BEGIN(PISAInsertLifetimeStart, DEBUG_TYPE,
+ "PISA insert lifetime.start", false, false)
+INITIALIZE_PASS_DEPENDENCY(MachineLoopInfoWrapperPass)
+INITIALIZE_PASS_DEPENDENCY(MachineDominatorTreeWrapperPass)
+INITIALIZE_PASS_DEPENDENCY(MachinePostDominatorTreeWrapperPass)
+INITIALIZE_PASS_END(PISAInsertLifetimeStart, DEBUG_TYPE,
+ "PISA insert lifetime.start", false, false)
+
+namespace llvm {
+FunctionPass *createPISAInsertLifetimeStart() {
+ return new PISAInsertLifetimeStart();
+}
+} // namespace llvm
diff --git a/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.cpp b/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.cpp
new file mode 100644
index 0000000000000..03b1d6141001a
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.cpp
@@ -0,0 +1,294 @@
+//===-- PISAKernelByValArgsLowering.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 "PISAKernelByValArgsLowering.h"
+
+#include "PISA.h"
+#include "PISADefines.h"
+
+#include <llvm/IR/BasicBlock.h>
+#include <llvm/IR/CallingConv.h>
+#include <llvm/IR/Function.h>
+#include <llvm/IR/IRBuilder.h>
+#include <llvm/IR/IntrinsicInst.h>
+#include <llvm/IR/Module.h>
+#include <llvm/Support/Debug.h>
+#include <llvm/Support/PISAAddrSpace.h>
+
+#define DEBUG_TYPE "pisa-kernel-byval-args-lowering"
+#define DEBUG_NAME "PISA kernel byval args lowering (Legacy)"
+
+using namespace llvm;
+using namespace llvm::PISA;
+using namespace llvm::PISAAS;
+
+namespace {
+class PISAKernelByValArgsLowering {
+public:
+ explicit PISAKernelByValArgsLowering(Function &F) : F(F), Ctx(F.getContext()) {
+ assert(F.getCallingConv() == CallingConv::PISA_KERNEL &&
+ "Expected PISA_KERNEL calling convention");
+ assert(F.getReturnType()->isVoidTy() && "Expected void return type");
+ }
+
+ bool run();
+
+private:
+ static constexpr auto ByRefAddressSpace =
+ static_cast<unsigned>(AddressSpace::CONSTANT);
+
+ AttributeList getNewAttributes(const AttributeList &Attrs) const;
+ void transferArgumentUses(Function &NewF);
+
+ Function &F;
+ LLVMContext &Ctx;
+};
+
+bool PISAKernelByValArgsLowering::run() {
+ if (none_of(F.args(), [](auto &Arg) { return Arg.hasByValAttr(); }))
+ return false;
+
+ LLVM_DEBUG(dbgs() << "Lowering kernel aggregate arguments for " << F.getName()
+ << "\n");
+
+ AttributeList NewAttrs = getNewAttributes(F.getAttributes());
+
+ SmallVector<Type *> NewArgTypes;
+ transform(F.args(), std::back_inserter(NewArgTypes),
+ [this](auto &Arg) -> Type * {
+ if (Arg.hasByValAttr())
+ return PointerType::get(Ctx, ByRefAddressSpace);
+ return Arg.getType();
+ });
+
+ auto *NewFTy = FunctionType::get(Type::getVoidTy(Ctx), NewArgTypes, false);
+ auto *NewF = Function::Create(NewFTy, F.getLinkage());
+ NewF->takeName(&F);
+ NewF->setAttributes(NewAttrs);
+ NewF->setCallingConv(CallingConv::PISA_KERNEL);
+
+ F.getParent()->getFunctionList().insert(F.getIterator(), NewF);
+
+ // Transfer debug info to the new function
+ auto *DISubprog = F.getSubprogram();
+ NewF->setSubprogram(DISubprog);
+ F.setSubprogram(nullptr);
+
+ // Splice the body of the old function into the new function.
+ NewF->splice(NewF->begin(), &F);
+
+ transferArgumentUses(*NewF);
+
+ F.replaceAllUsesWith(NewF);
+
+ NewF->copyMetadata(&F, 0);
+ return true;
+}
+
+AttributeList
+PISAKernelByValArgsLowering::getNewAttributes(const AttributeList &Attrs) const {
+ AttributeList NewAttrs;
+
+ // Copy function attributes.
+ if (auto FunctionAttrs = Attrs.getFnAttrs(); FunctionAttrs.hasAttributes()) {
+ AttrBuilder AB(Ctx, FunctionAttrs);
+ NewAttrs = NewAttrs.addFnAttributes(Ctx, AB);
+ }
+
+ for (const auto &Arg : F.args()) {
+ const auto Index = Arg.getArgNo();
+ auto ArgAttrs = Attrs.getParamAttrs(Index);
+ AttrBuilder AB(Ctx, ArgAttrs);
+
+ if (ArgAttrs.hasAttribute(Attribute::ByVal)) {
+ // Replace byval with byref
+ assert(cast<PointerType>(Arg.getType())->getAddressSpace() ==
+ static_cast<unsigned>(AddressSpace::PRIVATE) &&
+ "Expected private address space");
+
+ auto *ByValTy = ArgAttrs.getByValType();
+
+ AB.removeAttribute(Attribute::ByVal).addByRefAttr(ByValTy);
+ NewAttrs = NewAttrs.addParamAttributes(Ctx, Index, AB);
+ } else {
+ // Copy the argument as is.
+ NewAttrs = NewAttrs.addParamAttributes(Ctx, Index, AB);
+ }
+ }
+
+ return NewAttrs;
+}
+
+void PISAKernelByValArgsLowering::transferArgumentUses(Function &NewF) {
+ IRBuilder<> Builder(Ctx);
+
+ for (auto [OldArg, NewArg] : zip_equal(F.args(), NewF.args())) {
+ NewArg.takeName(&OldArg);
+
+ if (!OldArg.hasByValAttr()) {
+ OldArg.replaceAllUsesWith(&NewArg);
+ continue;
+ }
+
+ LLVM_DEBUG(dbgs() << "Convert byval argument <" << OldArg << "> to byref <"
+ << NewArg << ">\n");
+
+ SmallVector<Value *, 8> Stack;
+ DenseMap<Value *, Value *> Map;
+ SmallVector<Instruction *, 8> ToDelete;
+ Stack.push_back(&OldArg);
+ Map[&OldArg] = &NewArg;
+
+ // Traverse all users of the argument and create GEPs and loads with the
+ // new address space. A GEP/memop chain can later be combined into a single
+ // ld.param PISA instruction.
+ while (!Stack.empty()) {
+ auto *V = Stack.back();
+ Stack.pop_back();
+ for (auto *U : V->users()) {
+ if (auto *GEP = dyn_cast<GetElementPtrInst>(U)) {
+ assert(GEP->getPointerOperand() == V);
+ Stack.push_back(GEP);
+ Builder.SetInsertPoint(GEP->getIterator());
+ auto *NewGEP = Builder.CreateGEP(
+ GEP->getSourceElementType(), Map[V],
+ SmallVector<Value *, 4>(GEP->indices()),
+ GEP->getName() + ".byref", GEP->getNoWrapFlags());
+ LLVM_DEBUG(dbgs() << "Created getelementptr: " << *NewGEP << "\n");
+ Map[GEP] = NewGEP;
+ } else if (auto *Load = dyn_cast<LoadInst>(U)) {
+ Builder.SetInsertPoint(Load->getIterator());
+ auto *NewLoad = Builder.CreateAlignedLoad(
+ Load->getType(), Map[Load->getPointerOperand()], Load->getAlign(),
+ Load->isVolatile(), Load->getName() + ".byref");
+ LLVM_DEBUG(dbgs() << "Created load: " << *NewLoad << "\n");
+ Load->replaceAllUsesWith(NewLoad);
+ ToDelete.push_back(Load);
+ } else if (auto *MemCpy = dyn_cast<MemCpyInst>(U)) {
+ if (MemCpy && MemCpy->getSource() == V) {
+ Builder.SetInsertPoint(MemCpy);
+ auto *NewMemCpy = Builder.CreateMemCpy(
+ MemCpy->getDest(), MemCpy->getDestAlign(),
+ Map[MemCpy->getSource()], MemCpy->getSourceAlign(),
+ MemCpy->getLength(), MemCpy->isVolatile());
+ LLVM_DEBUG(dbgs() << "Created memcpy: " << *NewMemCpy << "\n");
+ (void)NewMemCpy;
+ ToDelete.push_back(MemCpy);
+ } else {
+ LLVM_DEBUG(dbgs() << "Only memcpy using byval argument as a source "
+ "can be combined into a ld.param: "
+ << *MemCpy << "\n");
+ }
+ } else {
+ LLVM_DEBUG(
+ dbgs()
+ << "Byval argument's user can't be combined into a ld.param: "
+ << *U << "\n");
+ }
+ }
+ }
+
+ // Perform cleanup: erase replaced memory operations and the chain of GEPs
+ // between them and the argument.
+ for (auto *Inst : ToDelete) {
+ Value *Ptr = nullptr;
+ if (auto *Load = dyn_cast<LoadInst>(Inst)) {
+ Ptr = Load->getPointerOperand();
+ } else if (auto *MemCpy = dyn_cast<MemCpyInst>(Inst)) {
+ Ptr = MemCpy->getSource();
+ }
+ assert(Ptr);
+ Inst->eraseFromParent();
+ while (auto *GEP = dyn_cast<GetElementPtrInst>(Ptr)) {
+ Ptr = GEP->getPointerOperand();
+ if (GEP->getNumUses() > 0)
+ break;
+ GEP->eraseFromParent();
+ }
+ }
+
+ // If all of the argument's users were processed earlier, nothing more needs
+ // to be done here.
+ if (OldArg.getNumUses() == 0)
+ continue;
+
+ // Otherwise, create a copy in the private address space and replace any
+ // remaining users.
+ Builder.SetInsertPoint(NewF.getEntryBlock().getFirstInsertionPt());
+ unsigned AS = static_cast<unsigned>(AddressSpace::PRIVATE);
+ auto *ByValTy = OldArg.getAttribute(Attribute::ByVal).getValueAsType();
+ auto *Alloca = Builder.CreateAlloca(ByValTy, AS, nullptr,
+ OldArg.getName() + ".private");
+ LLVM_DEBUG(dbgs() << "Created alloca: " << *Alloca << "\n");
+ const auto &DL = F.getParent()->getDataLayout();
+ auto *MemCpy = Builder.CreateMemCpy(Alloca, Alloca->getAlign(), &NewArg,
+ NewArg.getPointerAlignment(DL),
+ DL.getTypeStoreSize(ByValTy));
+ LLVM_DEBUG(dbgs() << "Created memcpy: " << *MemCpy << "\n");
+ (void)MemCpy;
+ OldArg.replaceAllUsesWith(Alloca);
+ }
+}
+
+class PISAKernelByValArgsLoweringLegacy : public ModulePass {
+public:
+ static char ID;
+
+ PISAKernelByValArgsLoweringLegacy() : ModulePass(ID) {}
+
+ StringRef getPassName() const override { return DEBUG_NAME; }
+
+ bool runOnModule(Module &M) override {
+ bool Changed = false;
+ for (auto FI = M.begin(), FE = M.end(); FI != FE;) {
+ Function &F = *FI++;
+ if (F.isDeclaration() || F.getCallingConv() != CallingConv::PISA_KERNEL)
+ continue;
+ PISAKernelByValArgsLowering KBVAL(F);
+ if (KBVAL.run()) {
+ F.eraseFromParent();
+ Changed = true;
+ }
+ }
+ return Changed;
+ }
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ ModulePass::getAnalysisUsage(AU);
+ }
+};
+} // namespace
+
+char PISAKernelByValArgsLoweringLegacy::ID = 0;
+INITIALIZE_PASS(PISAKernelByValArgsLoweringLegacy, DEBUG_TYPE, DEBUG_NAME,
+ false, false)
+
+ModulePass *llvm::createPISAKernelByValArgsLoweringLegacyPass() {
+ return new PISAKernelByValArgsLoweringLegacy();
+}
+
+PreservedAnalyses KernelByValArgsLoweringPass::run(Module &M,
+ ModuleAnalysisManager &) {
+ SmallVector<Function *> ToErase;
+
+ for (auto &F : M) {
+ if (F.isDeclaration() || F.getCallingConv() != CallingConv::PISA_KERNEL)
+ continue;
+
+ if (PISAKernelByValArgsLowering LKA(F); LKA.run())
+ ToErase.push_back(&F);
+ }
+
+ if (ToErase.empty())
+ return PreservedAnalyses::all();
+
+ for (auto *F : ToErase)
+ F->eraseFromParent();
+
+ return PreservedAnalyses::none();
+}
diff --git a/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.h b/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.h
new file mode 100644
index 0000000000000..80854888ae97e
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.h
@@ -0,0 +1,40 @@
+//===-- PISAKernelByValArgsLowering.h -------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass lowers byval arguments. The byval semantics cannot work with the
+// PISA calling convention, so the pass replaces byval with byref as follows:
+//
+// 1. The pass creates a new function with the same signature as the original
+// function, but with byref arguments instead of byval arguments. The new
+// byref arguments are pointers with constant address space.
+// 2. The pass creates alloca instructions for each byval argument of the
+// original function and replaces the byval argument uses with the alloca.
+// 3. The pass creates a memcpy intrinsic to copy the new byref argument to the
+// alloca.
+// 4. The pass replaces old function uses with the new function and removes the
+// old function.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_TARGET_PISA_PISAKERNELBYVALARGSLOWERING_H
+#define LLVM_LIB_TARGET_PISA_PISAKERNELBYVALARGSLOWERING_H
+
+#include <llvm/IR/PassManager.h>
+
+namespace llvm {
+namespace PISA {
+class KernelByValArgsLoweringPass
+ : public OptionalPassInfoMixin<KernelByValArgsLoweringPass> {
+public:
+ PreservedAnalyses run(Module &M, ModuleAnalysisManager &);
+};
+
+} // namespace PISA
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_PISA_PISAKERNELBYVALARGSLOWERING_H
diff --git a/llvm/lib/Target/PISA/PISALayout.cpp b/llvm/lib/Target/PISA/PISALayout.cpp
new file mode 100644
index 0000000000000..ab43298ae8d5c
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISALayout.cpp
@@ -0,0 +1,581 @@
+//===-- PISALayout.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
+//
+//===----------------------------------------------------------------------===//
+
+#define GET_PISAAtomicSubOpcode_DECL
+#include "PISAGenSearchableTables.inc"
+
+#include "PISA.h"
+#include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Analysis/PostDominators.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/IntrinsicsPISA.h"
+#include "llvm/IR/Module.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Support/PISAAddrSpace.h"
+
+#define DEBUG_TYPE "pisa-layout"
+#define DEBUG_NAME "PISA layout"
+
+using namespace llvm;
+
+namespace {
+/// This is a temporary solution to the issues with divergent barriers.
+/// Ultimately it's going to be replaced with MachineBlockPlacement, which
+/// currently is causing issues in the PISA backend and cannot be
+/// enabled yet.
+///
+/// @brief sort basic blocks into topological order
+/// Arbitrary reverse postorder is not sufficient.
+/// Whenever it is possible, we want to layout blocks in such way
+/// that the we can recognize the control-flow structures.
+class PISALayout : public FunctionPass {
+public:
+ static char ID;
+ PISALayout() : FunctionPass(ID) {}
+
+ virtual void getAnalysisUsage(AnalysisUsage &AU) const override;
+
+ /// @brief Provides name of pass
+ virtual StringRef getPassName() const override { return DEBUG_NAME; }
+
+ virtual bool runOnFunction(Function &F) override;
+
+private:
+ BasicBlock *getLastReturnBlock(Function &F);
+ void layoutBlocks(Function &F, LoopInfo &LI);
+ void layoutBlocks(Function &F);
+ BasicBlock *selectSucc(BasicBlock *CurrBlk, bool SelectNoInstBlk,
+ const LoopInfo &LI,
+ const SmallSet<BasicBlock *, 8> &VisitSet);
+ bool isAtomicWrite(Instruction *Inst, bool OnlyLocalMem);
+ bool isAtomicRead(Instruction *Inst, bool OnlyLocalMem);
+ Value *getMemoryOperand(Instruction *Inst, bool OnlyLocalMem);
+ bool isReturnBlock(BasicBlock *BB);
+ bool tryMovingWrite(Instruction *Write, Loop *Loop, LoopInfo &LI);
+ void moveAtomicWrites2Loop(Function &F, LoopInfo &LI, bool OnlyLocalMem);
+
+ PostDominatorTree *PDT = nullptr;
+ DominatorTree *DT = nullptr;
+};
+} // namespace
+
+char PISALayout::ID = 0;
+INITIALIZE_PASS_BEGIN(PISALayout, DEBUG_TYPE, DEBUG_NAME, false, false)
+INITIALIZE_PASS_DEPENDENCY(LoopInfoWrapperPass)
+INITIALIZE_PASS_DEPENDENCY(PostDominatorTreeWrapperPass)
+INITIALIZE_PASS_END(PISALayout, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+constexpr unsigned BreakBlockSizeLimit = 3;
+
+static void pushSucc(BasicBlock *BB, std::function<bool(BasicBlock *)> Cond,
+ SmallVector<BasicBlock *> &VisitVec,
+ SmallSet<BasicBlock *, 8> &VisitSet) {
+ for (succ_iterator IT = succ_begin(BB), End = succ_end(BB); IT != End; ++IT) {
+ BasicBlock *Succ = *IT;
+ if (!VisitSet.count(Succ) && Cond(Succ)) {
+ VisitVec.push_back(Succ);
+ VisitSet.insert(Succ);
+ break;
+ }
+ }
+}
+
+inline static auto sizeWithoutDebug(const BasicBlock *BB) { return BB->size(); }
+
+void PISALayout::getAnalysisUsage(AnalysisUsage &AU) const {
+ // Doesn't change the IR at all, it just move the blocks so no changes in the
+ // IR
+ AU.setPreservesAll();
+ AU.addRequired<LoopInfoWrapperPass>();
+ AU.addRequired<PostDominatorTreeWrapperPass>();
+ AU.addRequired<DominatorTreeWrapperPass>();
+}
+
+bool PISALayout::runOnFunction(Function &Func) {
+ PDT = &getAnalysis<PostDominatorTreeWrapperPass>().getPostDomTree();
+ DT = &getAnalysis<DominatorTreeWrapperPass>().getDomTree();
+ LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>().getLoopInfo();
+ if (LI.empty())
+ layoutBlocks(Func);
+ else
+ layoutBlocks(Func, LI);
+
+ return true;
+}
+
+// check if the instruction is atomic write (xchg or cmpxchng)
+bool PISALayout::isAtomicWrite(Instruction *Inst, bool OnlyLocalMem) {
+ auto MemCond = [Inst, OnlyLocalMem]() {
+ Value *Ptr = Inst->getOperand(0);
+ bool IsLocalMem = Ptr->getType()->getPointerAddressSpace() ==
+ static_cast<unsigned>(PISAAS::AddressSpace::SHARED);
+ return (!OnlyLocalMem || IsLocalMem);
+ };
+
+ if (isa<AtomicCmpXchgInst>(Inst) && MemCond())
+ return true;
+
+ if (auto *AtomicRMW = dyn_cast<AtomicRMWInst>(Inst))
+ return AtomicRMW->getOperation() == AtomicRMWInst::Xchg && MemCond();
+
+ if (auto *CI = dyn_cast<CallInst>(Inst)) {
+ Function *F = CI->getCalledFunction();
+ if (!F)
+ return false;
+
+ Intrinsic::ID IntrID = F->getIntrinsicID();
+ if (IntrID == Intrinsic::pisa_cas_fatom && MemCond())
+ return true;
+ }
+
+ return false;
+}
+
+// check if the instruction is atomic read (ATOMIC_OR with src == 0)
+bool PISALayout::isAtomicRead(Instruction *Inst, bool OnlyLocalMem) {
+ auto LocalMemCond = [Inst, OnlyLocalMem]() {
+ return !OnlyLocalMem ||
+ Inst->getOperand(0)->getType()->getPointerAddressSpace() ==
+ static_cast<unsigned>(PISAAS::AddressSpace::SHARED);
+ };
+
+ auto *AtomicRMW = dyn_cast<AtomicRMWInst>(Inst);
+ if (AtomicRMW && AtomicRMW->getOperation() == AtomicRMWInst::Or &&
+ LocalMemCond()) {
+ ConstantInt *Src = dyn_cast<ConstantInt>(AtomicRMW->getValOperand());
+ return Src && Src->getZExtValue() == 0;
+ }
+
+ return false;
+}
+
+// get memory operand for atomic read or write
+Value *PISALayout::getMemoryOperand(Instruction *Inst, bool OnlyLocalMem) {
+ if (!isAtomicRead(Inst, OnlyLocalMem) && !isAtomicWrite(Inst, OnlyLocalMem))
+ return nullptr;
+
+ Value *DstAddr = Inst->getOperand(0);
+ if (auto *PTI = dyn_cast<PtrToIntInst>(DstAddr))
+ return PTI->getPointerOperand();
+
+ return DstAddr;
+}
+
+bool PISALayout::isReturnBlock(BasicBlock *BB) {
+ return isa<ReturnInst>(BB->getTerminator());
+}
+
+// Try moving atomic write (or its loop) into the given destination loop
+// If there are no direct predecessor in the needed loop,
+// Try to move it together with a chain of predecessors. New BB is added in
+// chain if it is either single predecessor or it is a previous node in current
+// layout.
+//
+bool PISALayout::tryMovingWrite(Instruction *Write, Loop *LP, LoopInfo &LI) {
+ SmallVector<BasicBlock *> BlocksToMove;
+
+ if (Loop *WritingLoop = LI.getLoopFor(Write->getParent())) {
+ auto Blocks = WritingLoop->getBlocks();
+ for (auto &BB : Blocks) {
+ if (isReturnBlock(BB))
+ return false;
+ BlocksToMove.push_back(BB);
+ }
+ } else {
+ if (!isReturnBlock(Write->getParent()))
+ BlocksToMove.push_back(Write->getParent());
+ else
+ return false;
+ }
+
+ // Loop exits when:
+ // - all BasicBlocks with the Write or its loop has been moved
+ // - processed BasicBlock contains a return or has more than one predecessor
+ while (true) {
+ BasicBlock *Blk = BlocksToMove.back();
+
+ // If one (and only one) of the predecessors is in the needed loop, move
+ // blocks after it
+ BasicBlock *InsertPoint = nullptr;
+ int PredsInLoop = 0;
+ for (pred_iterator PredIter = pred_begin(Blk), PredEnd = pred_end(Blk);
+ PredIter != PredEnd; ++PredIter) {
+ BasicBlock *Pred = *PredIter;
+ if (LP->contains(Pred)) {
+ PredsInLoop++;
+ InsertPoint = Pred;
+ }
+ }
+ if (PredsInLoop == 1) {
+ for (auto *BB : BlocksToMove)
+ BB->moveAfter(InsertPoint);
+ return true;
+ }
+ if (PredsInLoop > 1)
+ return false;
+
+ // Add prev node if it is the predecessor of the block
+ bool PredPushed = false;
+ for (pred_iterator PredIter = pred_begin(Blk), PredEnd = pred_end(Blk);
+ PredIter != PredEnd; ++PredIter) {
+ BasicBlock *Pred = *PredIter;
+
+ if ((Pred == Blk->getPrevNode()) && !isReturnBlock(Pred)) {
+ BlocksToMove.push_back(Pred);
+ PredPushed = true;
+ break;
+ }
+ }
+
+ if (PredPushed)
+ continue;
+
+ // Add predecessor if it is single
+ BasicBlock *Pred = Blk->getSinglePredecessor();
+ if (Pred && !isReturnBlock(Pred)) {
+ BlocksToMove.push_back(Pred);
+ PredPushed = true;
+ } else
+ // Don't move the blocks and return
+ return false;
+ }
+}
+
+// Place basic blocks with atomic write (or the whole loop with the
+// atomic write) into the other loop if there is an atomic read
+// from the same memory, which dominates the write.
+//
+// It benefits cases like:
+//
+// Loop:
+// Load A
+// if (!pred(Load A))
+// {
+// break;
+// }
+// if (success(do_work())
+// {
+// Store A;
+// break;
+// }
+// Br Loop
+//
+// If the Store is placed after the back edge of the loop
+// there will be goto instruction disabling channels based on some
+// "success(do_work())" condition placed before the back edge in SIMD control
+// flow, and the store will be delayed until the whole loop is finished. It
+// makes "if (!pred(Load A))" checking useless and doesn't allow to perform
+// early break based on the condition.
+//
+void PISALayout::moveAtomicWrites2Loop(Function &F, LoopInfo &LI,
+ bool OnlyLocalMem) {
+ SmallVector<Instruction *> Writes;
+ SmallVector<Instruction *> Reads;
+ for (auto &I : instructions(F)) {
+ if (isAtomicWrite(&I, OnlyLocalMem))
+ Writes.push_back(&I);
+ else if (isAtomicRead(&I, OnlyLocalMem))
+ Reads.push_back(&I);
+ }
+
+ // write: LoopWhereToMove mapping
+ MapVector<Instruction *, Loop *> WritesToMove;
+
+ for (auto *Read : Reads)
+ for (auto *Write : Writes)
+ if (getMemoryOperand(Read, OnlyLocalMem) ==
+ getMemoryOperand(Write, OnlyLocalMem)) {
+ Loop *ReadLoop = LI.getLoopFor(Read->getParent());
+ Loop *WriteLoop = LI.getLoopFor(Write->getParent());
+ if (ReadLoop && (ReadLoop != WriteLoop) &&
+ ((DT->dominates(Read, Write))))
+ WritesToMove[Write] = LI.getLoopFor(Read->getParent());
+ }
+
+ for (const auto &Pair : WritesToMove) {
+ Instruction *Write = Pair.first;
+ Loop *Loop = Pair.second;
+
+ tryMovingWrite(Write, Loop, LI);
+ }
+}
+
+static bool hasThreadGroupBarrierInBlock(BasicBlock *BB) {
+ Module *M = BB->getParent()->getParent();
+ for (Function &F : *M) {
+ if (!F.isDeclaration() || !F.isIntrinsic())
+ continue;
+
+ Intrinsic::ID IntrID = F.getIntrinsicID();
+ if (IntrID == Intrinsic::pisa_workgroup_barrier)
+ for (auto *U : F.users()) {
+ auto *Inst = dyn_cast<Instruction>(U);
+ if (Inst && Inst->getParent() == BB)
+ return true;
+ }
+ }
+ return false;
+}
+
+BasicBlock *PISALayout::getLastReturnBlock(Function &F) {
+ // If Func has any return BB, return the last return BB (may have multiple);
+ // otherwise, return the last BB that has no succ;
+ // or nullptr if every BB has Succ (infinite looping)
+ BasicBlock *NoRetAndNoSucc = nullptr; // for func that never returns
+ for (auto RI = std::make_reverse_iterator(F.end()),
+ RE = std::make_reverse_iterator(F.begin());
+ RI != RE; ++RI) {
+ BasicBlock *BB = &*RI;
+ if (succ_begin(BB) == succ_end(BB)) {
+ if (isa_and_nonnull<ReturnInst>(BB->getTerminator()))
+ return BB;
+ if (!NoRetAndNoSucc)
+ NoRetAndNoSucc = BB;
+ }
+ }
+ // Function does not have a return block
+ return NoRetAndNoSucc;
+}
+
+//
+// selectSucc: select a succ with condition SelectNoInstBlk and return it.
+//
+// This is used to select one if there are two Successors with condition
+// SelectNoInstBlk, rather than take the first one in the succ list.
+//
+// Condition SelectNoInstBlk: If SelectNoInstBlk is true, select an empty
+// block, if it is false, select non-empty block.
+//
+BasicBlock *PISALayout::selectSucc(BasicBlock *CurrBlk, bool SelectNoInstBlk,
+ const LoopInfo &LI,
+ const SmallSet<BasicBlock *, 8> &VisitSet) {
+ SmallVector<BasicBlock *, 4> Succs;
+ for (succ_iterator SI = succ_begin(CurrBlk), SE = succ_end(CurrBlk); SI != SE;
+ ++SI) {
+ BasicBlock *Succ = *SI;
+ auto Size = sizeWithoutDebug(Succ);
+ if (VisitSet.count(Succ) == 0 &&
+ ((SelectNoInstBlk && Size <= 1) || (!SelectNoInstBlk && Size > 1)))
+ Succs.push_back(Succ);
+ }
+
+ // Right now, only handle the case of two empty blocks.
+ // If it has no two empty blocks, just take the first
+ // one and return it.
+ if (Succs.size() != 2 || !SelectNoInstBlk)
+ return Succs.empty() ? nullptr : Succs[0];
+
+ // For two empty blocks, the case we want to handle
+ // is the following:
+ //
+ // (B0 = CurrBlk)
+ // B0 : if (c) goto THEN (else goto ELSE)
+ // ELSE : goto B2
+ // B1 : ....
+ // B2 : ....
+ // ......
+ // Bn :
+ // (ELSE, B1, B2, ..., Bn) has END as single exit
+ // THEN: goto END:
+ // END :
+ // PHI...
+ //
+ // where ELSE and THEN are empty BBs, and END has phi in it.
+ // In this case, THEN and ELSE might have phi moves as the result
+ // DeSSA when emitting visa. For example, suppose d0 = s0 will
+ // be emitted in THEN. If s0 is dead after THEN, it would be good
+ // to lay out THEN right after B0 as the live-range of s0 will not
+ // be overlapped with ones in ELSE. (If s0 is live out of THEN,
+ // moving THEN right after B0 or right before END does not matter
+ // as far as liveness is concerned.). To lay out THEN first, this
+ // function will select ELSE to return (as the algo does layout
+ // backward).
+ //
+ // For simplicity, assume those BBs are not inside loops. It could
+ // be applied to Loop later when appropriate testing is done.
+ BasicBlock *Suc0 = Succs[0], *Suc1 = Succs[1];
+ BasicBlock *SS0 = Suc0->getSingleSuccessor();
+
+ if (SS0 && (SS0 != Suc1) && isa<PHINode>(&*SS0->begin()) &&
+ !LI.getLoopFor(Suc0) && PDT->dominates(SS0, Suc1))
+ return Suc1;
+
+ return Suc0;
+}
+
+void PISALayout::layoutBlocks(Function &F, LoopInfo &LI) {
+ SmallVector<BasicBlock *> VisitVec;
+ SmallSet<BasicBlock *, 8> VisitSet;
+ // Insertion Position per loop header
+ MapVector<BasicBlock *, BasicBlock *> InsPos;
+
+ BasicBlock *Entry = &(F.getEntryBlock());
+ VisitVec.push_back(Entry);
+ VisitSet.insert(Entry);
+ InsPos[Entry] = Entry;
+
+ // Push a return block to make sure the last BB is the return block.
+ if (BasicBlock *LastReturnBlock = getLastReturnBlock(F)) {
+ if (LastReturnBlock != Entry) {
+ VisitVec.push_back(LastReturnBlock);
+ VisitSet.insert(LastReturnBlock);
+ }
+ }
+
+ while (!VisitVec.empty()) {
+ BasicBlock *BB = VisitVec.back();
+ Loop *CurLoop = LI.getLoopFor(BB);
+ if (CurLoop) {
+ auto *HD = CurLoop->getHeader();
+ if (BB == HD && InsPos.find(HD) == InsPos.end())
+ InsPos[BB] = BB;
+ }
+
+ // push: time for DFS visit
+ auto PushHasInstCond = [](BasicBlock *Succ) -> bool {
+ return sizeWithoutDebug(Succ) > 1;
+ };
+ pushSucc(BB, PushHasInstCond, VisitVec, VisitSet);
+ if (BB != VisitVec.back())
+ continue;
+ // push: time for DFS visit
+ if (BasicBlock *ABlk = selectSucc(BB, true, LI, VisitSet)) {
+ VisitVec.push_back(ABlk);
+ VisitSet.insert(ABlk);
+ continue;
+ }
+
+ // pop: time to move the block to the right location
+ if (BB == VisitVec.back()) {
+ VisitVec.pop_back();
+ if (CurLoop) {
+ auto *HD = CurLoop->getHeader();
+ if (BB != HD) {
+ // move the block to the beginning of the loop
+ auto *Insp = InsPos[HD];
+ assert(Insp);
+ if (BB != Insp) {
+ BB->moveBefore(Insp);
+ InsPos[HD] = BB;
+ }
+ } else {
+ // move the entire loop to the beginning of
+ // the parent loop
+ auto *LoopStart = InsPos[HD];
+ assert(LoopStart);
+ auto *PaLoop = CurLoop->getParentLoop();
+ auto *PaHd = PaLoop ? PaLoop->getHeader() : Entry;
+ auto *Insp = InsPos[PaHd];
+ if (LoopStart == HD)
+ // single-block loop
+ HD->moveBefore(Insp);
+ else {
+ // loop-header is not moved yet, so should be at the end
+ // use splice
+ F.splice(Insp->getIterator(), &F, LoopStart->getIterator(),
+ HD->getIterator());
+ HD->moveBefore(LoopStart);
+ }
+ InsPos[PaHd] = HD;
+ }
+ } else {
+ auto *Insp = InsPos[Entry];
+ if (BB != Insp) {
+ BB->moveBefore(Insp);
+ InsPos[Entry] = BB;
+ }
+ }
+ }
+ }
+
+ moveAtomicWrites2Loop(F, LI, false);
+
+ // if function has a single exit, then the last block must be an exit
+ // fix the loop-exit pattern, put break-blocks into the loop
+ for (BasicBlock &BB : F) {
+ Loop *CurLoop = LI.getLoopFor(&BB);
+ bool AllPredLoopExit = true;
+ unsigned NumPreds = 0;
+ SmallPtrSet<BasicBlock *, 4> PredSet;
+ for (pred_iterator PredIter = pred_begin(&BB), PredEnd = pred_end(&BB);
+ PredIter != PredEnd; ++PredIter) {
+ BasicBlock *Pred = *PredIter;
+ NumPreds++;
+ Loop *PredLoop = LI.getLoopFor(Pred);
+ if (CurLoop == PredLoop) {
+ BasicBlock *PredPred = Pred->getSinglePredecessor();
+ if (PredPred) {
+ Loop *PredPredLoop = LI.getLoopFor(PredPred);
+ if (PredPredLoop != CurLoop &&
+ (!CurLoop || CurLoop->contains(PredPredLoop))) {
+ // Debug instructions should not be counted into considered size
+ if (sizeWithoutDebug(Pred) <= BreakBlockSizeLimit &&
+ !hasThreadGroupBarrierInBlock(Pred))
+ PredSet.insert(Pred);
+ else
+ AllPredLoopExit = false;
+ break;
+ }
+ }
+ } else if (!CurLoop || CurLoop->contains(PredLoop))
+ continue;
+ else {
+ AllPredLoopExit = false;
+ break;
+ }
+ }
+ if (AllPredLoopExit && NumPreds > 1) {
+ for (BasicBlock *Pred : PredSet) {
+ BasicBlock *PredPred = Pred->getSinglePredecessor();
+ Pred->moveAfter(PredPred);
+ }
+ }
+ }
+}
+
+void PISALayout::layoutBlocks(Function &F) {
+ SmallVector<BasicBlock *> VisitVec;
+ SmallSet<BasicBlock *, 8> VisitSet;
+ // Reorder basic block to allow more fall-through
+ BasicBlock *Entry = &(F.getEntryBlock());
+ VisitVec.push_back(Entry);
+
+ // Push a return block to make sure the last BB is the return block.
+ if (BasicBlock *LastReturnBlock = getLastReturnBlock(F))
+ if (LastReturnBlock != Entry) {
+ VisitVec.push_back(LastReturnBlock);
+ VisitSet.insert(LastReturnBlock);
+ }
+
+ while (!VisitVec.empty()) {
+ BasicBlock *BB = VisitVec.back();
+ // push in the empty successor
+ auto PushNoInstCond = [](BasicBlock *Succ) -> bool {
+ return sizeWithoutDebug(Succ) <= 1;
+ };
+ pushSucc(BB, PushNoInstCond, VisitVec, VisitSet);
+ if (BB != VisitVec.back())
+ continue;
+ // push in all the same-loop successors
+ auto PushAnyCond = [](BasicBlock *Succ) -> bool { return true; };
+ pushSucc(BB, PushAnyCond, VisitVec, VisitSet);
+ // pop
+ if (BB == VisitVec.back()) {
+ VisitVec.pop_back();
+ if (BB != Entry) {
+ BB->moveBefore(Entry);
+ Entry = BB;
+ }
+ }
+ }
+}
+
+FunctionPass *llvm::createPISALayoutPass() { return new PISALayout(); }
diff --git a/llvm/lib/Target/PISA/PISALegalizeCalls.cpp b/llvm/lib/Target/PISA/PISALegalizeCalls.cpp
new file mode 100644
index 0000000000000..d2972e6ab525b
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISALegalizeCalls.cpp
@@ -0,0 +1,505 @@
+//===-- PISALegalizeCalls.cpp - modify function signatures ----------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Modify function argument/return types to the ones support by PISA:
+// - natively supported types are used as is
+// - i8,i16,i32, and i64 scalar types
+// - vectors if 2,3, and 4 elements, with element types being i8,i16, and i64
+// - vectors if 2,3,4,5,6,7,8,16 and 32 elements, with element type being i32
+// - vectors of a single element are scalarized
+// - vectors of pointers are treated as equivalent native integer type
+// - vectors of p3/p4 pointers are treated as vectors of i32
+// - vectors of p0/p1/p2 pointers are treated as vectors of i64
+// - i1 and i4 types are extended/truncated to i16
+// - vectors of i1 are extended to ^2 size, cast to native integer type
+// - all other types are passed in via memory argument
+//===------------------------------------------------------------------===//
+
+#include "PISA.h"
+#include "llvm/IR/AttributeMask.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstVisitor.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/Support/PISAAddrSpace.h"
+#include "llvm/Transforms/Utils/Cloning.h"
+#include "llvm/Transforms/Utils/LowerMemIntrinsics.h"
+#include "llvm/Transforms/Utils/ValueMapper.h"
+
+#define DEBUG_TYPE "pisa-legalize-calls"
+#define DEBUG_NAME "PISA legalize calls"
+
+using namespace llvm;
+
+namespace {
+
+class PISALegalizeCalls : public ModulePass,
+ public InstVisitor<PISALegalizeCalls> {
+public:
+ static char ID;
+ PISALegalizeCalls() : ModulePass(ID) {}
+
+ StringRef getPassName() const override { return DEBUG_NAME; }
+
+ void visitCallInst(CallInst &CI);
+ void visitIntrinsicInst(IntrinsicInst &I);
+ void visitReturnInst(ReturnInst &RI);
+
+private:
+ SmallVector<Function *, 8> Funcs;
+ SmallVector<CallInst *, 8> Calls;
+ SmallVector<ReturnInst *, 8> Returns;
+
+ bool runOnModule(Module &M) override;
+ bool needsModification(Type *Ty, const DataLayout &DL);
+ Type *getModifiedType(Type *, const DataLayout &, LLVMContext &);
+
+ void collectFuncs(Function &F);
+ void modifyFunctionSignature(Function &F);
+
+ void modifyReturnInst(ReturnInst *RI);
+ void modifyCallInst(CallInst *CI);
+};
+} // namespace
+
+char PISALegalizeCalls::ID = 0;
+INITIALIZE_PASS(PISALegalizeCalls, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+// determine if type will be modified in new function
+bool PISALegalizeCalls::needsModification(Type *Ty, const DataLayout &DL) {
+ bool Modify = false;
+ if (Ty->isVoidTy() || Ty->isPointerTy())
+ return false;
+
+ if (auto *VecTy = dyn_cast<FixedVectorType>(Ty)) {
+ auto NumElts = VecTy->getNumElements();
+ auto EltSize = VecTy->getScalarSizeInBits();
+ if (EltSize == 0) {
+ // vectors of pointers are treated as underlying integer
+ assert(VecTy->getElementType()->isPointerTy());
+ auto AS = VecTy->getElementType()->getPointerAddressSpace();
+ EltSize = DL.getPointerSizeInBits(AS);
+ }
+ switch (EltSize) {
+ case 0: { // vector of pointers
+ assert(VecTy->getElementType()->isPointerTy());
+ Modify = (NumElts > 4);
+ } break;
+ case 1:
+ case 4:
+ Modify = true;
+ break;
+ case 8:
+ case 16:
+ case 64:
+ Modify = (NumElts == 1) || (NumElts > 4);
+ break;
+ case 32:
+ Modify = (NumElts == 1) || ((NumElts > 8) && (NumElts != 16) &&
+ (NumElts != 32) && (NumElts != 64));
+ break;
+ }
+ } else if (auto *IntTy = dyn_cast<IntegerType>(Ty)) {
+ switch (IntTy->getScalarSizeInBits()) {
+ default:
+ llvm_unreachable("unsupported integer type");
+ break;
+ case 1:
+ case 4:
+ case 128:
+ Modify = true;
+ break;
+ case 8:
+ case 16:
+ case 32:
+ case 64:
+ break;
+ }
+ } else if (Ty->isFloatingPointTy()) {
+ switch (Ty->getScalarSizeInBits()) {
+ default:
+ llvm_unreachable("unsupported fp type");
+ break;
+ case 16:
+ case 32:
+ case 64:
+ break;
+ }
+ } else if (isa<StructType>(Ty)) {
+ Modify = true;
+ } else {
+ llvm_unreachable("unsupported type");
+ }
+ return Modify;
+}
+
+Type *PISALegalizeCalls::getModifiedType(Type *Ty, const DataLayout &DL,
+ LLVMContext &Ctx) {
+ Type *NewTy = Ty;
+ if (needsModification(Ty, DL)) {
+ if (isa<IntegerType>(Ty)) {
+ if (Ty->getScalarSizeInBits() == 1 || Ty->getScalarSizeInBits() == 4) {
+ NewTy = IntegerType::get(Ctx, 16); // i{1, 4} => i16
+ } else if (Ty->getScalarSizeInBits() == 128) {
+ auto *EltTy = IntegerType::get(Ctx, 64);
+ NewTy = FixedVectorType::get(EltTy, 2); // i128 => v2i64
+ } else {
+ llvm_unreachable("unsupported type to be modified");
+ }
+ } else if (auto *VecTy = dyn_cast<FixedVectorType>(Ty)) {
+ if (VecTy->getNumElements() == 1) { // <1 x i?> => i?
+ NewTy = VecTy->getElementType();
+ } else if (VecTy->getScalarSizeInBits() == 1) { // <? x i1> => i16
+ auto NewSize =
+ std::max((unsigned)PowerOf2Ceil(VecTy->getNumElements()), 16u);
+ NewTy = IntegerType::get(Ctx, NewSize);
+ } else { // e.g. <8 x i8>
+ int TotalSize = VecTy->getNumElements() * VecTy->getScalarSizeInBits();
+ int NumEle = TotalSize / 32;
+ if (NumEle > 0 && TotalSize % 32 == 0) {
+ Type *NewElemTy = IntegerType::get(Ctx, 32);
+ NewTy = FixedVectorType::get(NewElemTy, NumEle);
+ }
+ if (needsModification(NewTy, DL))
+ NewTy =
+ PointerType::get(Ctx, (unsigned)PISAAS::AddressSpace::PRIVATE);
+ }
+ } else {
+ NewTy = PointerType::get(Ctx, (unsigned)PISAAS::AddressSpace::PRIVATE);
+ }
+ }
+ return NewTy;
+}
+
+// record all call instructions that will require modification
+void PISALegalizeCalls::visitCallInst(CallInst &CI) {
+ auto *Caller = CI.getFunction();
+ auto *Callee = CI.getCalledFunction();
+
+ if (!Callee)
+ Callee = dyn_cast<Function>(CI.getCalledOperand());
+ if (!Callee && dyn_cast<InlineAsm>(CI.getCalledOperand()))
+ return;
+
+ assert((Callee || isa<PointerType>(CI.getCalledOperand()->getType())) &&
+ "unable to extract callee info");
+
+ // Indirect call
+ if (!Callee && !isa<PointerType>(CI.getCalledOperand()->getType()))
+ return;
+
+ // call must with pisa cc
+ if (CI.getCallingConv() == CallingConv::PISA_KERNEL)
+ return;
+
+ bool ArgNeedModification = llvm::any_of(CI.args(), [&](Value *Arg) {
+ auto *SType = Arg->getType();
+ return needsModification(SType, Caller->getParent()->getDataLayout());
+ });
+ if (ArgNeedModification ||
+ needsModification(CI.getType(), Caller->getParent()->getDataLayout()))
+ Calls.push_back(&CI);
+}
+
+void PISALegalizeCalls::visitIntrinsicInst(IntrinsicInst &I) {
+ assert(!IntrinsicInst::mayLowerToFunctionCall(I.getIntrinsicID()) &&
+ "Pre-Isel Intrinsics should be lowered to function calls by now");
+ return;
+}
+
+// record all return instructions that will require modification
+void PISALegalizeCalls::visitReturnInst(ReturnInst &RI) {
+ auto *F = RI.getFunction();
+
+ if (F->getCallingConv() == CallingConv::PISA_KERNEL)
+ return;
+
+ if (auto *RV = RI.getReturnValue()) {
+ if (needsModification(RV->getType(), F->getParent()->getDataLayout())) {
+ Returns.push_back(&RI);
+ }
+ }
+}
+
+void PISALegalizeCalls::collectFuncs(Function &F) {
+ if (F.isIntrinsic() || F.getCallingConv() == CallingConv::PISA_KERNEL)
+ return;
+
+ bool ToAdd = false;
+ for (auto &Arg : F.args()) {
+ if (needsModification(Arg.getType(), F.getParent()->getDataLayout()))
+ ToAdd = true;
+ }
+ auto *Ty = F.getFunctionType()->getReturnType();
+ if (needsModification(Ty, F.getParent()->getDataLayout()))
+ ToAdd = true;
+
+ if (ToAdd)
+ Funcs.push_back(&F);
+}
+
+void PISALegalizeCalls::modifyFunctionSignature(Function &F) {
+ auto &DL = F.getParent()->getDataLayout();
+ auto &Ctx = F.getContext();
+
+ AttributeList AL = F.getAttributes();
+ SmallVector<Type *> NewArgTys;
+ for (unsigned I = 0, E = F.arg_size(); I < E; ++I) {
+ auto *NewTy = getModifiedType(F.getArg(I)->getType(), DL, Ctx);
+ NewArgTys.push_back(NewTy);
+ AL = AL.removeParamAttributes(
+ Ctx, I,
+ AttributeFuncs::typeIncompatible(NewTy, F.getArg(I)->getAttributes()));
+ }
+ auto *RetTy = F.getFunctionType()->getReturnType();
+ auto *NewRetTy = getModifiedType(RetTy, DL, Ctx);
+ if (RetTy != NewRetTy) {
+ // return via hidden memory arg
+ if (NewRetTy->isPointerTy()) {
+ NewArgTys.push_back(NewRetTy);
+ NewRetTy = Type::getVoidTy(Ctx);
+ // void functions cannot return any argument value
+ for (const auto &Arg : F.args())
+ AL = AL.removeParamAttribute(Ctx, Arg.getArgNo(), Attribute::Returned);
+ }
+ AL = AL.removeRetAttributes(
+ Ctx, AttributeFuncs::typeIncompatible(NewRetTy,
+ F.getAttributes().getRetAttrs()));
+ }
+
+ // update function definition
+ FunctionType *FTy = FunctionType::get(NewRetTy, NewArgTys, false);
+ Function *NewF = Function::Create(FTy, F.getLinkage(), F.getAddressSpace());
+ ValueToValueMapTy VMap;
+
+ // map args 1:1, but types will be different.
+ // modify actual references to args below.
+ for (unsigned I = 0; I < F.arg_size(); I++) {
+ auto *SArg = F.getArg(I);
+ VMap[SArg] = SArg;
+ }
+
+ SmallVector<ReturnInst *, 8> Returns;
+ CloneFunctionInto(NewF, &F, VMap,
+ llvm::CloneFunctionChangeType::LocalChangesOnly, Returns,
+ "", 0);
+ F.getParent()->getFunctionList().insert(F.getIterator(), NewF);
+
+ // transform new arg types into ones expected within function
+ IRBuilder<> IRB(Ctx);
+ if (!NewF->isDeclaration()) {
+ auto FirstBB = NewF->begin();
+ IRB.SetInsertPoint(FirstBB->begin());
+ }
+ for (unsigned I = 0; I < F.arg_size(); I++) {
+ auto *SArg = F.getArg(I);
+ auto *DArg = NewF->getArg(I);
+ auto *SType = SArg->getType(); // type to change from
+ auto *DType = DArg->getType(); // type to change to
+ if (!NewF->isDeclaration()) {
+ if (SType != DType) {
+ if (DType->isPointerTy()) { // value passed via memory
+ auto *Load = IRB.CreateLoad(SType, DArg);
+ SArg->replaceAllUsesWith(Load);
+ } else {
+ auto SSize = DL.getTypeSizeInBits(SType);
+ auto DSize = DL.getTypeSizeInBits(DType);
+ if (SSize == DSize) { // <1 x i?> => i?
+ auto *DCast = IRB.CreateBitCast(DArg, SType);
+ SArg->replaceAllUsesWith(DCast);
+ } else if (SType->isIntegerTy(1) ||
+ SType->isIntegerTy(4)) { // i8 => i{1, 4}
+ auto *Trunc = IRB.CreateTrunc(DArg, SType);
+ SArg->replaceAllUsesWith(Trunc);
+ } else if (SType->isVectorTy()) { // i16 => <? x i1>
+ assert(SType->getScalarSizeInBits() == 1);
+ auto *ScalarType = IntegerType::get(Ctx, SSize);
+ auto *Trunc = IRB.CreateTrunc(DArg, ScalarType);
+ auto *Cast = IRB.CreateBitCast(Trunc, SType);
+ SArg->replaceAllUsesWith(Cast);
+ } else {
+ llvm_unreachable("unsupported argument type");
+ }
+ }
+ } else {
+ SArg->replaceAllUsesWith(DArg);
+ }
+ }
+ }
+ // return modification handled in visitReturnInst
+ NewF->takeName(&F);
+ NewF->setAttributes(AL);
+ NewF->setCallingConv(F.getCallingConv());
+ F.replaceAllUsesWith(NewF);
+ F.eraseFromParent();
+}
+
+void PISALegalizeCalls::modifyReturnInst(ReturnInst *RI) {
+ auto *F = RI->getFunction();
+ auto &Ctx = F->getContext();
+ assert(F->getCallingConv() != CallingConv::PISA_KERNEL &&
+ "return instruction in kernel");
+
+ auto &DL = F->getParent()->getDataLayout();
+ auto *RV = RI->getReturnValue();
+ auto *SType = RV->getType(); // type to change from
+ auto *DType = F->getFunctionType()->getReturnType(); // type to change to
+ if (!SType->isVoidTy() && (SType != DType)) {
+ IRBuilder<> IRB(dyn_cast<Instruction>(RI));
+ if (DType->isVoidTy()) {
+ // return via hidden memory arg
+ auto *Ptr = F->getArg(F->arg_size() - 1);
+ IRB.CreateStore(RV, Ptr);
+ IRB.CreateRet(nullptr);
+ } else {
+ auto SSize = DL.getTypeSizeInBits(SType);
+ auto DSize = DL.getTypeSizeInBits(DType);
+ if (SSize == DSize) { // <1 x i?> => i?
+ auto *DCast = IRB.CreateBitCast(RV, DType);
+ IRB.CreateRet(DCast);
+ } else if (SType->isIntegerTy(1) ||
+ SType->isIntegerTy(4)) { // i{1, 4} => i8
+ auto SExt = F->hasRetAttribute(Attribute::SExt);
+ auto *Extend =
+ SExt ? IRB.CreateSExt(RV, DType) : IRB.CreateZExt(RV, DType);
+ IRB.CreateRet(Extend);
+ } else if (SType->isVectorTy()) { // <? x i1> => i16
+ assert(SType->getScalarSizeInBits() == 1);
+ auto *ScalarType = IntegerType::get(Ctx, SSize);
+ auto *Cast = IRB.CreateBitCast(RV, ScalarType);
+ auto *Extend = IRB.CreateZExt(Cast, DType);
+ IRB.CreateRet(Extend);
+ } else {
+ llvm_unreachable("unsupported return type");
+ }
+ }
+ RI->eraseFromParent();
+ }
+}
+
+void PISALegalizeCalls::modifyCallInst(CallInst *CI) {
+ auto *F = CI->getFunction();
+ auto &Ctx = F->getContext();
+ auto &DL = F->getParent()->getDataLayout();
+
+ auto *Callee = CI->getCalledOperand();
+ SmallVector<Value *, 8> NewArgs;
+ SmallVector<Type *, 8> NewArgTys;
+
+ IRBuilder<> IRB(dyn_cast<Instruction>(CI));
+ for (unsigned I = 0; I < CI->arg_size(); I++) {
+ auto *Arg = CI->getArgOperand(I);
+ auto *SType = Arg->getType(); // type to change from
+ auto *DType = getModifiedType(SType, DL, Ctx); // type to change to
+ Value *NewV = nullptr;
+ if (SType != DType) {
+ if (DType->isPointerTy()) { // pass via memory
+ NewV = IRB.CreateAlloca(SType);
+ IRB.CreateStore(Arg, NewV);
+ } else {
+ auto SSize = DL.getTypeSizeInBits(SType);
+ auto DSize = DL.getTypeSizeInBits(DType);
+ if (SSize == DSize) { // <1 x i?> => i?
+ NewV = IRB.CreateBitCast(Arg, DType);
+ } else if (SType->isIntegerTy(1) ||
+ SType->isIntegerTy(4)) { // i{1, 4} => i8
+ auto SExt = CI->getParamAttr(I, Attribute::SExt).getKindAsEnum() ==
+ Attribute::SExt;
+ NewV = SExt ? IRB.CreateSExt(Arg, DType) : IRB.CreateZExt(Arg, DType);
+ } else if (SType->isVectorTy()) { // <? x i1> => i16
+ assert(SType->getScalarSizeInBits() == 1);
+ auto *ScalarType = IntegerType::get(Ctx, SSize);
+ auto *Cast = IRB.CreateBitCast(Arg, ScalarType);
+ NewV = IRB.CreateZExt(Cast, DType);
+ } else {
+ llvm_unreachable("unsupported call argument type");
+ }
+ }
+ }
+ if (NewV) {
+ NewArgs.push_back(NewV);
+ NewArgTys.push_back(NewV->getType());
+ } else {
+ NewArgs.push_back(Arg);
+ NewArgTys.push_back(Arg->getType());
+ }
+ }
+
+ auto *RetTy = CI->getType();
+ auto *NewRetTy = RetTy;
+ Value *RetAlloca = nullptr;
+ NewRetTy = getModifiedType(RetTy, DL, Ctx);
+ if ((RetTy != NewRetTy) && NewRetTy->isPointerTy()) {
+ // return via hidden memory arg
+ RetAlloca = IRB.CreateAlloca(RetTy);
+ NewArgs.push_back(RetAlloca);
+ NewArgTys.push_back(NewRetTy);
+ NewRetTy = Type::getVoidTy(Ctx);
+ }
+ auto *FTy = FunctionType::get(NewRetTy, NewArgTys, false);
+ auto *NewCI = IRB.CreateCall(FTy, Callee, NewArgs);
+ NewCI->setCallingConv(CI->getCallingConv());
+
+ // handle return value
+ if (RetTy != NewRetTy) {
+ auto SSize = DL.getTypeSizeInBits(RetTy);
+ auto DSize = NewRetTy->isVoidTy() ? 0u : DL.getTypeSizeInBits(NewRetTy);
+ if (SSize == DSize) { // i? => <1 x i?>
+ auto *Bitcast = IRB.CreateBitCast(NewCI, RetTy);
+ CI->replaceAllUsesWith(Bitcast);
+ } else if (RetTy->isIntegerTy(1) ||
+ RetTy->isIntegerTy(4)) { // i8 => i{1, 4}
+ auto *Trunc = IRB.CreateTrunc(NewCI, RetTy);
+ CI->replaceAllUsesWith(Trunc);
+ } else if (DSize == 0) {
+ // return via hidden memory arg
+ auto *Load = IRB.CreateLoad(RetTy, RetAlloca);
+ CI->replaceAllUsesWith(Load);
+ } else if (RetTy->isVectorTy()) { // i16 => <? x i1>
+ assert(RetTy->getScalarSizeInBits() == 1);
+ auto *ScalarType = IntegerType::get(Ctx, SSize);
+ auto *Trunc = IRB.CreateTrunc(NewCI, ScalarType);
+ auto *Cast = IRB.CreateBitCast(Trunc, RetTy);
+ CI->replaceAllUsesWith(Cast);
+ } else {
+ llvm_unreachable("unsupported return type");
+ }
+ } else {
+ CI->replaceAllUsesWith(NewCI);
+ }
+ CI->eraseFromParent();
+}
+
+bool PISALegalizeCalls::runOnModule(Module &M) {
+ // record functions to be modified
+ for (auto &F : M) {
+ collectFuncs(F);
+ }
+ // modify function signatures
+ for (auto &F : Funcs) {
+ modifyFunctionSignature(*F);
+ }
+
+ // record call/return instructions
+ for (auto &F : M) {
+ visit(F);
+ }
+ // modify call/return instructions
+ for (auto &I : Returns) {
+ modifyReturnInst(I);
+ }
+ for (auto &I : Calls) {
+ modifyCallInst(I);
+ }
+ return !(Calls.empty() && Returns.empty() && Funcs.empty());
+}
+
+ModulePass *llvm::createPISALegalizeCallsPass() {
+ return new PISALegalizeCalls();
+}
diff --git a/llvm/lib/Target/PISA/PISALegalizePredicates.cpp b/llvm/lib/Target/PISA/PISALegalizePredicates.cpp
new file mode 100644
index 0000000000000..86f9e37238329
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISALegalizePredicates.cpp
@@ -0,0 +1,994 @@
+//=== PISALegalizePredicates.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Unified pre-legalization strategy for bitwise operations on predicate
+// (i1) values. PISA has no bitwise operations on predicate registers, and
+// `cmp` instructions yield i32 -1/0 values. The legalizer would
+// widen i1 chains piecewise (i1 -> i32 for bitwise, i8/i16 for other sub-i16
+// types), leaving redundant ext/trunc pairs and missing fusion opportunities
+// (e.g. bfn).
+//
+// This pass walks forward from every G_ICMP/G_FCMP result and rewrites any
+// chain of bitwise-on-i1, sext/zext/anyext-of-i1, vector-extract from a
+// vector-of-i1, and trunc-back-to-i1 to operate uniformly on i32. Consumers
+// that still need an i1 (G_BRCOND, G_SELECT condition, etc.) are fed via a
+// single G_ICMP ne 0 restoration at the boundary.
+//
+// Pipeline placement: scheduled in addPreLegalizeMachineIR(); the pass only
+// rewrites scalar/vector s1 chains and leaves other pre-legalization results
+// undisturbed.
+//
+//===----------------------------------------------------------------------===//
+
+#include "MCTargetDesc/PISAMCTargetDesc.h"
+#include "PISA.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/CodeGen/MachineInstr.h"
+#include "llvm/CodeGen/MachineRegisterInfo.h"
+#include "llvm/IR/InstrTypes.h"
+#include "llvm/Support/Debug.h"
+
+#define DEBUG_TYPE "pisa-legalize-predicates"
+#define DEBUG_NAME "PISA Legalize Predicates"
+
+using namespace llvm;
+
+STATISTIC(NumPromoted, "Number of registers promoted to s32");
+STATISTIC(NumRestorations, "Number of sink restorations inserted");
+STATISTIC(NumErased, "Number of dead original instructions erased");
+
+namespace {
+
+// ===================== Pass boilerplate =====================
+
+class PISALegalizePredicates : public MachineFunctionPass {
+public:
+ static char ID;
+
+ PISALegalizePredicates() : MachineFunctionPass(ID) {}
+ StringRef getPassName() const override { return DEBUG_NAME; }
+ void getAnalysisUsage(AnalysisUsage &AU) const override;
+ bool runOnMachineFunction(MachineFunction &MF) override;
+};
+
+// ===================== Implementation =====================
+
+class PISALegalizePredicatesImpl {
+ MachineFunction &MF;
+ MachineRegisterInfo &MRI;
+ MachineIRBuilder B;
+
+ // Map from each promotable original Register to its s32 replacement.
+ // Scalar s1 -> s32; <Nxs1> -> <Nxs32>; sN (N>1) coming from sext-of-s1 ->
+ // s32.
+ DenseMap<Register, Register> Promoted;
+
+ // Cache: classification status for registers we've already inspected.
+ enum Status { Unknown, Yes, No };
+ DenseMap<Register, Status> Cache;
+
+ // Magnitude domain of a register's *original* value. The internal promoted
+ // form is always the SEXT representation ({0, -1}); the domain records what
+ // the value was before promotion so the exact bits can be restored at a
+ // non-i1 (integer) sink:
+ // Sext -- true == all-ones ({0, -1}); e.g. OpenCL vector relational.
+ // Zext -- true == 1 ({0, 1}); e.g. OpenCL scalar relational.
+ // Any -- no magnitude (a 1-bit value or a literal 0); matches either side
+ // of a bitwise op.
+ // A bitwise op mixing Sext and Zext operands has an ambiguous result
+ // magnitude and is excluded from promotion (tryGetDomain returns false).
+ enum class Domain : unsigned char { Any, Sext, Zext };
+ // Memo codes: 0 = visiting (cycle guard), 1 = Any, 2 = Sext, 3 = Zext,
+ // 4 = bail.
+ DenseMap<Register, unsigned char> DomMemo;
+
+public:
+ PISALegalizePredicatesImpl(MachineFunction &MF)
+ : MF(MF), MRI(MF.getRegInfo()) {
+ B.setMF(MF);
+ }
+
+ bool run();
+
+private:
+ // ---- Type helpers ----
+ // Returns true for s1 or <N x s1>; getScalarSizeInBits() returns 1 in
+ // either case.
+ static bool isBoolType(LLT Ty) { return Ty.getScalarSizeInBits() == 1; }
+ static LLT promotedType(LLT Ty) {
+ // s1 -> s32; <Nxs1> -> <Nxs32>; sN -> s32; <NxsM> -> <Nxs32>.
+ LLT S32 = LLT::integer(32);
+ if (Ty.isVector())
+ return LLT::fixed_vector(Ty.getNumElements(), S32);
+ return S32;
+ }
+
+ // Opcodes whose originals stay alive in MIR after rewrite (the new s32
+ // chain is built alongside, not in place).
+ static bool isKeptAliveOpcode(unsigned Opc) {
+ return Opc == TargetOpcode::G_ICMP || Opc == TargetOpcode::G_FCMP ||
+ Opc == TargetOpcode::G_CONSTANT || Opc == TargetOpcode::COPY;
+ }
+
+ // ---- Constant value helpers ----
+ // Boolean-domain test: returns true iff the constant is 0 (false),
+ // 1 (interpreted as true on s1), or all-ones (-1 on wider). Other constant
+ // values disqualify promotion because they aren't predicate values. Caller
+ // must guarantee MI is G_CONSTANT.
+ static bool isBoolConst(const MachineInstr &MI, int64_t &PromotedVal) {
+ assert(MI.getOpcode() == TargetOpcode::G_CONSTANT);
+ const APInt &V = MI.getOperand(1).getCImm()->getValue();
+ if (V.isZero()) {
+ PromotedVal = 0;
+ return true;
+ }
+ // For wider types -1 is "all-ones"; for i1 the bit "true" is value 1 and
+ // also already covered by isAllOnes (1-bit all-ones == 1). Either way map
+ // to -1 in the promoted s32 form.
+ if (V.isAllOnes()) {
+ PromotedVal = -1;
+ return true;
+ }
+ return false;
+ }
+
+ // ---- Classification (Phase 1) ----
+ // Returns true if R is part of the promotable chain rooted at some cmp.
+ bool isPromotable(Register R);
+
+ // ---- Use validation (Phase 2) ----
+ // Returns true if a use of R inside UseInstr can be handled (either the use
+ // is itself promotable, or we know how to insert a sink restoration).
+ bool isHandledUse(MachineInstr &UseInstr, Register R);
+
+ // Drop R from the promotion set transitively if any of its uses is
+ // unhandled. Re-iterates to fix-point.
+ void invalidateUnhandled();
+
+ // Compute the original magnitude domain of R (see Domain above). Returns
+ // false if the magnitude is ambiguous (a bitwise op mixing Sext and Zext
+ // operands), meaning R cannot be safely restored at an integer sink and must
+ // not be promoted. Memoized in DomMemo.
+ bool tryGetDomain(Register R, Domain &Out);
+
+ // ---- Rewrite (Phase 3) ----
+ Register getOrBuildPromoted(Register R);
+ Register restoreS1(Register PromotedReg, MachineInstr &InsertBeforeMI);
+ void eraseDeadOriginals();
+
+ // Inserts B at the right point for MI's *next* instruction.
+ void setInsertPointAfter(MachineInstr &MI) {
+ B.setInsertPt(*MI.getParent(), std::next(MI.getIterator()));
+ }
+ // Inserts B right before MI.
+ void setInsertPointBefore(MachineInstr &MI) {
+ B.setInsertPt(*MI.getParent(), MI.getIterator());
+ }
+};
+
+// -----------------------------------------------------------------------------
+// Phase 1: classification.
+// -----------------------------------------------------------------------------
+
+bool PISALegalizePredicatesImpl::isPromotable(Register R) {
+ auto It = Cache.find(R);
+ if (It != Cache.end())
+ return It->second == Yes;
+
+ // Mark as "currently visiting" by inserting No first. Cycles (e.g. through
+ // a G_PHI) thus terminate as not-promotable.
+ Cache[R] = No;
+
+ MachineInstr *Def = MRI.getVRegDef(R);
+ assert(Def && "expected a def for every virtual register in SSA MIR");
+
+ LLT Ty = MRI.getType(R);
+ bool Result = false;
+
+ switch (Def->getOpcode()) {
+ case TargetOpcode::G_ICMP:
+ case TargetOpcode::G_FCMP:
+ // Seed: a cmp whose result is i1 (scalar or vector); isBoolType covers
+ // both since getScalarSizeInBits == 1 for s1 and <N x s1>.
+ Result = isBoolType(Ty);
+ break;
+ case TargetOpcode::G_AND:
+ case TargetOpcode::G_OR:
+ case TargetOpcode::G_XOR:
+ // Promotable if both operands are promotable (or boolean-domain consts).
+ Result = isPromotable(Def->getOperand(1).getReg()) &&
+ isPromotable(Def->getOperand(2).getReg());
+ break;
+ case TargetOpcode::G_SEXT:
+ case TargetOpcode::G_ZEXT:
+ case TargetOpcode::G_ANYEXT:
+ case TargetOpcode::G_TRUNC:
+ case TargetOpcode::COPY:
+ Result = isPromotable(Def->getOperand(1).getReg());
+ break;
+ case TargetOpcode::G_EXTRACT_VECTOR_ELT:
+ Result = isPromotable(Def->getOperand(1).getReg());
+ break;
+ case TargetOpcode::G_CONSTANT: {
+ int64_t Dummy;
+ Result = isBoolConst(*Def, Dummy);
+ break;
+ }
+ default:
+ break;
+ }
+
+ Cache[R] = Result ? Yes : No;
+ return Result;
+}
+
+// -----------------------------------------------------------------------------
+// Phase 2: validate uses; demote any register whose uses we can't handle.
+// -----------------------------------------------------------------------------
+
+bool PISALegalizePredicatesImpl::isHandledUse(MachineInstr &UseInstr,
+ Register R) {
+ // If UseInstr is itself promotable (its def is in the chain), the operand
+ // will be rewritten in lock-step with the def.
+ if (UseInstr.getNumDefs() >= 1 &&
+ Cache.lookup(UseInstr.getOperand(0).getReg()) == Yes)
+ return true;
+
+ // Otherwise, this is a sink. We need to be able to restore the original
+ // value at the boundary. For vector promotable defs feeding a
+ // non-promotable user, we don't try to vector-restore in v1. Bail.
+ if (MRI.getType(R).isVector())
+ return false;
+
+ // Scalar restoration: G_ICMP ne s32, 0 -> s1, or G_TRUNC s32 -> sN.
+ // Both are always materializable, so any scalar use is "handled" in
+ // principle, but we still skip a few opcodes that need special care:
+ switch (UseInstr.getOpcode()) {
+ case TargetOpcode::G_PHI:
+ // PHI of i1 across blocks requires inserting restoration in the
+ // predecessor blocks. v1 conservatively skips.
+ return false;
+ default:
+ return true;
+ }
+}
+
+void PISALegalizePredicatesImpl::invalidateUnhandled() {
+ bool Changed = true;
+ while (Changed) {
+ Changed = false;
+ for (auto &E : Cache) {
+ if (E.second != Yes)
+ continue;
+ Register R = E.first;
+
+ // (a) All uses of R must be handled (in-set or known sink).
+ bool OK = true;
+ for (MachineOperand &U : MRI.use_nodbg_operands(R)) {
+ MachineInstr *UseInstr = U.getParent();
+ if (!isHandledUse(*UseInstr, R)) {
+ OK = false;
+ break;
+ }
+ }
+ if (!OK) {
+ E.second = No;
+ Changed = true;
+ continue;
+ }
+
+ // (b) All required operands of R's def must still be promotable.
+ // If an operand was just demoted above, cascade the demotion so that
+ // Phase 3 doesn't recursively try to promote a non-promotable reg.
+ MachineInstr *Def = MRI.getVRegDef(R);
+ assert(Def);
+ auto IsOperandPromotable = [&](Register Op) {
+ return Cache.lookup(Op) == Yes;
+ };
+ switch (Def->getOpcode()) {
+ case TargetOpcode::G_AND:
+ case TargetOpcode::G_OR:
+ case TargetOpcode::G_XOR: {
+ if (!IsOperandPromotable(Def->getOperand(1).getReg()) ||
+ !IsOperandPromotable(Def->getOperand(2).getReg())) {
+ E.second = No;
+ Changed = true;
+ }
+ break;
+ }
+ case TargetOpcode::G_SEXT:
+ case TargetOpcode::G_ZEXT:
+ case TargetOpcode::G_ANYEXT:
+ case TargetOpcode::G_TRUNC:
+ case TargetOpcode::G_EXTRACT_VECTOR_ELT:
+ case TargetOpcode::COPY: {
+ if (!IsOperandPromotable(Def->getOperand(1).getReg())) {
+ E.second = No;
+ Changed = true;
+ }
+ break;
+ }
+ // Seeds (G_ICMP, G_FCMP) and G_CONSTANT don't depend on the
+ // promotability of any operand.
+ default:
+ break;
+ }
+ }
+ }
+}
+
+bool PISALegalizePredicatesImpl::tryGetDomain(Register R, Domain &Out) {
+ auto It = DomMemo.find(R);
+ if (It != DomMemo.end()) {
+ switch (It->second) {
+ case 1:
+ Out = Domain::Any;
+ return true;
+ case 2:
+ Out = Domain::Sext;
+ return true;
+ case 3:
+ Out = Domain::Zext;
+ return true;
+ default: // 0 (cycle) or 4 (bail)
+ return false;
+ }
+ }
+ DomMemo[R] = 0; // visiting -- a cycle reaching back here resolves to bail.
+ auto Set = [&](Domain D) {
+ DomMemo[R] = D == Domain::Any ? 1 : D == Domain::Sext ? 2 : 3;
+ Out = D;
+ return true;
+ };
+ auto Bail = [&]() {
+ DomMemo[R] = 4;
+ return false;
+ };
+
+ // A 1-bit value (scalar s1 or <N x s1>) carries no magnitude; the extend
+ // that consumes it decides the domain.
+ if (MRI.getType(R).getScalarSizeInBits() == 1)
+ return Set(Domain::Any);
+
+ MachineInstr *Def = MRI.getVRegDef(R);
+ switch (Def->getOpcode()) {
+ case TargetOpcode::G_ZEXT:
+ return Set(Domain::Zext);
+ case TargetOpcode::G_SEXT:
+ case TargetOpcode::G_ANYEXT:
+ // ANYEXT leaves the high bits undefined, so the sext form ({0, -1}) is an
+ // acceptable refinement.
+ return Set(Domain::Sext);
+ case TargetOpcode::G_CONSTANT: {
+ const APInt &V = Def->getOperand(1).getCImm()->getValue();
+ // 0 matches any domain; all-ones is a true value of -1 (Sext). Other
+ // constants are not classified promotable, so never reach here.
+ return Set(V.isZero() ? Domain::Any : Domain::Sext);
+ }
+ case TargetOpcode::G_TRUNC:
+ case TargetOpcode::COPY:
+ case TargetOpcode::G_EXTRACT_VECTOR_ELT: {
+ Domain D;
+ if (!tryGetDomain(Def->getOperand(1).getReg(), D))
+ return Bail();
+ return Set(D);
+ }
+ case TargetOpcode::G_AND:
+ case TargetOpcode::G_OR:
+ case TargetOpcode::G_XOR: {
+ Domain A, BD;
+ if (!tryGetDomain(Def->getOperand(1).getReg(), A) ||
+ !tryGetDomain(Def->getOperand(2).getReg(), BD))
+ return Bail();
+ if (A == Domain::Any)
+ return Set(BD);
+ if (BD == Domain::Any)
+ return Set(A);
+ if (A == BD)
+ return Set(A);
+ return Bail(); // mixed sext/zext -- ambiguous magnitude.
+ }
+ default:
+ // tryGetDomain is only ever called on registers in the promotable closure,
+ // whose defs are one of the opcodes handled above (i1-typed values, incl.
+ // G_ICMP/G_FCMP results, are handled before the switch).
+ llvm_unreachable("magnitude domain queried for a non-promotable opcode");
+ }
+}
+
+// -----------------------------------------------------------------------------
+// Phase 3: rewrite.
+// -----------------------------------------------------------------------------
+
+Register PISALegalizePredicatesImpl::getOrBuildPromoted(Register R) {
+ auto It = Promoted.find(R);
+ if (It != Promoted.end())
+ return It->second;
+
+ assert(isPromotable(R) && "asked to promote a non-promotable reg");
+
+ MachineInstr *Def = MRI.getVRegDef(R);
+ assert(Def && "expected a def");
+ LLT OrigTy = MRI.getType(R);
+ LLT NewTy = promotedType(OrigTy);
+
+ // Allocate the destination s32 register up front so cyclic chains (none
+ // expected, but safe) terminate cleanly via Promoted lookup.
+ Register Dst = MRI.createGenericVirtualRegister(NewTy);
+ Promoted[R] = Dst;
+ LLVM_DEBUG(dbgs() << " Promoting %" << R.virtRegIndex() << " ("
+ << MRI.getType(R) << " -> " << NewTy << "): " << *Def);
+ ++NumPromoted;
+
+ switch (Def->getOpcode()) {
+ case TargetOpcode::G_ICMP: {
+ // Peephole for the common reduction pattern:
+ // %p = ... (promotable, in s32 represents 0 or -1)
+ // %r = G_ICMP eq/ne %p, <0 or all-ones>
+ // The cmp's promoted s32 form is just Promoted(%p) (or its XOR -1)
+ // depending on the predicate and constant -- skip the
+ // trunc -> icmp -> sext detour the legalizer would otherwise produce.
+ // The eq/ne peephole below materializes scalar values (a scalar -1 / xor
+ // and a scalar i1 compare), so it only applies to a scalar cmp result. A
+ // vector cmp (<N x i1>) falls through to the vector-safe G_SEXT default.
+ CmpInst::Predicate Pred =
+ (CmpInst::Predicate)Def->getOperand(1).getPredicate();
+ if (!MRI.getType(R).isVector() &&
+ (Pred == CmpInst::ICMP_EQ || Pred == CmpInst::ICMP_NE)) {
+ Register LhsReg = Def->getOperand(2).getReg();
+ Register RhsReg = Def->getOperand(3).getReg();
+
+ auto ClassifyOperands = [&](Register A, Register B, Register &PromOp,
+ bool &IsAllOnes) -> bool {
+ if (!isPromotable(A))
+ return false;
+ MachineInstr *BDef = MRI.getVRegDef(B);
+ if (BDef->getOpcode() != TargetOpcode::G_CONSTANT)
+ return false;
+ const APInt &V = BDef->getOperand(1).getCImm()->getValue();
+ if (V.isZero()) {
+ PromOp = A;
+ IsAllOnes = false;
+ return true;
+ }
+ if (V.isAllOnes()) {
+ PromOp = A;
+ IsAllOnes = true;
+ return true;
+ }
+ return false;
+ };
+
+ Register PromOp;
+ bool IsAllOnes = false;
+ if (ClassifyOperands(LhsReg, RhsReg, PromOp, IsAllOnes) ||
+ ClassifyOperands(RhsReg, LhsReg, PromOp, IsAllOnes)) {
+ Register PromS32 = getOrBuildPromoted(PromOp);
+ // eq(p, all_ones) == p ; eq(p, 0) == ~p
+ // ne(p, 0) == p ; ne(p, all_ones) == ~p
+ bool Negate = (Pred == CmpInst::ICMP_EQ) ^ IsAllOnes;
+ if (!Negate) {
+ Promoted[R] = PromS32;
+ return PromS32;
+ }
+ setInsertPointAfter(*Def);
+ auto AllOnesC = B.buildConstant(LLT::integer(32), -1);
+ B.buildXor(Dst, PromS32, AllOnesC.getReg(0));
+ break;
+ }
+
+ // Peephole: both operands are promotable (e.g. icmp eq of two AND
+ // chains). Compare the promoted s32 forms directly. Both are in the
+ // {0, -1} domain, so eq/ne is preserved.
+ if (isPromotable(LhsReg) && isPromotable(RhsReg)) {
+ Register PromLHS = getOrBuildPromoted(LhsReg);
+ Register PromRHS = getOrBuildPromoted(RhsReg);
+ setInsertPointAfter(*Def);
+ Register CmpS32 = MRI.createGenericVirtualRegister(LLT::integer(1));
+ B.buildICmp(Pred, CmpS32, PromLHS, PromRHS);
+ B.buildSExt(Dst, CmpS32);
+ break;
+ }
+ }
+ // Default: no peephole matched; promote via sext.
+ setInsertPointAfter(*Def);
+ B.buildSExt(Dst, R);
+ break;
+ }
+ case TargetOpcode::G_FCMP: {
+ // Materialize one G_SEXT of the cmp's i1 def to s32 (scalar or vector),
+ // right after the cmp. We keep the original cmp alive -- any non-promoted
+ // sink (e.g. G_BRCOND) keeps using its i1 def directly.
+ setInsertPointAfter(*Def);
+ B.buildSExt(Dst, R);
+ break;
+ }
+ case TargetOpcode::G_CONSTANT: {
+ int64_t Val = 0;
+ bool OK = isBoolConst(*Def, Val);
+ (void)OK;
+ assert(OK);
+ setInsertPointAfter(*Def);
+ auto NewC = B.buildConstant(NewTy, Val);
+ Promoted[R] = NewC.getReg(0);
+ return NewC.getReg(0);
+ }
+ case TargetOpcode::G_AND:
+ case TargetOpcode::G_OR:
+ case TargetOpcode::G_XOR: {
+ Register LHS = getOrBuildPromoted(Def->getOperand(1).getReg());
+ Register RHS = getOrBuildPromoted(Def->getOperand(2).getReg());
+ setInsertPointBefore(*Def);
+ B.buildInstr(Def->getOpcode(), {Dst}, {LHS, RHS});
+ break;
+ }
+ case TargetOpcode::G_SEXT:
+ case TargetOpcode::G_ZEXT:
+ case TargetOpcode::G_ANYEXT:
+ case TargetOpcode::G_TRUNC: {
+ // The promoted source is already an s32 (or <Nxs32>) representation of
+ // a predicate-domain value (0 / all-ones). The corresponding promoted
+ // form for *this* instruction is the same s32 representation, so just
+ // alias to the source's promoted reg.
+ Register Src = getOrBuildPromoted(Def->getOperand(1).getReg());
+ Promoted[R] = Src;
+ return Src;
+ }
+ case TargetOpcode::G_EXTRACT_VECTOR_ELT: {
+ // Replace the vector source with its <Nxs32> promoted form and extract
+ // the same index at s32.
+ Register VecS32 = getOrBuildPromoted(Def->getOperand(1).getReg());
+ Register Idx = Def->getOperand(2).getReg();
+ setInsertPointBefore(*Def);
+ B.buildExtractVectorElement(Dst, VecS32, Idx);
+ break;
+ }
+ case TargetOpcode::COPY: {
+ Register Src = getOrBuildPromoted(Def->getOperand(1).getReg());
+ Promoted[R] = Src;
+ return Src;
+ }
+ }
+ // The assert at the top of this function -- isPromotable(R) -- guarantees
+ // the opcode is one of the cases handled above.
+
+ return Dst;
+}
+
+Register PISALegalizePredicatesImpl::restoreS1(Register PromotedReg,
+ MachineInstr &InsertBeforeMI) {
+ // Build the restoration just before the sink so it trivially dominates.
+ // Each sink gets its own restoration -- sharing across sinks would need
+ // dominance analysis and is not worth the complexity in v1.
+ setInsertPointBefore(InsertBeforeMI);
+ auto Zero = B.buildConstant(LLT::integer(32), 0);
+ Register Dst = MRI.createGenericVirtualRegister(LLT::integer(1));
+ B.buildICmp(CmpInst::ICMP_NE, Dst, PromotedReg, Zero.getReg(0));
+ return Dst;
+}
+
+bool PISALegalizePredicatesImpl::run() {
+ // -- Phase 1: classify every reachable register.
+ // Seed with every cmp def, then transitively classify any user that reads
+ // those defs, etc. We rely on the memoized isPromotable() to do the work
+ // recursively when we encounter a use whose own operands haven't been
+ // classified yet.
+ SmallVector<MachineInstr *, 32> Cmps;
+ for (auto &MBB : MF) {
+ for (auto &MI : MBB) {
+ unsigned Opc = MI.getOpcode();
+ if (Opc != TargetOpcode::G_ICMP && Opc != TargetOpcode::G_FCMP)
+ continue;
+ // isPromotable below filters out cmps with non-i1 result; no need to
+ // pre-filter here.
+ Cmps.push_back(&MI);
+ }
+ }
+
+ if (Cmps.empty())
+ return false;
+
+ // BFS forward from each cmp to discover the promotable closure. The
+ // memoized isPromotable() returns by classifying defs; here we also need
+ // to populate the Cache for downstream uses that don't get directly
+ // queried. We do this by walking users.
+ SmallVector<Register, 64> Worklist;
+ for (MachineInstr *MI : Cmps) {
+ Register R = MI->getOperand(0).getReg();
+ if (isPromotable(R))
+ Worklist.push_back(R);
+ }
+ while (!Worklist.empty()) {
+ Register R = Worklist.pop_back_val();
+ for (MachineOperand &U : MRI.use_nodbg_operands(R)) {
+ MachineInstr *UI = U.getParent();
+ // Sinks (G_BRCOND/G_STORE/G_RETURN) have no def -- Phase 2 deals with
+ // them. operand(0).isReg() is guaranteed by MIR semantics whenever
+ // getNumDefs >= 1.
+ if (UI->getNumDefs() < 1)
+ continue;
+ Register UR = UI->getOperand(0).getReg();
+ auto It = Cache.find(UR);
+ if (It != Cache.end())
+ continue; // already classified
+ if (isPromotable(UR))
+ Worklist.push_back(UR);
+ }
+ }
+
+ // -- Phase 2: invalidate any promotable reg whose uses we can't handle.
+ invalidateUnhandled();
+
+ // -- Phase 2b: drop chains whose original integer magnitude is ambiguous
+ // (a bitwise op mixing sext- and zext-rooted operands). They cannot be
+ // restored exactly at an integer sink. Re-run use validation after each
+ // demotion round so the cascade reaches consumers of the dropped regs.
+ {
+ bool Changed = true;
+ while (Changed) {
+ Changed = false;
+ DomMemo.clear();
+ for (auto &E : Cache) {
+ if (E.second != Yes)
+ continue;
+ Domain D;
+ if (!tryGetDomain(E.first, D)) {
+ E.second = No;
+ Changed = true;
+ }
+ }
+ if (Changed)
+ invalidateUnhandled();
+ }
+ }
+
+ // Collect the final promote set and decide whether the rewrite is
+ // profitable. A single G_XOR-with-true (NOT of a cmp) feeding a branch is
+ // already handled by the existing opt_brcond_by_inverting_cond combiner --
+ // promoting it would only block that rule. We require *either*
+ // - at least one G_EXTRACT_VECTOR_ELT (a vectorized-cmp reduction), or
+ // - two or more bitwise ops in the promote set (a real reduction chain
+ // that the legalizer would otherwise widen piecewise).
+ //
+ // We additionally require the chain to genuinely *escape* the boolean
+ // domain at one of its *sinks* (see the HasEscapingSink scan below for the
+ // precise test). A pure boolean reduction whose every sink is an i1 consumer
+ // (G_BRCOND / G_SELECT condition) is deliberately left to the legalizer,
+ // which widens-and-fuses such chains optimally. Promoting them here instead
+ // restores the result with a `sext` + `icmp ne 0` round trip that is a no-op
+ // on a boolean (sext then "!= 0" is the identity). Some downstream consumers
+ // fold that pair away, but others do not -- where it survives, the round trip
+ // defeats `bfn`-with-flag fusion and emits extra scalar ops across many
+ // shaders. Keying the gate on the sinks (rather than on whether some internal
+ // node happens to be integer-typed) keeps the unified strategy where it helps
+ // -- vector reductions, genuine integer escapes, and the round-trip-free
+ // eq/ne operand-redirect fast path -- without regressing pure boolean chains.
+ SmallVector<MachineInstr *, 32> ToRewriteDefs;
+ bool HasVectorExtract = false;
+ unsigned NumBitwise = 0;
+ for (auto &E : Cache) {
+ if (E.second != Yes)
+ continue;
+ MachineInstr *Def = MRI.getVRegDef(E.first);
+ assert(Def);
+ ToRewriteDefs.push_back(Def);
+ switch (Def->getOpcode()) {
+ case TargetOpcode::G_XOR: {
+ // A NOT (xor with a boolean constant) is not a real reduction: promoting
+ // it just materializes an explicit `not` that the comparison-inverting
+ // combiner would otherwise fold into the cmp predicate (e.g. slt -> sge).
+ // Only count xors of two non-constant predicate values toward
+ // profitability. (Once promotion is decided by genuine reduction ops, a
+ // NOT inside the chain is still rewritten and is absorbed by `bfn`.)
+ Register X = Def->getOperand(1).getReg();
+ Register Y = Def->getOperand(2).getReg();
+ if (MRI.getVRegDef(X)->getOpcode() != TargetOpcode::G_CONSTANT &&
+ MRI.getVRegDef(Y)->getOpcode() != TargetOpcode::G_CONSTANT)
+ ++NumBitwise;
+ break;
+ }
+ case TargetOpcode::G_AND:
+ case TargetOpcode::G_OR:
+ ++NumBitwise;
+ break;
+ case TargetOpcode::G_EXTRACT_VECTOR_ELT:
+ HasVectorExtract = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Decide whether the chain genuinely escapes the boolean domain by examining
+ // its *sinks* (uses outside the promote set, plus the kept-alive cmps that
+ // stay live). A sink "escapes" iff promoting it does not introduce the
+ // sext + `icmp ne 0` round trip:
+ // - integer sink: a non-bool consumer reading a promoted value at width
+ // > 1 (restored via trunc/sext/and, i.e. consumed as a number), or
+ // - clean eq/ne sink: a G_ICMP eq/ne both of whose operands are promoted,
+ // which is redirected to the s32 forms with no restore at all.
+ // Any other out-of-set consumer of a bool-typed promoted value is a
+ // *predicate sink* (G_BRCOND, G_SELECT condition, an `icmp ==/!= 0` flag
+ // test, an i1 store, ...) that must be fed an s1 reconstructed via the
+ // sext + `icmp ne/eq 0` round trip.
+ //
+ // Vector extracts are tracked separately above. A chain whose every sink is
+ // a predicate sink has only round-trip restorations and is left unpromoted
+ // (the i1-sink case). A chain that has *both* an escaping sink and a
+ // predicate sink is also left unpromoted on the scalar path: promoting it
+ // would still emit the round-trip restore at the predicate sink, and that
+ // restore defeats the backend's `bfn`-with-flag fusion (a single
+ // `bfn3.(...)::eq ...,0x1` becomes `bfn2 ... + cmp ::eq 0`, i.e. +1 op per
+ // flag) -- the dominant residual regression after the i1-sink gate. The
+ // legalizer widens the integer escape at its boundary and the matcher keeps
+ // the fused flag, so deferring the whole mixed chain matches the
+ // pre-promotion baseline. Only chains whose every sink escapes (pure integer
+ // / clean eq-ne reductions, with no predicate consumer) are promoted on the
+ // scalar path.
+ bool HasEscapingSink = false;
+ bool HasPredicateSink = false;
+ for (auto &E : Cache) {
+ if (E.second != Yes)
+ continue;
+ Register R = E.first;
+ LLT RTy = MRI.getType(R);
+ if (RTy.isVector())
+ continue;
+ for (MachineOperand &U : MRI.use_nodbg_operands(R)) {
+ MachineInstr *UI = U.getParent();
+ // In-set users that will be erased are rewritten in lock-step; not sinks.
+ bool InSetEraseable = UI->getNumDefs() >= 1 &&
+ Cache.lookup(UI->getOperand(0).getReg()) == Yes &&
+ !isKeptAliveOpcode(UI->getOpcode());
+ if (InSetEraseable)
+ continue;
+ // Clean eq/ne sink: redirected to the s32 promoted forms, no round trip.
+ if (UI->getOpcode() == TargetOpcode::G_ICMP) {
+ auto Pred = (CmpInst::Predicate)UI->getOperand(1).getPredicate();
+ auto Qualifies = [&](Register X) { return Cache.lookup(X) == Yes; };
+ if ((Pred == CmpInst::ICMP_EQ || Pred == CmpInst::ICMP_NE) &&
+ Qualifies(UI->getOperand(2).getReg()) &&
+ Qualifies(UI->getOperand(3).getReg())) {
+ HasEscapingSink = true;
+ continue;
+ }
+ }
+ // Integer sink: the value is consumed at width > 1, restored as a number
+ // rather than via the i1 sext + (!=0) round trip. isBoolType keys off
+ // getScalarSizeInBits, which (unlike getSizeInBits) does not assert on a
+ // typeless register-class operand such as an inline-asm sink.
+ if (!isBoolType(RTy)) {
+ HasEscapingSink = true;
+ continue;
+ }
+ // Bool-typed value consumed out of set by anything else: a predicate
+ // sink that needs the fusion-defeating round-trip restore.
+ HasPredicateSink = true;
+ }
+ }
+
+ if (!HasVectorExtract &&
+ (NumBitwise < 2 || !HasEscapingSink || HasPredicateSink))
+ return false;
+
+ LLVM_DEBUG(dbgs() << "PISALegalizePredicates: processing " << MF.getName()
+ << " (" << ToRewriteDefs.size() << " defs to rewrite)\n");
+
+ // -- Phase 3: materialize s32 forms for every promoted def, then rewrite
+ // each use.
+ for (MachineInstr *Def : ToRewriteDefs)
+ (void)getOrBuildPromoted(Def->getOperand(0).getReg());
+
+ // The set of s32 promoted-form registers. Used to recognise an eq/ne operand
+ // that an earlier restore iteration already redirected to its s32 form (such
+ // a register is a *value* in Promoted, not a key, so Promoted.count() alone
+ // would miss it).
+ DenseSet<Register> PromotedVals;
+ for (auto &P : Promoted)
+ PromotedVals.insert(P.second);
+
+ // Walk each promoted register's uses; rewrite out-of-set sinks via a
+ // restored s1 (or a trunc to their iN width). In-set users will be
+ // erased shortly, so their use of the original need not be touched.
+ //
+ // Only "eraseable" originals require this -- cmps/constants/copies remain
+ // alive in MIR and feed their sinks unchanged.
+ for (auto &E : Cache) {
+ if (E.second != Yes)
+ continue;
+ Register OrigReg = E.first;
+ Register NewReg = Promoted.lookup(OrigReg);
+ // The G_ICMP peephole can lazily classify additional constant operands
+ // as promotable when probing operand orientation. Such entries land in
+ // Cache as Yes but never go through getOrBuildPromoted (the peephole
+ // only needed the bool-ness flag). Skip them here.
+ if (!NewReg)
+ continue;
+
+ MachineInstr *OrigDef = MRI.getVRegDef(OrigReg);
+ assert(OrigDef);
+
+ // Skip kinds whose originals stay alive -- they keep feeding their sinks
+ // directly, so we don't need to insert restoration.
+ if (isKeptAliveOpcode(OrigDef->getOpcode()))
+ continue;
+
+ LLT OrigTy = MRI.getType(OrigReg);
+
+ // Vector sinks not supported in v1 (we bailed in isHandledUse).
+ if (OrigTy.isVector())
+ continue;
+
+ // Snapshot uses up front -- the iterator is invalidated by rewrites.
+ SmallVector<MachineOperand *, 8> Uses;
+ for (MachineOperand &U : MRI.use_nodbg_operands(OrigReg))
+ Uses.push_back(&U);
+
+ for (MachineOperand *U : Uses) {
+ MachineInstr *UI = U->getParent();
+ // An in-set user whose own opcode is *also* eraseable will be removed
+ // shortly; its new promoted form already reads the right value, so
+ // we can leave its original operand alone.
+ // An in-set user whose opcode is kept-alive (cmp/const/copy) keeps
+ // its original instruction in MIR and still references this orig.
+ // We must redirect that operand to a restored value so the orig
+ // becomes truly dead and the legalizer doesn't widen it.
+ bool InSetEraseable = UI->getNumDefs() >= 1 &&
+ Cache.lookup(UI->getOperand(0).getReg()) == Yes &&
+ !isKeptAliveOpcode(UI->getOpcode());
+ if (InSetEraseable)
+ continue;
+
+ // Special case: a kept-alive G_ICMP eq/ne both of whose compared operands
+ // are promoted. eq/ne is invariant under the {0,-1} vs {0,1}
+ // representation (both promoted forms are the canonical {0,-1}), so let
+ // the comparison read the s32 promoted forms directly. This avoids
+ // restoring each operand back to i1 -- which would cost a ucmp.ne plus a
+ // select per operand -- and keeps the cmp's i1 result feeding its sink.
+ if (UI->getOpcode() == TargetOpcode::G_ICMP) {
+ auto Pred = (CmpInst::Predicate)UI->getOperand(1).getPredicate();
+ // An operand qualifies if it is a promoted chain (a key of Promoted) or
+ // already its s32 promoted form (redirected in an earlier iteration).
+ auto Qualifies = [&](Register X) {
+ return Promoted.count(X) || PromotedVals.count(X);
+ };
+ if ((Pred == CmpInst::ICMP_EQ || Pred == CmpInst::ICMP_NE) &&
+ Qualifies(UI->getOperand(2).getReg()) &&
+ Qualifies(UI->getOperand(3).getReg())) {
+ U->setReg(NewReg);
+ ++NumRestorations;
+ continue;
+ }
+ }
+
+ Register Restored;
+ if (OrigTy.getSizeInBits() == 1) {
+ Restored = restoreS1(NewReg, *UI);
+ } else {
+ // Restore the *original* integer value at this sink. The promoted form
+ // is always SEXT-domain ({0, -1}); the original value may be
+ // ZEXT-domain ({0, 1}) if its chain was rooted in zero-extends.
+ // Reconstruct exactly from the tracked magnitude domain:
+ // Zext: boolean -> G_ZEXT to width ({0, 1}),
+ // Sext: trunc/sext of the {0, -1} form (sign bits already correct).
+ Domain Dom = Domain::Sext;
+ bool OK = tryGetDomain(OrigReg, Dom);
+ assert(OK && "promoted reg must have an unambiguous magnitude domain");
+ (void)OK;
+ if (Dom == Domain::Zext) {
+ // ZEXT domain ({0, 1}): mask the low bit of the {0, -1} form and
+ // adapt to width. This is cheaper than materializing a predicate and
+ // selecting 1/0 -- on an 8-bit sink the predicate+select path expands
+ // to ucmp + sel + trunc, whereas `and , 1` keeps the value in a GP
+ // register and matches the legalizer's fused boolean-mask lowering.
+ setInsertPointBefore(*UI);
+ auto One = B.buildConstant(LLT::integer(32), 1);
+ Register Masked = MRI.createGenericVirtualRegister(LLT::integer(32));
+ B.buildAnd(Masked, NewReg, One.getReg(0));
+ unsigned W = OrigTy.getSizeInBits();
+ if (W == 32) {
+ Restored = Masked;
+ } else {
+ Restored = MRI.createGenericVirtualRegister(OrigTy);
+ if (W < 32)
+ B.buildTrunc(Restored, Masked);
+ else
+ B.buildZExt(Restored, Masked);
+ }
+ } else if (OrigTy.getSizeInBits() == 32) {
+ Restored = NewReg;
+ } else {
+ setInsertPointBefore(*UI);
+ Restored = MRI.createGenericVirtualRegister(OrigTy);
+ if (OrigTy.getSizeInBits() < 32)
+ B.buildTrunc(Restored, NewReg);
+ else
+ B.buildSExt(Restored, NewReg);
+ }
+ }
+ U->setReg(Restored);
+ LLVM_DEBUG(dbgs() << " Restored use in: " << *UI);
+ ++NumRestorations;
+ }
+ }
+
+ // Erase dead originals (skip the cmps -- they're still alive for s1 sinks
+ // and may also be DCEd by later passes if they end up unused).
+ eraseDeadOriginals();
+
+ return true;
+}
+
+void PISALegalizePredicatesImpl::eraseDeadOriginals() {
+ // Collect erasable candidates into a worklist. Iterate until no more
+ // instructions become dead (removing a def may free its operands).
+ SmallVector<Register, 16> Worklist;
+ for (auto &E : Cache) {
+ if (E.second != Yes)
+ continue;
+ Register R = E.first;
+ MachineInstr *Def = MRI.getVRegDef(R);
+ assert(Def && "every classified vreg has a def in SSA MIR");
+ unsigned Opc = Def->getOpcode();
+ if (Opc == TargetOpcode::G_ICMP || Opc == TargetOpcode::G_FCMP ||
+ Opc == TargetOpcode::G_CONSTANT)
+ continue;
+ Worklist.push_back(R);
+ }
+
+ bool Changed = true;
+ while (Changed) {
+ Changed = false;
+ for (unsigned I = 0; I < Worklist.size(); ++I) {
+ Register R = Worklist[I];
+ MachineInstr *Def = MRI.getVRegDef(R);
+ if (!Def)
+ continue;
+ if (!MRI.use_nodbg_empty(R))
+ continue;
+ LLVM_DEBUG(dbgs() << " Erasing dead original: " << *Def);
+ ++NumErased;
+ Def->eraseFromParent();
+ Changed = true;
+ }
+ }
+}
+
+} // end anonymous namespace
+
+// -----------------------------------------------------------------------------
+// Pass plumbing.
+// -----------------------------------------------------------------------------
+
+void PISALegalizePredicates::getAnalysisUsage(AnalysisUsage &AU) const {
+ AU.setPreservesCFG();
+ MachineFunctionPass::getAnalysisUsage(AU);
+}
+
+bool PISALegalizePredicates::runOnMachineFunction(MachineFunction &MF) {
+ if (skipFunction(MF.getFunction()))
+ return false;
+ // No FailedISel guard: this pass runs in addPreLegalizeMachineIR(), before
+ // instruction selection, so that property is never set at this stage.
+ if (MF.getTarget().getOptLevel() == CodeGenOptLevel::None)
+ return false;
+ PISALegalizePredicatesImpl Impl(MF);
+ return Impl.run();
+}
+
+char PISALegalizePredicates::ID = 0;
+INITIALIZE_PASS(PISALegalizePredicates, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+MachineFunctionPass *llvm::createPISALegalizePredicatesPass() {
+ return new PISALegalizePredicates();
+}
diff --git a/llvm/lib/Target/PISA/PISALegalizeSubregAccess.cpp b/llvm/lib/Target/PISA/PISALegalizeSubregAccess.cpp
new file mode 100644
index 0000000000000..7202a2c40268a
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISALegalizeSubregAccess.cpp
@@ -0,0 +1,435 @@
+//=== PISALegalizeSubregAccess.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Enforce restrictions on instructions that may not be able to handle
+// a given subreg access (as generated by e.g., register coalescing).
+//
+//===----------------------------------------------------------------------===//
+
+#include "PISA.h"
+#include "PISAMCInstLower.h"
+#include "PISASubtarget.h"
+#include "llvm/Target/TargetMachine.h"
+
+#define DEBUG_TYPE "pisa-legalize-subreg-access"
+#define DEBUG_NAME "PISA legalize subreg access"
+
+using namespace llvm;
+
+namespace {
+
+class PISALegalizeSubregAccess : public MachineFunctionPass {
+public:
+ static char ID;
+
+ PISALegalizeSubregAccess();
+
+ StringRef getPassName() const override { return DEBUG_NAME; }
+
+ bool runOnMachineFunction(MachineFunction &MF) override;
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override;
+};
+} // end anonymous namespace
+
+char PISALegalizeSubregAccess::ID = 0;
+INITIALIZE_PASS(PISALegalizeSubregAccess, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+void PISALegalizeSubregAccess::getAnalysisUsage(AnalysisUsage &AU) const {
+ AU.setPreservesCFG();
+ MachineFunctionPass::getAnalysisUsage(AU);
+}
+
+PISALegalizeSubregAccess::PISALegalizeSubregAccess() : MachineFunctionPass(ID) {
+ initializePISALegalizeSubregAccessPass(*PassRegistry::getPassRegistry());
+}
+
+bool PISALegalizeSubregAccess::runOnMachineFunction(MachineFunction &MF) {
+ auto &ST = MF.getSubtarget<PISASubtarget>();
+ auto *TII = ST.getInstrInfo();
+ auto *TRI = ST.getRegisterInfo();
+ auto &MRI = MF.getRegInfo();
+ bool Changed = false;
+ SmallVector<MachineInstr *, 8> FnArgs;
+ SmallVector<MachineInstr *> CopyV3Insts;
+ SmallVector<MachineInstr *> LargeCopyInsts;
+ SmallVector<MachineInstr *> NonCopyXYInsts;
+ for (auto &MBB : MF) {
+ for (auto &MI : MBB) {
+ if (TII->isFunctionParamInstr(MI))
+ FnArgs.push_back(&MI);
+ else if (MI.isCopy()) {
+ auto &Dst = MI.getOperand(0);
+ auto &Src = MI.getOperand(1);
+ if (Dst.getReg().isVirtual() && Src.getReg().isVirtual()) {
+ auto *DstRC = TRI->getSubRegisterClass(MRI.getRegClass(Dst.getReg()),
+ Dst.getSubReg());
+ auto *SrcRC = TRI->getSubRegisterClass(MRI.getRegClass(Src.getReg()),
+ Src.getSubReg());
+ // If either subreg isn't directly named on its RC, this COPY isn't
+ // a candidate for V3 or large-copy legalization; skip classification.
+ if (DstRC && SrcRC) {
+ auto DstNumElts = TRI->getNumEltsFromRegClass(DstRC);
+ auto DstEltSize = TRI->getBitSizeFromRegClass(DstRC);
+ auto SrcNumElts = TRI->getNumEltsFromRegClass(SrcRC);
+ if (((DstNumElts == 3) && (Dst.getSubReg() == 0)) &&
+ ((SrcNumElts == 3) && (Src.getSubReg() == 0))) {
+ CopyV3Insts.push_back(&MI);
+ } else if ((DstNumElts * DstEltSize) > 128) {
+ LargeCopyInsts.push_back(&MI);
+ }
+ }
+ }
+ } else {
+ bool NeedsLegalization = false;
+ for (unsigned I = 0; I < MI.getNumOperands(); I++) {
+ auto Opnd = MI.getOperand(I);
+ if (!Opnd.isReg() || !Opnd.getSubReg())
+ continue;
+ auto Swizzle = TRI->getSwizzle(Opnd.getSubReg());
+ if (TRI->isSelectorSwizzle(Swizzle))
+ continue;
+
+ // Tied operands collapse to a single MC operand during lowering and
+ // lose their swizzle. A composite-swizzle tied operand therefore
+ // cannot be emitted directly, so keep legalizing it into an explicit
+ // copy.
+ if (Opnd.isTied()) {
+ NeedsLegalization = true;
+ break;
+ }
+
+ // Composite swizzle (.xy / .zw) on a use or def operand.
+ // It is legal to leave in place when the subregister's shape
+ // matches the instruction's expected operand register class:
+ // the operand is then a naturally nameable, contiguous
+ // sub-slice (e.g. %v3h.xy) that register coalescing produced
+ // and that the AsmPrinter emits directly. This holds for both
+ // reads and partial-register writes; the printer applies the
+ // operand's swizzle uniformly, so a partial-register write is
+ // representable. A shape mismatch (or a subreg class that is
+ // not directly nameable, i.e. getSubRegisterClass == nullptr)
+ // still needs legalization.
+ bool IsLegal = false;
+ if (Opnd.getReg().isVirtual()) {
+ auto *SuperRC = MRI.getRegClass(Opnd.getReg());
+ auto *SubRC = TRI->getSubRegisterClass(SuperRC, Opnd.getSubReg());
+ auto *ExpectedRC = TII->getRegClass(TII->get(MI.getOpcode()), I);
+ if (SubRC && ExpectedRC) {
+ IsLegal = (TRI->getNumEltsFromRegClass(SubRC) ==
+ TRI->getNumEltsFromRegClass(ExpectedRC)) &&
+ (TRI->getBitSizeFromRegClass(SubRC) ==
+ TRI->getBitSizeFromRegClass(ExpectedRC));
+ }
+ }
+ if (!IsLegal) {
+ NeedsLegalization = true;
+ break;
+ }
+ }
+ if (NeedsLegalization)
+ NonCopyXYInsts.push_back(&MI);
+ }
+ }
+ }
+
+ for (auto *MI : reverse(FnArgs)) {
+ auto &Dst = MI->getOperand(0);
+ unsigned Subreg = Dst.getSubReg();
+ if (Subreg == 0 && !TRI->isSpecialReg(Dst.getReg()))
+ continue;
+
+ Changed = true;
+
+ auto *RC = TII->getRegClass(TII->get(MI->getOpcode()), 0);
+ Register NewDstReg = MRI.createVirtualRegister(RC);
+
+ MachineInstr *CopyMI =
+ BuildMI(MF, MI->getDebugLoc(), TII->get(TargetOpcode::COPY))
+ .add(Dst)
+ .addReg(NewDstReg);
+
+ auto *InsertPt = FnArgs[FnArgs.size() - 1];
+
+ Dst.setReg(NewDstReg);
+ Dst.setSubReg(0);
+ Dst.setIsUndef(false);
+ MI->getParent()->insertAfter(InsertPt, CopyMI);
+ }
+
+ for (auto *MI : CopyV3Insts) {
+ // .v3.16b A = mov .v3.16b B
+ // => .v3.16b A.x = mov .v3.16b B.x
+ // => .v3.16b A.y = mov .v3.16b B.y
+ // => .v3.16b A.z = mov .v3.16b B.z
+ auto &Dst = MI->getOperand(0);
+ auto &Src = MI->getOperand(1);
+ auto *DstRC = TRI->getSubRegisterClass(MRI.getRegClass(Dst.getReg()),
+ Dst.getSubReg());
+ auto NumElts = TRI->getNumEltsFromRegClass(DstRC);
+ auto EltSize = TRI->getBitSizeFromRegClass(DstRC);
+
+ DebugLoc DL = MI->getDebugLoc();
+ auto DstReg = Dst.getReg();
+ auto SrcReg = Src.getReg();
+ for (unsigned I = 0; I < NumElts; I++) {
+ auto NewMI =
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY));
+ auto SubRegIdx = TRI->getSubRegIdx(EltSize, I);
+ auto Undef = (I == 0) ? RegState::Undef : RegState::NoFlags;
+ NewMI.addDef(DstReg, Undef, SubRegIdx);
+ NewMI.addReg(SrcReg, {}, SubRegIdx);
+ }
+ Changed = true;
+ MI->eraseFromParent();
+ }
+
+ for (auto *MI : LargeCopyInsts) {
+ // (#1).v?.64b A = mov .v?.64b B
+ // => .v?.64b A.x = mov .v?.64b B.x
+ // => .v?.64b A.y = mov .v?.64b B.y
+ // => ... possibly .z, .w
+ // (#2) .v2.64b A = mov .v4.32b B
+ // => .v2.32b Lo.x = mov .v4.32b B.x
+ // => .v2.32b Lo.y = mov .v4.32b B.y
+ // => .v2.32b Hi.x = mov .v4.32b B.z
+ // => .v2.32b Hi.y = mov .v4.32b B.w
+ // => .64b Lo64 = mov .v2.32b Lo
+ // => .64b Hi64 = mov .v2.32b Hi
+ // => .v2.64b A.x = Lo64
+ // => .v2.64b A.y = Hi64
+ // (#3) .v4.32b A = mov .v2.64b B
+ // => .64b Lo64 = mov .v2.64b B.x
+ // => .64b Hi64 = mov .v2.64b B.y
+ // => .v2.32b Lo = mov .64b Lo64
+ // => .v2.32b Hi = mov .64b Hi64
+ // => .v4.32b A.x = mov .v2.32b Lo.x
+ // => .v4.32b A.y = mov .v2.32b Lo.y
+ // => .v4.32b A.z = mov .v2.32b Lo.z
+ // => .v4.32b A.w = mov .v2.32b Lo.w
+ auto &Dst = MI->getOperand(0);
+ auto &Src = MI->getOperand(1);
+ auto *DstRC = TRI->getSubRegisterClass(MRI.getRegClass(Dst.getReg()),
+ Dst.getSubReg());
+ auto *SrcRC = TRI->getSubRegisterClass(MRI.getRegClass(Src.getReg()),
+ Src.getSubReg());
+ auto DstNumElts = TRI->getNumEltsFromRegClass(DstRC);
+ auto DstEltSize = TRI->getBitSizeFromRegClass(DstRC);
+ auto SrcNumElts = TRI->getNumEltsFromRegClass(SrcRC);
+ auto SrcEltSize = TRI->getBitSizeFromRegClass(SrcRC);
+
+ DebugLoc DL = MI->getDebugLoc();
+ auto DstReg = Dst.getReg();
+ auto SrcReg = Src.getReg();
+ if (SrcEltSize == DstEltSize) { // (#1)
+ if (DstNumElts > 4) {
+ // large vector support
+ assert(DstEltSize == 32);
+ if (MRI.def_empty(SrcReg)) {
+ BuildMI(*MI->getParent(), MI, DL,
+ TII->get(TargetOpcode::IMPLICIT_DEF))
+ .addDef(DstReg);
+ } else {
+ unsigned Opcode = 0;
+ switch (DstNumElts) {
+ case 5:
+ Opcode = PISA::extract_0_v5i32_v5i32_r;
+ break;
+ case 6:
+ Opcode = PISA::extract_0_v6i32_v6i32_r;
+ break;
+ case 7:
+ Opcode = PISA::extract_0_v7i32_v7i32_r;
+ break;
+ case 8:
+ Opcode = PISA::extract_0_v8i32_v8i32_r;
+ break;
+ case 16:
+ Opcode = PISA::extract_0_v16i32_v16i32_r;
+ break;
+ case 32:
+ Opcode = PISA::extract_0_v32i32_v32i32_r;
+ break;
+ case 64:
+ Opcode = PISA::extract_0_v64i32_v64i32_r;
+ break;
+ default:
+ assert(0 && "implement");
+ break;
+ }
+ BuildMI(*MI->getParent(), MI, DL, TII->get(Opcode))
+ .addDef(DstReg)
+ .addReg(SrcReg);
+ }
+ } else {
+ // copy using swizzle
+ for (unsigned I = 0; I < DstNumElts; I++) {
+ auto NewMI =
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY));
+ auto SubRegIdx = TRI->getSubRegIdx(DstEltSize, I);
+ auto Undef = (I == 0) ? RegState::Undef : RegState::NoFlags;
+ NewMI.addDef(DstReg, Undef, SubRegIdx);
+ NewMI.addReg(SrcReg, {}, SubRegIdx);
+ }
+ }
+ } else {
+ auto *SmallVecRC =
+ (DstEltSize > SrcEltSize)
+ ? TRI->getVectorRegClass(SrcNumElts / 2, SrcEltSize)
+ : TRI->getVectorRegClass(DstNumElts / 2, DstEltSize); // .v2.32b
+ auto *LargeEltRC =
+ (DstEltSize > SrcEltSize)
+ ? TRI->getSubRegisterClass(MRI.getRegClass(DstReg),
+ TRI->getSubRegIdx(DstEltSize, 0))
+ : TRI->getSubRegisterClass(
+ MRI.getRegClass(SrcReg),
+ TRI->getSubRegIdx(SrcEltSize, 0)); // .64b
+ Register LoReg = MRI.createVirtualRegister(SmallVecRC);
+ Register HiReg = MRI.createVirtualRegister(SmallVecRC);
+ Register Lo64Reg = MRI.createVirtualRegister(LargeEltRC);
+ Register Hi64Reg = MRI.createVirtualRegister(LargeEltRC);
+ if (DstEltSize > SrcEltSize) { // (#2)
+ auto IdX = TRI->getSubRegIdx(SrcEltSize, 0);
+ auto IdY = TRI->getSubRegIdx(SrcEltSize, 1);
+ auto IdZ = TRI->getSubRegIdx(SrcEltSize, 2);
+ auto IdW = TRI->getSubRegIdx(SrcEltSize, 3);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(LoReg, RegState::Undef, IdX)
+ .addReg(SrcReg, {}, IdX);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(LoReg, {}, IdY)
+ .addReg(SrcReg, {}, IdY);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(HiReg, RegState::Undef, IdX)
+ .addReg(SrcReg, {}, IdZ);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(HiReg, {}, IdY)
+ .addReg(SrcReg, {}, IdW);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(Hi64Reg)
+ .addReg(HiReg);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(Lo64Reg)
+ .addReg(LoReg);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(DstReg, RegState::Undef, TRI->getSubRegIdx(DstEltSize, 0))
+ .addReg(Lo64Reg);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(DstReg, {}, TRI->getSubRegIdx(DstEltSize, 1))
+ .addReg(Hi64Reg);
+ } else { // (#3)
+ auto IdX = TRI->getSubRegIdx(DstEltSize, 0);
+ auto IdY = TRI->getSubRegIdx(DstEltSize, 1);
+ auto IdZ = TRI->getSubRegIdx(DstEltSize, 2);
+ auto IdW = TRI->getSubRegIdx(DstEltSize, 3);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(Lo64Reg)
+ .addReg(SrcReg, {}, TRI->getSubRegIdx(SrcEltSize, 0));
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(Hi64Reg)
+ .addReg(SrcReg, {}, TRI->getSubRegIdx(SrcEltSize, 1));
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(LoReg)
+ .addReg(Lo64Reg);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(HiReg)
+ .addReg(Hi64Reg);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(DstReg, RegState::Undef, IdX)
+ .addReg(LoReg, {}, IdX);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(DstReg, {}, IdY)
+ .addReg(LoReg, {}, IdY);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(DstReg, {}, IdZ)
+ .addReg(HiReg, {}, IdX);
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(DstReg, {}, IdW)
+ .addReg(HiReg, {}, IdY);
+ }
+ }
+ Changed = true;
+ MI->eraseFromParent();
+ }
+
+ for (auto *MI : NonCopyXYInsts) {
+ // A = add B.xy, C.zw
+ // => mov B', B.xy
+ // => mov C', C.zw
+ // => A = add B', C'
+ DebugLoc DL = MI->getDebugLoc();
+ SmallVector<MachineOperand, 4> NewOperands;
+ for (unsigned I = 0; I < MI->getNumOperands(); I++) {
+ auto Opnd = MI->getOperand(I);
+ if (Opnd.isReg() && Opnd.getSubReg() &&
+ (!TRI->isSelectorSwizzle(TRI->getSwizzle(Opnd.getSubReg())))) {
+ auto SubReg = Opnd.getSubReg();
+ auto Reg = Opnd.getReg();
+ // Prefer the register class the instruction declares for this operand
+ // (always allocatable). Fall back to the operand's sub-register class;
+ // if that is a non-allocatable structural sub-class (e.g. the .zw half
+ // Reg16bx2H), substitute the equivalent allocatable vector class so the
+ // temporary register can be created.
+ auto *RC = TII->getRegClass(TII->get(MI->getOpcode()), I);
+ if (!RC) {
+ RC = TRI->getSubRegisterClass(MRI.getRegClass(Reg), SubReg);
+ if (RC && !RC->isAllocatable())
+ RC = TRI->getVectorRegClass(TRI->getNumEltsFromRegClass(RC),
+ TRI->getBitSizeFromRegClass(RC));
+ }
+ auto NewReg = MRI.createVirtualRegister(RC);
+ if (!Opnd.isDef() || Opnd.isTied())
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(NewReg)
+ .addReg(Reg, {}, SubReg);
+ NewOperands.push_back(MachineOperand::CreateReg(NewReg, Opnd.isDef()));
+ } else {
+ NewOperands.push_back(Opnd);
+ }
+ }
+ auto NewMI = BuildMI(*MI->getParent(), MI, DL, TII->get(MI->getOpcode()));
+ for (unsigned I = 0; I < MI->getNumOperands(); I++) {
+ auto OldOpnd = MI->getOperand(I);
+ auto IsTied = OldOpnd.isReg() && OldOpnd.isTied();
+ auto NewOpnd = NewOperands[I];
+ if (IsTied && (I >= MI->getNumDefs())) {
+ auto TiedIdx = MI->findTiedOperandIdx(I);
+ // for originally tied register, specifying same
+ // register number will generate 'tied-def' entry
+ NewMI.addReg(NewOperands[TiedIdx].getReg(), {},
+ NewOperands[TiedIdx].getSubReg());
+ } else {
+ NewMI.add(NewOpnd);
+ }
+ }
+ for (unsigned I = 0; I < MI->getNumOperands(); I++) {
+ auto Opnd = MI->getOperand(I);
+ if (Opnd.isReg() && Opnd.getSubReg() &&
+ (!TRI->isSelectorSwizzle(TRI->getSwizzle(Opnd.getSubReg())))) {
+ auto SubReg = Opnd.getSubReg();
+ auto Reg = Opnd.getReg();
+ auto NewReg = NewOperands[I].getReg();
+ if (Opnd.isDef()) {
+ auto Undef = (I == 0) ? RegState::Undef : RegState::NoFlags;
+ BuildMI(*MI->getParent(), MI, DL, TII->get(TargetOpcode::COPY))
+ .addDef(Reg, Undef, SubReg)
+ .addReg(NewReg);
+ }
+ }
+ }
+ Changed = true;
+ MI->eraseFromParent();
+ }
+ return Changed;
+}
+
+namespace llvm {
+FunctionPass *createPISALegalizeSubregAccess() {
+ return new PISALegalizeSubregAccess();
+}
+} // end namespace llvm
diff --git a/llvm/lib/Target/PISA/PISAMarkConvergentNoMerge.cpp b/llvm/lib/Target/PISA/PISAMarkConvergentNoMerge.cpp
new file mode 100644
index 0000000000000..16041c31e978d
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAMarkConvergentNoMerge.cpp
@@ -0,0 +1,64 @@
+//===-- PISAMarkConvergentNoMerge.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Mark convergent instructions with the NoMerge flag to prevent the branch
+// folder from tail-merging blocks that contain them. Convergent instructions
+// must execute with specific thread convergence guarantees, and tail merging
+// can violate those guarantees by changing the control flow paths that reach
+// the instruction.
+//
+//===----------------------------------------------------------------------===//
+
+#include "PISA.h"
+#include "llvm/CodeGen/MachineFunctionPass.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Pass.h"
+
+#define DEBUG_TYPE "pisa-mark-convergent-no-merge"
+#define DEBUG_NAME "PISA mark convergent instructions as NoMerge"
+
+using namespace llvm;
+
+namespace {
+
+class PISAMarkConvergentNoMerge : public MachineFunctionPass {
+public:
+ static char ID;
+
+ PISAMarkConvergentNoMerge() : MachineFunctionPass(ID) {
+ initializePISAMarkConvergentNoMergePass(*PassRegistry::getPassRegistry());
+ }
+
+ StringRef getPassName() const override { return DEBUG_NAME; }
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.setPreservesAll();
+ MachineFunctionPass::getAnalysisUsage(AU);
+ }
+
+ bool runOnMachineFunction(MachineFunction &MF) override {
+ bool Changed = false;
+ for (MachineBasicBlock &MBB : MF) {
+ for (MachineInstr &MI : MBB) {
+ if (MI.isConvergent() && !MI.getFlag(MachineInstr::NoMerge)) {
+ MI.setFlag(MachineInstr::NoMerge);
+ Changed = true;
+ }
+ }
+ }
+ return Changed;
+ }
+};
+
+} // end anonymous namespace
+
+char PISAMarkConvergentNoMerge::ID = 0;
+INITIALIZE_PASS(PISAMarkConvergentNoMerge, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+FunctionPass *llvm::createPISAMarkConvergentNoMerge() {
+ return new PISAMarkConvergentNoMerge();
+}
diff --git a/llvm/lib/Target/PISA/PISAOptimizeRedundantCopies.cpp b/llvm/lib/Target/PISA/PISAOptimizeRedundantCopies.cpp
new file mode 100644
index 0000000000000..b7e536dd70fdd
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAOptimizeRedundantCopies.cpp
@@ -0,0 +1,171 @@
+//=== PISAOptimizeRedundantCopies.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Remove redundant COPY operations generated as result of previous passes.
+//
+// B = COPY A
+// C = COPY B
+// => B = COPY A
+// => C = COPY A
+//
+// Subsequent DCE will eliminate 'B = COPY A' if B is no longer used.
+//===----------------------------------------------------------------------===//
+
+#include "PISA.h"
+#include "PISAMCInstLower.h"
+#include "PISASubtarget.h"
+#include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h"
+#include "llvm/Target/TargetMachine.h"
+
+#define DEBUG_TYPE "pisa-optimize-redundant-copies"
+#define DEBUG_NAME "PISA optimize redundant copies"
+
+using namespace llvm;
+
+namespace {
+
+class PISAOptimizeRedundantCopies : public MachineFunctionPass {
+public:
+ static char ID;
+
+ PISAOptimizeRedundantCopies();
+
+ StringRef getPassName() const override { return DEBUG_NAME; }
+
+ bool runOnMachineFunction(MachineFunction &MF) override;
+ void getAnalysisUsage(AnalysisUsage &AU) const override;
+};
+} // end anonymous namespace
+
+char PISAOptimizeRedundantCopies::ID = 0;
+INITIALIZE_PASS(PISAOptimizeRedundantCopies, DEBUG_TYPE, DEBUG_NAME, false,
+ false)
+
+// Erase RegMap entries for any register or subregister that is defined (written
+// to) by MI. This ensures that if a register or any of its subregisters is
+// redefined between COPYs, we do not propagate stale mappings. Example:
+// A = COPY B
+// B = INST ... // B is redefined here
+// C = COPY A
+// => cannot replace C = COPY B
+//
+// Also handles cases where a subregister of B is modified:
+// A = COPY B
+// B.sub_0 = INST ... // subregister of B is redefined here
+// C = COPY A
+// => cannot replace C = COPY B
+static void eraseRegMapEntriesForDef(
+ const MachineInstr &MI,
+ SmallDenseMap<std::pair<Register, unsigned>, std::pair<Register, unsigned>>
+ &RegMap) {
+ for (const MachineOperand &Op : MI.operands()) {
+ if (Op.isReg() && Op.isDef()) {
+ Register WrittenReg = Op.getReg();
+ RegMap.remove_if([WrittenReg](const auto &Entry) {
+ return Entry.second.first == WrittenReg;
+ });
+ }
+ }
+}
+
+// Erase RegMap entries for registers or subregisters that overlap with the
+// destination of MI. This handles cases where an instruction overwrites part of
+// a register, invalidating previous mappings. Example:
+// A = COPY B
+// A.sub_0 = INST C // overwrites part of A
+// D = COPY A
+// => cannot replace D = COPY B
+static void eraseRegMapEntriesForOverlap(
+ const MachineInstr &MI,
+ SmallDenseMap<std::pair<Register, unsigned>, std::pair<Register, unsigned>>
+ &RegMap) {
+ const static SmallDenseMap<unsigned, unsigned> OverLap = {
+ {PISA::sub8_0, PISA::sub8_xy}, {PISA::sub8_1, PISA::sub8_xy},
+ {PISA::sub8_2, PISA::sub8_zw}, {PISA::sub8_3, PISA::sub8_zw},
+ {PISA::sub16_0, PISA::sub16_xy}, {PISA::sub16_1, PISA::sub16_xy},
+ {PISA::sub16_2, PISA::sub16_zw}, {PISA::sub16_3, PISA::sub16_zw},
+ {PISA::sub32_0, PISA::sub32_xy}, {PISA::sub32_1, PISA::sub32_xy},
+ {PISA::sub32_2, PISA::sub32_zw}, {PISA::sub32_3, PISA::sub32_zw},
+ {PISA::sub64_0, PISA::sub64_xy}, {PISA::sub64_1, PISA::sub64_xy},
+ {PISA::sub64_2, PISA::sub64_zw}, {PISA::sub64_3, PISA::sub64_zw},
+ };
+
+ if (MI.getNumOperands() == 0 || !MI.getOperand(0).isReg())
+ return;
+
+ auto Dst = MI.getOperand(0);
+ auto DstReg = Dst.getReg();
+ auto DstSubReg = Dst.getSubReg();
+
+ auto EraseOverlapReg = [&RegMap](Register DstReg, unsigned DstSubReg) {
+ auto Key = std::make_pair(DstReg, DstSubReg);
+ RegMap.erase(Key);
+ };
+
+ if (DstSubReg) {
+ EraseOverlapReg(DstReg, DstSubReg);
+ if (auto It = OverLap.find(DstSubReg); It != OverLap.end())
+ EraseOverlapReg(DstReg, It->second); // overlap subreg
+ }
+ EraseOverlapReg(DstReg, 0); // overlap full reg
+}
+
+static void processInterveningInsts(
+ const MachineInstr &MI,
+ SmallDenseMap<std::pair<Register, unsigned>, std::pair<Register, unsigned>>
+ &RegMap) {
+ eraseRegMapEntriesForDef(MI, RegMap);
+ eraseRegMapEntriesForOverlap(MI, RegMap);
+}
+
+void PISAOptimizeRedundantCopies::getAnalysisUsage(AnalysisUsage &AU) const {
+ AU.setPreservesCFG();
+ MachineFunctionPass::getAnalysisUsage(AU);
+}
+
+PISAOptimizeRedundantCopies::PISAOptimizeRedundantCopies()
+ : MachineFunctionPass(ID) {
+ initializePISAOptimizeRedundantCopiesPass(*PassRegistry::getPassRegistry());
+}
+
+bool PISAOptimizeRedundantCopies::runOnMachineFunction(MachineFunction &MF) {
+ bool Changed = false;
+ for (auto &MBB : MF) {
+ SmallDenseMap<std::pair<Register, unsigned>, std::pair<Register, unsigned>>
+ RegMap;
+ for (auto &MI : make_early_inc_range(MBB)) {
+ processInterveningInsts(MI, RegMap);
+ if (!MI.isCopy())
+ continue;
+
+ auto Dst = MI.getOperand(0);
+ auto Opnd = MI.getOperand(1);
+ auto Key = std::make_pair(Opnd.getReg(), Opnd.getSubReg());
+ if (auto It = RegMap.find(Key); It != RegMap.end()) {
+ auto [SrcReg, SrcSubReg] = It->second;
+ MachineIRBuilder B(MI);
+ B.buildInstr(TargetOpcode::COPY)
+ .addDef(Dst.getReg(), getRegState(Dst), Dst.getSubReg())
+ .addUse(SrcReg, {}, SrcSubReg);
+ MI.eraseFromParent();
+ Changed = true;
+ continue;
+ }
+
+ auto DKey = std::make_pair(Dst.getReg(), Dst.getSubReg());
+ RegMap[DKey] = std::make_pair(Opnd.getReg(), Opnd.getSubReg());
+ }
+ }
+ return Changed;
+}
+
+namespace llvm {
+FunctionPass *createPISAOptimizeRedundantCopies() {
+ return new PISAOptimizeRedundantCopies();
+}
+} // end namespace llvm
diff --git a/llvm/lib/Target/PISA/PISAOptimizeSubregAccess.cpp b/llvm/lib/Target/PISA/PISAOptimizeSubregAccess.cpp
new file mode 100644
index 0000000000000..7b86c34c2c782
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAOptimizeSubregAccess.cpp
@@ -0,0 +1,195 @@
+//=== PISAOptimizeSubregAccess.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Given two consecutive COPY operations utilizing same destination and source
+// registers (and differing in subregister index), attempt to combine them into
+// a single COPY operation, e.g.
+//
+// %v.sub16_2:regv4_16b = COPY %4.sub16_0:regv2_16b
+// %v.sub16_3:regv4_16b = COPY %4.sub16_1:regv2_16b
+// => undef %v.sub16_zw:regv4_16b = COPY %4:regv2_16b
+//
+//===----------------------------------------------------------------------===//
+
+#include "PISA.h"
+#include "PISAMCInstLower.h"
+#include "PISASubtarget.h"
+#include "llvm/Target/TargetMachine.h"
+
+#define DEBUG_TYPE "pisa-optimize-subreg-access"
+#define DEBUG_NAME "PISA optimize subreg accesses"
+
+using namespace llvm;
+
+namespace {
+
+class PISAOptimizeSubregAccess : public MachineFunctionPass {
+public:
+ static char ID;
+
+ PISAOptimizeSubregAccess();
+
+ StringRef getPassName() const override { return DEBUG_NAME; }
+
+ bool runOnMachineFunction(MachineFunction &MF) override;
+ void getAnalysisUsage(AnalysisUsage &AU) const override;
+
+ int getCombineSubreg(unsigned, unsigned, unsigned, unsigned);
+};
+} // end anonymous namespace
+
+char PISAOptimizeSubregAccess::ID = 0;
+INITIALIZE_PASS(PISAOptimizeSubregAccess, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+void PISAOptimizeSubregAccess::getAnalysisUsage(AnalysisUsage &AU) const {
+ AU.setPreservesCFG();
+ MachineFunctionPass::getAnalysisUsage(AU);
+}
+
+PISAOptimizeSubregAccess::PISAOptimizeSubregAccess() : MachineFunctionPass(ID) {
+ initializePISAOptimizeSubregAccessPass(*PassRegistry::getPassRegistry());
+}
+
+int PISAOptimizeSubregAccess::getCombineSubreg(unsigned RegSize,
+ unsigned SubRegSize,
+ unsigned Idx0, unsigned Idx1) {
+ int NewIdx = -1; // invalid
+ switch (SubRegSize) {
+ case 8: {
+ if ((Idx0 == PISA::sub8_0) && (Idx1 == PISA::sub8_1))
+ NewIdx = PISA::sub8_xy;
+ if ((Idx0 == PISA::sub8_2) && (Idx1 == PISA::sub8_3))
+ NewIdx = PISA::sub8_zw;
+ } break;
+ case 16: {
+ if ((Idx0 == PISA::sub16_0) && (Idx1 == PISA::sub16_1))
+ NewIdx = PISA::sub16_xy;
+ if ((Idx0 == PISA::sub16_2) && (Idx1 == PISA::sub16_3))
+ NewIdx = PISA::sub16_zw;
+ } break;
+ case 32: {
+ if ((Idx0 == PISA::sub32_0) && (Idx1 == PISA::sub32_1))
+ NewIdx = PISA::sub32_xy;
+ if ((Idx0 == PISA::sub32_2) && (Idx1 == PISA::sub32_3))
+ NewIdx = PISA::sub32_zw;
+ } break;
+ case 64: {
+ if ((Idx0 == PISA::sub64_0) && (Idx1 == PISA::sub64_1))
+ NewIdx = PISA::sub64_xy;
+ if ((Idx0 == PISA::sub64_2) && (Idx1 == PISA::sub64_3))
+ NewIdx = PISA::sub64_zw;
+ } break;
+ default:
+ break;
+ }
+ if ((NewIdx > 0) && (SubRegSize * 2 == RegSize))
+ NewIdx = 0; // use full reg
+ return NewIdx;
+}
+
+bool PISAOptimizeSubregAccess::runOnMachineFunction(MachineFunction &MF) {
+ auto &ST = MF.getSubtarget<PISASubtarget>();
+ auto *TII = ST.getInstrInfo();
+ auto *TRI = ST.getRegisterInfo();
+ auto &MRI = MF.getRegInfo();
+
+ bool Changed = false;
+ SmallVector<MachineInstr *> DeleteMIs;
+ MachineInstr *LastMI = nullptr;
+ for (auto &MBB : MF) {
+ LastMI = nullptr;
+ for (auto &MI : MBB) {
+ if (!LastMI || !MI.isCopy() || !LastMI->isCopy()) {
+ LastMI = &MI;
+ continue;
+ }
+ auto &Dst = MI.getOperand(0);
+ auto DstReg = Dst.getReg();
+ auto DstSubreg = Dst.getSubReg();
+ if (!(DstReg.isVirtual() && DstSubreg)) {
+ LastMI = &MI;
+ continue;
+ }
+ auto LDst = LastMI->getOperand(0);
+ auto LDstReg = LDst.getReg();
+ auto LDstSubreg = LDst.getSubReg();
+ if (!(LDstReg.isVirtual() && LDstSubreg && (LDstReg == DstReg))) {
+ LastMI = &MI;
+ continue;
+ }
+ auto *DstRC =
+ TRI->getSubRegisterClass(MRI.getRegClass(DstReg), DstSubreg);
+ auto *LDstRC =
+ TRI->getSubRegisterClass(MRI.getRegClass(LDstReg), LDstSubreg);
+ if (!DstRC || !LDstRC) {
+ LastMI = &MI;
+ continue;
+ }
+ auto DstRegSize = TRI->getRegSizeInBits(*MRI.getRegClass(DstReg));
+ auto DstSubRegSize = TRI->getSubRegIdxSize(DstSubreg);
+ auto LDstSubRegSize = TRI->getSubRegIdxSize(LDstSubreg);
+ if ((DstSubRegSize + LDstSubRegSize) > 128) { // exceed max 'mov' size
+ LastMI = &MI;
+ continue;
+ }
+ auto NewDstIdx =
+ getCombineSubreg(DstRegSize, DstSubRegSize, LDstSubreg, DstSubreg);
+ if (NewDstIdx >= 0) {
+ auto &Src = MI.getOperand(1);
+ auto SrcReg = Src.getReg();
+ auto SrcSubreg = Src.getSubReg();
+ if (!(SrcReg.isVirtual() && SrcSubreg)) {
+ LastMI = &MI;
+ continue;
+ }
+ auto LSrc = LastMI->getOperand(1);
+ auto LSrcReg = LSrc.getReg();
+ auto LSrcSubreg = LSrc.getSubReg();
+ if (!(LSrcReg.isVirtual() && LSrcSubreg && (LSrcReg == SrcReg))) {
+ LastMI = &MI;
+ continue;
+ }
+ auto *SrcRC =
+ TRI->getSubRegisterClass(MRI.getRegClass(SrcReg), SrcSubreg);
+ if (!SrcRC) {
+ LastMI = &MI;
+ continue;
+ }
+ auto SrcRegSize = TRI->getRegSizeInBits(*MRI.getRegClass(SrcReg));
+ auto SrcSubRegSize = TRI->getSubRegIdxSize(SrcSubreg);
+ auto NewSrcIdx =
+ getCombineSubreg(SrcRegSize, SrcSubRegSize, LSrcSubreg, SrcSubreg);
+ if (NewSrcIdx >= 0) {
+ DebugLoc DL = MI.getDebugLoc();
+ auto NewMI =
+ BuildMI(*MI.getParent(), MI, DL, TII->get(TargetOpcode::COPY));
+ auto DstUndef = NewDstIdx == 0 ? RegState::NoFlags : RegState::Undef;
+ auto SrcUndef = (Src.isUndef() && LSrc.isUndef()) ? RegState::Undef
+ : RegState::NoFlags;
+ NewMI.addDef(DstReg, DstUndef, NewDstIdx);
+ NewMI.addReg(SrcReg, SrcUndef, NewSrcIdx);
+ DeleteMIs.push_back(LastMI);
+ DeleteMIs.push_back(&MI);
+ Changed = true;
+ LastMI = nullptr;
+ continue;
+ }
+ }
+ LastMI = &MI;
+ }
+ }
+ for (auto *MI : DeleteMIs)
+ MI->eraseFromParent();
+ return Changed;
+}
+
+namespace llvm {
+FunctionPass *createPISAOptimizeSubregAccess() {
+ return new PISAOptimizeSubregAccess();
+}
+} // end namespace llvm
diff --git a/llvm/lib/Target/PISA/PISAPropagateNullPointers.cpp b/llvm/lib/Target/PISA/PISAPropagateNullPointers.cpp
new file mode 100644
index 0000000000000..9b9a200f88045
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAPropagateNullPointers.cpp
@@ -0,0 +1,171 @@
+//===-- PISAPropagateNullPointers.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass ensures null pointers remain null when cast between address
+// spaces, handling both direct addrspacecast instructions and constant
+// expressions.
+//
+//===----------------------------------------------------------------------===//
+
+#include "PISA.h"
+#include "PISASubtarget.h"
+#include "PISATargetMachine.h"
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Analysis/ValueTracking.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/PISAAddrSpace.h"
+
+using namespace llvm;
+using namespace llvm::PISA;
+using namespace llvm::PISAAS;
+
+#define DEBUG_TYPE "pisa-propagate-null-pointers"
+#define DEBUG_NAME "PISA propagate null pointers"
+
+namespace {
+
+class PISAPropagateNullPointers : public ModulePass {
+public:
+ static char ID;
+
+ PISAPropagateNullPointers() : ModulePass(ID) {}
+ StringRef getPassName() const override { return DEBUG_NAME; }
+ bool runOnModule(Module &M) override;
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.setPreservesCFG();
+ ModulePass::getAnalysisUsage(AU);
+ }
+};
+
+} // namespace
+
+char PISAPropagateNullPointers::ID = 0;
+INITIALIZE_PASS(PISAPropagateNullPointers, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+// Creates a null pointer for the specified address space without using
+// ConstantPointerNull::get, as LLVM assumes null pointers have a zero-bit
+// representation regardless of address space which can lead to incorrect code.
+static Constant *createNullPtr(PointerType *PtrTy, const DataLayout &DL) {
+ unsigned AS = PtrTy->getAddressSpace();
+ unsigned BitSize = DL.getPointerSizeInBits(AS);
+ return ConstantExpr::getIntToPtr(
+ ConstantInt::get(Type::getIntNTy(PtrTy->getContext(), BitSize),
+ PISATargetMachine::getNullPointerValue(AS), true),
+ PtrTy);
+}
+
+static bool isPtrKnownNonNull(const Value *Src, const DataLayout &DL) {
+ if (isa<GlobalValue, AllocaInst>(Src))
+ return true;
+
+ if (const auto *Arg = dyn_cast<Argument>(Src)) {
+ if (Arg->hasNonNullAttr())
+ return true;
+ if (Arg->getParent()->getCallingConv() == CallingConv::PISA_KERNEL) {
+ unsigned AS = Arg->getType()->getPointerAddressSpace();
+ if (AS == static_cast<unsigned>(AddressSpace::PRIVATE) ||
+ AS == static_cast<unsigned>(AddressSpace::SHARED))
+ return true;
+ }
+ }
+
+ return isKnownNonZero(Src, DL);
+}
+
+// Only casts between generic and shared/private address spaces are processed.
+// Casts between global and generic address spaces are skipped since they do not
+// change the pointer's bit representation.
+static bool isCandidate(const AddrSpaceCastInst &ASC) {
+ if (ASC.getType()->isVectorTy())
+ return false;
+
+ const unsigned SrcAS = ASC.getSrcAddressSpace();
+ const unsigned DstAS = ASC.getDestAddressSpace();
+
+ constexpr unsigned PrivateAS = static_cast<unsigned>(AddressSpace::PRIVATE);
+ constexpr unsigned SharedAS = static_cast<unsigned>(AddressSpace::SHARED);
+ constexpr unsigned GenericAS = static_cast<unsigned>(AddressSpace::GENERIC);
+
+ if (SrcAS == GenericAS && (DstAS == PrivateAS || DstAS == SharedAS))
+ return true;
+ if ((SrcAS == PrivateAS || SrcAS == SharedAS) && DstAS == GenericAS)
+ return true;
+ return false;
+}
+
+static bool processASC(AddrSpaceCastInst &ASC, const DataLayout &DL) {
+ if (!isCandidate(ASC))
+ return false;
+
+ auto *Src = ASC.getPointerOperand();
+ SmallVector<const Value *, 4> WorkList;
+ getUnderlyingObjects(Src, WorkList);
+ if (all_of(WorkList,
+ [&DL](const Value *V) { return isPtrKnownNonNull(V, DL); }))
+ return false;
+
+ auto *SrcNull = createNullPtr(cast<PointerType>(Src->getType()), DL);
+ auto *DstNull = createNullPtr(cast<PointerType>(ASC.getType()), DL);
+
+ IRBuilder<> IRB(&ASC);
+ auto *ASCCopy = IRB.CreateAddrSpaceCast(Src, ASC.getType(), ASC.getName());
+ auto *IsNonNull = IRB.CreateICmpNE(Src, SrcNull);
+ auto *Select = IRB.CreateSelect(IsNonNull, ASCCopy, DstNull);
+ ASC.replaceAllUsesWith(Select);
+ ASC.eraseFromParent();
+ return true;
+}
+
+// Replaces Clang-generated constant expression casts from generic null pointers
+// to shared/private address spaces with inttoptr expressions. Only casts from
+// generic to shared/private address spaces are processed.
+static bool updateConstExprCasts(LLVMContext &Ctx, const DataLayout &DL) {
+ auto *NullGeneric = ConstantPointerNull::get(
+ PointerType::get(Ctx, static_cast<unsigned>(AddressSpace::GENERIC)));
+ auto *NullPrivate = ConstantPointerNull::get(
+ PointerType::get(Ctx, static_cast<unsigned>(AddressSpace::PRIVATE)));
+ auto *NullShared = ConstantPointerNull::get(
+ PointerType::get(Ctx, static_cast<unsigned>(AddressSpace::SHARED)));
+
+ auto *GenericToPrivateCast =
+ ConstantExpr::getAddrSpaceCast(NullGeneric, NullPrivate->getType());
+ auto *GenericToSharedCast =
+ ConstantExpr::getAddrSpaceCast(NullGeneric, NullShared->getType());
+
+ bool Changed =
+ !GenericToPrivateCast->use_empty() || !GenericToSharedCast->use_empty();
+ GenericToPrivateCast->replaceAllUsesWith(
+ createNullPtr(cast<PointerType>(NullPrivate->getType()), DL));
+ GenericToSharedCast->replaceAllUsesWith(
+ createNullPtr(cast<PointerType>(NullShared->getType()), DL));
+ return Changed;
+}
+
+bool PISAPropagateNullPointers::runOnModule(Module &M) {
+ bool Changed = false;
+ for (auto &F : M)
+ for (auto &I : make_early_inc_range(instructions(F)))
+ if (auto *ASC = dyn_cast<AddrSpaceCastInst>(&I))
+ Changed |= processASC(*ASC, M.getDataLayout());
+
+ Changed |= updateConstExprCasts(M.getContext(), M.getDataLayout());
+ return Changed;
+}
+
+ModulePass *llvm::createPISAPropagateNullPointersPass() {
+ return new PISAPropagateNullPointers();
+}
diff --git a/llvm/lib/Target/PISA/PISAReplaceIntrinsics.cpp b/llvm/lib/Target/PISA/PISAReplaceIntrinsics.cpp
new file mode 100644
index 0000000000000..13f16fb594f61
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAReplaceIntrinsics.cpp
@@ -0,0 +1,99 @@
+//===-- PISAReplaceIntrinsics.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
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass runs just after IRTranslator. It will replace some of intrinsics
+// with an equivalent GMIR opcode, so that subsequent optimizations can be made.
+//
+//===----------------------------------------------------------------------===//
+
+#include "PISA.h"
+#include "PISAInstrInfo.h"
+#include "PISATargetMachine.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/Instructions.h"
+#include "llvm/IR/IntrinsicInst.h"
+#include "llvm/IR/IntrinsicsPISA.h"
+#include "llvm/Target/TargetMachine.h"
+
+#define DEBUG_TYPE "pisa-replace-intrinsics"
+#define DEBUG_NAME "PISA replace intrinsics"
+
+using namespace llvm;
+
+namespace {
+
+class PISAReplaceIntrinsics : public MachineFunctionPass {
+
+public:
+ static char ID;
+ PISAReplaceIntrinsics() : MachineFunctionPass(ID) {}
+ StringRef getPassName() const override { return DEBUG_NAME; }
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.setPreservesCFG();
+ MachineFunctionPass::getAnalysisUsage(AU);
+ }
+
+ bool runOnMachineFunction(MachineFunction &MF) override;
+
+private:
+ const PISAInstrInfo *TII = nullptr;
+};
+
+} // namespace
+
+char PISAReplaceIntrinsics::ID = 0;
+INITIALIZE_PASS(PISAReplaceIntrinsics, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+bool PISAReplaceIntrinsics::runOnMachineFunction(MachineFunction &MF) {
+ TII = MF.getSubtarget<PISASubtarget>().getInstrInfo();
+ bool Changed = false;
+
+ SmallVector<MachineInstr *, 8> Delete;
+ for (auto &MBB : MF) {
+ for (auto &MI : MBB) {
+ if (MI.getOpcode() == TargetOpcode::G_INTRINSIC) {
+ auto ID = cast<GIntrinsic>(MI).getIntrinsicID();
+ switch (ID) {
+ case Intrinsic::pisa_sbfe:
+ case Intrinsic::pisa_ubfe: {
+ // Res = pisa_[su]bfe (Base,Width,Offset)
+ // Res = G_[SU]BFX (Base,LSB,Width)
+ auto Opcode = (ID == Intrinsic::pisa_sbfe) ? TargetOpcode::G_SBFX
+ : TargetOpcode::G_UBFX;
+ DebugLoc DL = MI.getDebugLoc();
+ auto &Dst = MI.getOperand(0);
+ BuildMI(MBB, &MI, DL, TII->get(Opcode))
+ .addDef(Dst.getReg()) // Operand(0) is actual intrinsic
+ .add(MI.getOperand(2))
+ .add(MI.getOperand(4))
+ .add(MI.getOperand(3));
+ Delete.push_back(&MI);
+ Changed = true;
+ } break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+ for (auto *MI : Delete) {
+ MI->eraseFromParent();
+ }
+
+ return Changed;
+}
+
+FunctionPass *llvm::createPISAReplaceIntrinsicsPass() {
+ return new PISAReplaceIntrinsics();
+}
diff --git a/llvm/lib/Target/PISA/PISAScopeSelector.cpp b/llvm/lib/Target/PISA/PISAScopeSelector.cpp
new file mode 100644
index 0000000000000..45899737a1b7b
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAScopeSelector.cpp
@@ -0,0 +1,112 @@
+//===-- PISAScopeSelector.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
+//
+//===----------------------------------------------------------------------===//
+
+// syncscope("<target-scope>") support for atomics
+// - atomic operations take a 'scope' argument (part of MachineMemOperand)
+// - PISA instructions define $scope input used in instruction printing
+// - this pass extract 'scope' from MI and encodes associated value into $scope
+#include "MCTargetDesc/PISAInstPrinter.h"
+#include "PISA.h"
+
+#define GET_INSTRINFO_OPERAND_ENUM
+#include "PISAGenInstrInfo.inc"
+#define GET_AtomicScopeControl_DECL
+#include "PISAGenSearchableTables.inc"
+
+#include "llvm/CodeGen/MachineMemOperand.h"
+#include "llvm/IR/DiagnosticInfo.h"
+
+#define DEBUG_TYPE "pisa-scope-selector"
+#define DEBUG_NAME "PISA Scope Selector"
+
+using namespace llvm;
+using namespace llvm::PISA;
+
+namespace {
+class PISAScopeSelector : public llvm::MachineFunctionPass {
+public:
+ static char ID;
+
+ PISAScopeSelector() : MachineFunctionPass(ID) {}
+ StringRef getPassName() const override { return DEBUG_NAME; }
+ void getAnalysisUsage(AnalysisUsage &AU) const override;
+ bool runOnMachineFunction(MachineFunction &MF) override;
+};
+} // namespace
+
+char PISAScopeSelector::ID = 0;
+INITIALIZE_PASS(PISAScopeSelector, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+MachineFunctionPass *llvm::createPISAScopeSelectorPass() {
+ return new PISAScopeSelector();
+}
+
+void PISAScopeSelector::getAnalysisUsage(AnalysisUsage &AU) const {
+ AU.setPreservesCFG();
+ MachineFunctionPass::getAnalysisUsage(AU);
+}
+
+namespace {
+const StringMap<unsigned> ScopeName2Encoding = {
+ {"workgroup", AtomicScopeControl_WORKGROUP},
+ {"gpu", AtomicScopeControl_GPU},
+ {"system", AtomicScopeControl_SYSTEM},
+ // using workgroup scope
+ {"subgroup", AtomicScopeControl_WORKGROUP},
+ {"workitem", AtomicScopeControl_WORKGROUP},
+};
+} // namespace
+
+bool PISAScopeSelector::runOnMachineFunction(MachineFunction &MF) {
+ // SyncScopeID are dynamically assigned during parsing, so
+ // we need to map them back to AtomicScopeControl definitions
+ auto &Ctx = MF.getFunction().getContext();
+ DenseMap<SyncScope::ID, unsigned> ScopeID2Encoding;
+ for (const auto &[Name, Encoding] : ScopeName2Encoding) {
+ auto ID = Ctx.getOrInsertSyncScopeID(Name);
+ ScopeID2Encoding.emplace_or_assign(ID, Encoding);
+ }
+ ScopeID2Encoding.emplace_or_assign(SyncScope::System,
+ AtomicScopeControl_SYSTEM);
+
+ for (MachineBasicBlock &MBB : MF) {
+ for (MachineInstr &MI : MBB) {
+ if (MI.memoperands_empty())
+ continue;
+
+ auto *MMO = *MI.memoperands_begin();
+ if (!(MMO->isLoad() || MMO->isStore()))
+ continue;
+
+ auto Ordering = MMO->getSuccessOrdering();
+ // Catch inconsistent atomic ordering
+ if (!isValidAtomicOrdering(static_cast<unsigned>(Ordering))) {
+ MI.emitGenericError("invalid atomic ordering in MachineMemOperand");
+ continue;
+ }
+
+ if (!isStrongerThanMonotonic(Ordering))
+ continue;
+
+ auto OpName = PISA::OpName::scope;
+ auto OpIdx = PISA::getNamedOperandIdx(MI.getOpcode(), OpName);
+ if (OpIdx == -1)
+ continue;
+
+ if (MI.getOperand(OpIdx).getImm() != AtomicScopeControl_NONE)
+ continue; // skip if already set (pisa2pisa)
+
+ auto ScopeID = MMO->getSyncScopeID();
+ auto Entry = ScopeID2Encoding.find(ScopeID);
+ if (Entry == ScopeID2Encoding.end())
+ llvm_unreachable("unsupported syncscope");
+ MI.getOperand(OpIdx).setImm(Entry->second);
+ }
+ }
+ return false;
+}
diff --git a/llvm/lib/Target/PISA/PISATargetMachine.cpp b/llvm/lib/Target/PISA/PISATargetMachine.cpp
index 6a6a750d6bb97..4a8f1a28dcb00 100644
--- a/llvm/lib/Target/PISA/PISATargetMachine.cpp
+++ b/llvm/lib/Target/PISA/PISATargetMachine.cpp
@@ -13,6 +13,7 @@
#include "TargetInfo/PISATargetInfo.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Analysis/TargetTransformInfo.h"
+#include "llvm/CodeGen/DeadMachineInstructionElim.h"
#include "llvm/CodeGen/GlobalISel/IRTranslator.h"
#include "llvm/CodeGen/GlobalISel/InstructionSelect.h"
#include "llvm/CodeGen/GlobalISel/Legalizer.h"
@@ -25,11 +26,18 @@
#include "llvm/IR/IntrinsicsPISA.h"
#include "llvm/InitializePasses.h"
#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Support/CommandLine.h"
#include "llvm/Support/PISAAddrSpace.h"
#include "llvm/Target/TargetOptions.h"
+#include "llvm/Transforms/Vectorize/LoadStoreVectorizer.h"
using namespace llvm;
+static cl::opt<bool>
+ EnableLoadStoreVectorizer("pisa-load-store-vectorizer",
+ cl::desc("Enable load store vectorizer"),
+ cl::init(true), cl::Hidden);
+
// NOLINTNEXTLINE(readability-identifier-naming)
extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializePISATarget() {
// Register the target.
@@ -37,8 +45,25 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializePISATarget() {
PassRegistry &PR = *PassRegistry::getPassRegistry();
initializeGlobalISel(PR);
+ initializePISALegalizeCallsPass(PR);
+ initializePISAEmitIntrinsicsPass(PR);
+ initializePISAExpandIntrinsicsPass(PR);
+ initializePISALegalizeSubregAccessPass(PR);
initializePISAPreLegalizerCombinerPass(PR);
initializePISAPostLegalizerCombinerPass(PR);
+ initializePISALegalizePredicatesPass(PR);
+ initializePISAReplaceIntrinsicsPass(PR);
+ initializePISAPropagateNullPointersPass(PR);
+ initializePISACacheHintSelectorPass(PR);
+ initializePISAVerifierPass(PR);
+ initializePISAOptimizeRedundantCopiesPass(PR);
+ initializePISAOptimizeSubregAccessPass(PR);
+ initializePISAInsertLifetimeStartPass(PR);
+ initializePISAMarkConvergentNoMergePass(PR);
+ initializePISAScopeSelectorPass(PR);
+ initializePISAVerifyTypesPass(PR);
+ initializePISAKernelByValArgsLoweringLegacyPass(PR);
+ initializePISALayoutPass(PR);
}
MachineFunctionInfo *PISATargetMachine::createMachineFunctionInfo(
@@ -135,82 +160,157 @@ namespace {
// PISA Code Generator Pass Configuration Options.
//
// PISA is a virtual-register-only target: it maintains virtual registers
-// throughout the pipeline and does not run register allocation. This
-// configuration wires up the GlobalISel selection stages and disables the
-// standard machine passes that assume physical registers exist.
+// throughout the pipeline and does not run register allocation.
class PISAPassConfig : public TargetPassConfig {
public:
PISAPassConfig(PISATargetMachine &TM, PassManagerBase &PM)
: TargetPassConfig(TM, PM) {
disablePass(&GCLoweringID);
disablePass(&ShadowStackGCLoweringID);
+ disablePass(&XRayInstrumentationID);
}
PISATargetMachine &getPISATargetMachine() const {
return getTM<PISATargetMachine>();
}
- void addIRPasses() override {
- TargetPassConfig::addIRPasses();
-
- // Disable passes that assume physical registers exist.
- disablePass(&PrologEpilogCodeInserterID);
- disablePass(&MachineLateInstrsCleanupID);
- disablePass(&MachineCopyPropagationID);
- disablePass(&TailDuplicateLegacyID);
- disablePass(&StackMapLivenessID);
- disablePass(&LiveDebugValuesID);
- disablePass(&PostRAMachineSinkingID);
- disablePass(&PostRASchedulerID);
- disablePass(&FuncletLayoutID);
- disablePass(&PatchableFunctionID);
- disablePass(&ShrinkWrapID);
- disablePass(&RemoveLoadsIntoFakeUsesID);
- disablePass(&GCMachineCodeAnalysisID);
- }
+ void addIRPasses() override;
+ void addISelPrepare() override;
+ bool addIRTranslator() override;
+ void addPreLegalizeMachineIR() override;
+ bool addLegalizeMachineIR() override;
+ void addPreRegBankSelect() override;
+ bool addRegBankSelect() override;
+ bool addGlobalInstructionSelect() override;
+ FunctionPass *createTargetRegisterAllocator(bool) override { return nullptr; }
+ bool addRegAssignAndRewriteFast() override { return false; }
+ bool addRegAssignAndRewriteOptimized() override { return false; }
+ void addPreRegAlloc() override;
+ void addPostRegAlloc() override;
+ void addPreEmitPass() override;
+};
+} // namespace
- bool addIRTranslator() override {
- addPass(new IRTranslatorLegacy(getOptLevel()));
- return false;
- }
+void PISAPassConfig::addIRPasses() {
+ addPass(createPISAVerifierPass());
- void addPreLegalizeMachineIR() override {
- if (getOptLevel() != CodeGenOptLevel::None)
- addPass(createPISAPreLegalizerCombiner());
- }
+ // Legalize atomics with LLVM's AtomicExpandPass, driven by the
+ // PISATargetLowering atomic hooks. Keep it first in addIRPasses().
+ addPass(createAtomicExpandLegacyPass());
- bool addLegalizeMachineIR() override {
- addPass(new LegalizerLegacy());
- return false;
- }
+ TargetPassConfig::addIRPasses();
+
+ addPass(createPISAPropagateNullPointersPass());
+ addPass(createPISAKernelByValArgsLoweringLegacyPass());
+ addPass(createPISAExpandIntrinsicsPass());
+ addPass(createPISALegalizeCallsPass());
+
+ if ((getOptLevel() != CodeGenOptLevel::None) && EnableLoadStoreVectorizer)
+ addPass(createLoadStoreVectorizerPass());
+
+ // A temporary solution to prevent divergent barrier calls.
+ addPass(createPISALayoutPass());
+
+ // Disable passes that assume physical registers exist.
+ disablePass(&PrologEpilogCodeInserterID);
+ disablePass(&MachineLateInstrsCleanupID);
+ disablePass(&MachineCopyPropagationID);
+ disablePass(&TailDuplicateLegacyID);
+ disablePass(&StackMapLivenessID);
+ disablePass(&LiveDebugValuesID);
+ disablePass(&PostRAMachineSinkingID);
+ disablePass(&PostRASchedulerID);
+ disablePass(&FuncletLayoutID);
+ disablePass(&PatchableFunctionID);
+ disablePass(&ShrinkWrapID);
+ disablePass(&RemoveLoadsIntoFakeUsesID);
+ disablePass(&GCMachineCodeAnalysisID);
+}
+
+void PISAPassConfig::addISelPrepare() {
+ addPass(createPISAEmitIntrinsicsPass());
+ TargetPassConfig::addISelPrepare();
+}
+
+bool PISAPassConfig::addIRTranslator() {
+ addPass(new IRTranslatorLegacy(getOptLevel()));
+ addPass(createPISAVerifyTypesPass());
+ addPass(createPISAReplaceIntrinsicsPass());
+ return false;
+}
- void addPreRegBankSelect() override {
- if (getOptLevel() != CodeGenOptLevel::None) {
- addPass(&MachineCSELegacyID);
- addPass(createPISAPostLegalizerCombiner());
- }
+void PISAPassConfig::addPreLegalizeMachineIR() {
+ if (getOptLevel() != CodeGenOptLevel::None) {
+ addPass(createPISALegalizePredicatesPass());
+ addPass(createPISAPreLegalizerCombiner());
+ addPass(createPISAVerifyTypesPass());
}
+}
+
+bool PISAPassConfig::addLegalizeMachineIR() {
+ addPass(new LegalizerLegacy());
+ addPass(createPISAVerifyTypesPass());
+ return false;
+}
- bool addRegBankSelect() override {
- addPass(new RegBankSelect());
- return false;
+void PISAPassConfig::addPreRegBankSelect() {
+ if (getOptLevel() != CodeGenOptLevel::None) {
+ addPass(&MachineCSELegacyID);
+ addPass(createPISAPostLegalizerCombiner());
+ addPass(createPISAVerifyTypesPass());
}
+}
+
+bool PISAPassConfig::addRegBankSelect() {
+ addPass(new RegBankSelect());
+ return false;
+}
+
+bool PISAPassConfig::addGlobalInstructionSelect() {
+ // The MachineCSE pass doesn't detect common subexpressions on instructions
+ // using IMPLICIT_DEF instructions. These are sometimes inserted by
+ // InstructionSelect, so we run MachineCSE before that to ensure good CSE.
+ if (getOptLevel() != CodeGenOptLevel::None)
+ addPass(&MachineCSELegacyID);
+
+ addPass(createPISAVerifyTypesPass());
+ addPass(new InstructionSelect());
+ addPass(createPISAScopeSelectorPass());
+ addPass(createPISACacheHintSelectorPass());
+ // G_BUILD_VECTOR will produce IMPLICIT_DEFS that must be removed.
+ if (getOptLevel() == CodeGenOptLevel::None)
+ addPass(&ProcessImplicitDefsID);
+
+ return false;
+}
- bool addGlobalInstructionSelect() override {
- if (getOptLevel() != CodeGenOptLevel::None)
- addPass(&MachineCSELegacyID);
- addPass(new InstructionSelect());
- if (getOptLevel() == CodeGenOptLevel::None)
- addPass(&ProcessImplicitDefsID);
- return false;
+void PISAPassConfig::addPreRegAlloc() {
+ if (getOptLevel() != CodeGenOptLevel::None)
+ addPass(&LiveRangeShrinkID);
+ TargetPassConfig::addPreRegAlloc();
+}
+
+void PISAPassConfig::addPostRegAlloc() {
+ addPass(createPISALegalizeSubregAccess());
+ if (getOptLevel() != CodeGenOptLevel::None) {
+ addPass(createPISAOptimizeSubregAccess());
+ addPass(createPISAOptimizeRedundantCopies());
+ addPass(&DeadMachineInstructionElimID);
}
+ addPass(createPISAMarkConvergentNoMerge());
+ // The machine block placement pass is able to rearrange blocks in a way that
+ // breaks control flow for kernels with disabled IFP.
+ disablePass(&MachineBlockPlacementID);
+ TargetPassConfig::addPostRegAlloc();
+}
- // PISA does not allocate physical registers.
- FunctionPass *createTargetRegisterAllocator(bool) override { return nullptr; }
- bool addRegAssignAndRewriteFast() override { return false; }
- bool addRegAssignAndRewriteOptimized() override { return false; }
-};
-} // namespace
+void PISAPassConfig::addPreEmitPass() {
+ // The lifetime.start marker names a *post-coalescing* virtual register, so it
+ // must run after Register Coalescer and PISAOptimizeRedundantCopies.
+ if (getOptLevel() != CodeGenOptLevel::None)
+ addPass(createPISAInsertLifetimeStart());
+ TargetPassConfig::addPreEmitPass();
+}
TargetPassConfig *PISATargetMachine::createPassConfig(PassManagerBase &PM) {
return new PISAPassConfig(*this, PM);
diff --git a/llvm/lib/Target/PISA/PISAVerifier.cpp b/llvm/lib/Target/PISA/PISAVerifier.cpp
new file mode 100644
index 0000000000000..3c536b05fb537
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAVerifier.cpp
@@ -0,0 +1,301 @@
+//===-- PISAVerifier.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 "PISA.h"
+#include "PISATargetMachine.h"
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/CodeGen/TargetPassConfig.h"
+#include "llvm/IR/DiagnosticInfo.h"
+#include "llvm/IR/Function.h"
+#include "llvm/IR/InstVisitor.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/IntrinsicsPISA.h"
+#include "llvm/IR/Metadata.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PISAIntrinsicUtils.h"
+#include "llvm/InitializePasses.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/PISAAddrSpace.h"
+
+#define DEBUG_TYPE "pisa-verifier"
+#define DEBUG_NAME "PISA verifier"
+
+using namespace llvm;
+
+namespace {
+
+class PISAVerifier : public ModulePass, public InstVisitor<PISAVerifier> {
+public:
+ static char ID;
+ PISAVerifier() : ModulePass(ID) {}
+
+ StringRef getPassName() const override { return DEBUG_NAME; }
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.addRequired<TargetPassConfig>();
+ AU.setPreservesAll();
+ }
+
+ bool runOnModule(Module &M) override;
+
+ void visitIntrinsicInst(IntrinsicInst &I);
+ void visitStoreInst(StoreInst &I);
+ void visitAtomicRMWInst(AtomicRMWInst &I);
+
+private:
+ void verifyFunction(Function &F);
+ void verifyGlobalVariable(GlobalVariable &GV);
+ void verifyKernelArg(Argument &Arg);
+ void verifyRoundingMode(IntrinsicInst &I, bool HasSaturation = true);
+ void verifyEnumArg(IntrinsicInst &I, unsigned ArgIdx, unsigned MaxVal,
+ StringRef ArgName);
+ void verifyHostAccessMetadata(const GlobalVariable &GV,
+ ArrayRef<const MDNode *> MDs);
+
+ void illegal(Twine Message) {
+ assert(Ctx);
+ Ctx->diagnose(DiagnosticInfoGeneric({Twine("PISA Verifier: ") + Message}));
+ }
+
+ void warning(Twine Message) {
+ assert(Ctx);
+ Ctx->diagnose(DiagnosticInfoGeneric(
+ {Twine("PISA Verifier: ") + Message, DS_Warning}));
+ }
+
+ SmallSet<StringRef, 4> HostAccessNamesSeen;
+ LLVMContext *Ctx = nullptr;
+ const TargetMachine *TM = nullptr;
+ const Function *CurrFunc = nullptr;
+};
+
+} // namespace
+
+char PISAVerifier::ID = 0;
+INITIALIZE_PASS_BEGIN(PISAVerifier, DEBUG_TYPE, DEBUG_NAME, false, false)
+INITIALIZE_PASS_DEPENDENCY(TargetPassConfig)
+INITIALIZE_PASS_END(PISAVerifier, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+void PISAVerifier::verifyKernelArg(Argument &Arg) {
+ // According to PISA spec:
+ // - Kernel arguments are modeled as being flattened out and stored in
+ // abstract location pointed to by corresponding kernel argument with actual
+ // storage location of kernel parameters being implementation defined.
+ // - Kernel parameters are read-only.
+ // If a kernel argument is passed by value, PISA backend
+ // substitutes the pointer argument with the actual value it points to.
+ // Based on all the above:
+ // - byref arguments have no sense in PISA and are not expected
+ // - byval arguments are expected to be in the private addrspace in order to
+ // allow creation of copies
+ if (!Arg.getType()->isPointerTy())
+ return;
+ if (Arg.hasByRefAttr())
+ illegal(Twine("Kernel pointer arguments with the byref attribute are not "
+ "allowed\nKernel: ") +
+ Twine(Arg.getParent()->getName()) +
+ "\nArg no: " + Twine(Arg.getArgNo()));
+ else if (Arg.hasByValAttr() &&
+ Arg.getType()->getPointerAddressSpace() !=
+ static_cast<unsigned>(PISAAS::AddressSpace::PRIVATE))
+ illegal(Twine("Kernel pointer arguments with byval attribute are "
+ "expected to be in the private addrspace\nKernel: ") +
+ Twine(Arg.getParent()->getName()) +
+ "\nArg no: " + Twine(Arg.getArgNo()));
+}
+
+void PISAVerifier::verifyRoundingMode(IntrinsicInst &I, bool HasSaturation) {
+ const llvm::Function *F = I.getCalledFunction();
+ auto RndOpndIdx =
+ HasSaturation ? I.getNumOperands() - 3 : I.getNumOperands() - 2;
+ auto RndValue = static_cast<llvm::RoundingMode>(
+ cast<ConstantInt>(I.getOperand(RndOpndIdx))->getZExtValue());
+ switch (RndValue) {
+ case llvm::RoundingMode::TowardZero:
+ case llvm::RoundingMode::NearestTiesToEven:
+ case llvm::RoundingMode::TowardPositive:
+ case llvm::RoundingMode::TowardNegative:
+ case llvm::RoundingMode::NearestTiesToAway:
+ case llvm::RoundingMode::Invalid:
+ break;
+ default:
+ illegal("Intrinsic " + F->getName() +
+ " specifies invalid rounding mode value " +
+ std::to_string(static_cast<int>(RndValue)));
+ }
+}
+
+void PISAVerifier::verifyEnumArg(IntrinsicInst &I, unsigned ArgIdx,
+ unsigned MaxVal, StringRef ArgName) {
+ const llvm::Function *F = I.getCalledFunction();
+ auto Val = cast<ConstantInt>(I.getArgOperand(ArgIdx))->getZExtValue();
+ if (Val > MaxVal)
+ illegal("Intrinsic " + F->getName() + " has invalid " + ArgName +
+ " value " + std::to_string(Val));
+}
+
+void PISAVerifier::visitIntrinsicInst(IntrinsicInst &I) {
+ auto IID = I.getIntrinsicID();
+ switch (IID) {
+ case Intrinsic::log:
+ case Intrinsic::log2:
+ case Intrinsic::log10:
+ case Intrinsic::exp:
+ case Intrinsic::sin:
+ case Intrinsic::cos:
+ case Intrinsic::pow:
+ case Intrinsic::powi:
+ if (I.getType()->isDoubleTy()) {
+ const llvm::Function *F = I.getCalledFunction();
+ assert(F && "Intrinsic must have a called function");
+ illegal("Intrinsic " + F->getName() +
+ " is not supported on the PISA target");
+ }
+ break;
+ case Intrinsic::pisa_bfn: {
+ uint8_t Lut = static_cast<uint8_t>(
+ cast<ConstantInt>(I.getArgOperand(0))->getZExtValue());
+ if (Lut == 0x00)
+ illegal("BFN operation 0x00 is invalid; valid range is 0x01 to 0xfe");
+ else if (Lut == 0xff)
+ illegal("BFN operation 0xff is invalid; valid range is 0x01 to 0xfe");
+ break;
+ }
+ case Intrinsic::pisa_fadd:
+ case Intrinsic::pisa_fsub:
+ case Intrinsic::pisa_fmul:
+ case Intrinsic::pisa_fma:
+ case Intrinsic::pisa_sitofp:
+ case Intrinsic::pisa_uitofp:
+ case Intrinsic::pisa_ftrunc:
+ verifyRoundingMode(I);
+ break;
+ case Intrinsic::pisa_fdiv_rnd:
+ case Intrinsic::pisa_pow_rnd:
+ case Intrinsic::pisa_fsqrt_rnd:
+ case Intrinsic::pisa_frnd_rnd:
+ case Intrinsic::pisa_frcp_rnd:
+ case Intrinsic::pisa_sin_rnd:
+ case Intrinsic::pisa_cos_rnd:
+ case Intrinsic::pisa_tanh_rnd:
+ case Intrinsic::pisa_exp_rnd:
+ case Intrinsic::pisa_exp2_rnd:
+ case Intrinsic::pisa_log_rnd:
+ case Intrinsic::pisa_log2_rnd:
+ case Intrinsic::pisa_log10_rnd:
+ case Intrinsic::pisa_fptosi_rnd:
+ case Intrinsic::pisa_fptoui_rnd:
+ verifyRoundingMode(I, /*HasSaturation=*/false);
+ break;
+ case Intrinsic::pisa_shfl:
+ verifyEnumArg(I, 0, pisa::SHFLMode::Last - 1, "shfl mode");
+ break;
+ case Intrinsic::pisa_ired:
+ verifyEnumArg(I, 0, pisa::IRedOp::Last - 1, "ired op");
+ break;
+ case Intrinsic::pisa_fred:
+ verifyEnumArg(I, 0, pisa::FRedOp::Last - 1, "fred op");
+ break;
+ default:
+ break;
+ }
+}
+
+void PISAVerifier::visitStoreInst(StoreInst &I) {
+ if (I.getPointerAddressSpace() ==
+ static_cast<unsigned>(PISAAS::AddressSpace::CONSTANT))
+ illegal("Store to constant memory is not allowed");
+}
+
+void PISAVerifier::visitAtomicRMWInst(AtomicRMWInst &I) {
+ if (I.getPointerAddressSpace() ==
+ static_cast<unsigned>(PISAAS::AddressSpace::CONSTANT))
+ illegal("AtomicRMW on constant memory is not allowed");
+}
+
+void PISAVerifier::verifyFunction(Function &F) {
+ CurrFunc = &F;
+ if (F.getCallingConv() == CallingConv::PISA_KERNEL)
+ for (auto &Arg : F.args())
+ verifyKernelArg(Arg);
+
+ visit(F);
+}
+
+// The frontend attaches !intel_host_access metadata to a global variable to
+// describe its host-side visibility. The metadata node has two operands: the
+// host access mode (a 32-bit integer in the range [0, 3]) and the host-visible
+// name (a string). The backend lowers it to the ".host_access" variable
+// directive documented in the PISA spec (intel.github.io/pisa/variables.html).
+void PISAVerifier::verifyHostAccessMetadata(const GlobalVariable &GV,
+ ArrayRef<const MDNode *> MDs) {
+ if (MDs.size() != 1) {
+ illegal("!intel_host_access metadata attached more than once to global '" +
+ GV.getName() + "'");
+ return;
+ }
+
+ const MDNode *MD = MDs[0];
+ if (MD->getNumOperands() != 2) {
+ illegal("!intel_host_access metadata must have exactly 2 operands");
+ return;
+ }
+
+ auto VerifyFirstOp = [&]() -> bool {
+ auto *HostAccessVal = mdconst::dyn_extract<ConstantInt>(MD->getOperand(0));
+ if (!HostAccessVal)
+ return false;
+
+ if (HostAccessVal->getBitWidth() != 32)
+ return false;
+
+ if (HostAccessVal->getZExtValue() > 3)
+ return false;
+
+ return true;
+ };
+
+ if (!VerifyFirstOp())
+ illegal("Host access mode (first operand) of !intel_host_access metadata "
+ "must be a 32-bit integer in the range [0, 3].");
+
+ auto *NameMD = dyn_cast<MDString>(MD->getOperand(1));
+ if (!NameMD) {
+ illegal(
+ "Host name (second operand) of !intel_host_access metadata must be a "
+ "string.");
+ return;
+ }
+
+ const bool Inserted = HostAccessNamesSeen.insert(NameMD->getString()).second;
+ if (!Inserted)
+ illegal("Host access name '" + NameMD->getString() +
+ "' is specified for more than one global variable");
+}
+
+void PISAVerifier::verifyGlobalVariable(GlobalVariable &GV) {
+ SmallVector<MDNode *, 1> MDs;
+ const unsigned HostAccessKindId = Ctx->getMDKindID("intel_host_access");
+ if (GV.getMetadata(HostAccessKindId, MDs); !MDs.empty())
+ verifyHostAccessMetadata(GV, MDs);
+}
+
+bool PISAVerifier::runOnModule(Module &M) {
+ Ctx = &M.getContext();
+ auto &TPC = getAnalysis<TargetPassConfig>();
+ TM = &TPC.getTM<TargetMachine>();
+ for (Function &F : M)
+ verifyFunction(F);
+ for (GlobalVariable &GV : M.globals())
+ verifyGlobalVariable(GV);
+ return false;
+}
+
+ModulePass *llvm::createPISAVerifierPass() { return new PISAVerifier(); }
diff --git a/llvm/lib/Target/PISA/PISAVerifyTypes.cpp b/llvm/lib/Target/PISA/PISAVerifyTypes.cpp
new file mode 100644
index 0000000000000..00039e2e0ea89
--- /dev/null
+++ b/llvm/lib/Target/PISA/PISAVerifyTypes.cpp
@@ -0,0 +1,87 @@
+//===-- PISAVerifyTypes.cpp - modify function signatures ------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Mixing of scalar/integer/float types causes issues with extended LLT
+// (scalar == integer, scalar == float, but integer != float), especially
+// with combiner, e.g. folding multiple COPY into illegal instruction.
+//
+// We disallow usage of ANY_SCALAR types when extended LLT is on.
+//
+//===----------------------------------------------------------------------===//
+
+#include "PISA.h"
+#include "llvm/CodeGen/MachineRegisterInfo.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "pisa-verify-types"
+#define DEBUG_NAME "PISA verify types"
+
+static cl::opt<bool> EnableVerifyTypes("pisa-verify-extended-types",
+ cl::desc("Enable PISA verify types"),
+#ifndef NDEBUG
+ cl::init(true),
+#else // NDEBUG
+ cl::init(false),
+#endif // NDEBUG
+ cl::Hidden);
+
+namespace {
+
+class PISAVerifyTypes : public MachineFunctionPass {
+public:
+ static char ID;
+
+ PISAVerifyTypes() : MachineFunctionPass(ID) {}
+ StringRef getPassName() const override { return DEBUG_NAME; }
+
+ void getAnalysisUsage(AnalysisUsage &AU) const override {
+ AU.setPreservesAll();
+ MachineFunctionPass::getAnalysisUsage(AU);
+ }
+ bool runOnMachineFunction(MachineFunction &MF) override;
+};
+
+} // namespace
+
+char PISAVerifyTypes::ID = 0;
+INITIALIZE_PASS(PISAVerifyTypes, DEBUG_TYPE, DEBUG_NAME, false, false)
+
+bool PISAVerifyTypes::runOnMachineFunction(MachineFunction &MF) {
+ if (!EnableVerifyTypes)
+ return false;
+
+ // TODO: remove next 2 lines after extended LLT is enabled
+ if (!LLT::getUseExtended())
+ return false;
+
+ assert(LLT::getUseExtended() &&
+ "PISAVerifyTypes only works with extended LLT");
+ // verify that we do not have any scalar types
+ for (auto &MBB : MF) {
+ for (auto &MI : MBB) {
+ if (!MI.isPreISelOpcode())
+ continue;
+ for (unsigned I = 0, E = MI.getNumOperands(); I != E; ++I) {
+ auto &MO = MI.getOperand(I);
+ if (!MO.isReg())
+ continue;
+ auto RegTy = MF.getRegInfo().getType(MO.getReg());
+ if (RegTy.getScalarType().getKind() == LLT::Kind::ANY_SCALAR)
+ MI.emitGenericError("use of scalar types in " +
+ MI.getMF()->getName() +
+ " not supported with extendedLLT");
+ }
+ }
+ }
+ return false;
+}
+
+MachineFunctionPass *llvm::createPISAVerifyTypesPass() {
+ return new PISAVerifyTypes();
+}
diff --git a/llvm/test/CodeGen/PISA/mark-convergent-no-merge.mir b/llvm/test/CodeGen/PISA/mark-convergent-no-merge.mir
new file mode 100644
index 0000000000000..b7836a729c330
--- /dev/null
+++ b/llvm/test/CodeGen/PISA/mark-convergent-no-merge.mir
@@ -0,0 +1,56 @@
+# A convergent instruction without the NoMerge flag should have it set by the pass.
+# A function with no convergent instructions should pass through unchanged.
+# RUN: llc -march=pisa -run-pass=pisa-mark-convergent-no-merge \
+# RUN: -verify-machineinstrs -o - %s | FileCheck %s
+
+# Test 1: A convergent ired instruction without NoMerge should get NoMerge set.
+# CHECK-LABEL: name: convergent_gets_nomerge
+# CHECK: %0:reg32b = nomerge ired_umax_32b_rir
+
+# Test 2: A function with no convergent instructions should pass through unchanged.
+# CHECK-LABEL: name: no_convergent_no_change
+# CHECK-NOT: nomerge
+# CHECK: ret
+
+---
+name: convergent_gets_nomerge
+alignment: 1
+legalized: true
+regBankSelected: true
+selected: true
+tracksRegLiveness: true
+noPhis: true
+isSSA: false
+noVRegs: false
+hasFakeUses: false
+registers:
+ - { id: 0, class: reg32b }
+ - { id: 1, class: reg32b }
+frameInfo:
+ maxAlignment: 1
+machineFunctionInfo: {}
+body: |
+ bb.0:
+ %1:reg32b = functionParameter_i32 0
+ %0:reg32b = ired_umax_32b_rir %1, -1, undef %0
+ retValue_i32_r %0
+...
+---
+name: no_convergent_no_change
+alignment: 1
+legalized: true
+regBankSelected: true
+selected: true
+tracksRegLiveness: true
+noPhis: true
+isSSA: false
+noVRegs: false
+hasFakeUses: false
+registers:
+frameInfo:
+ maxAlignment: 1
+machineFunctionInfo: {}
+body: |
+ bb.0:
+ ret
+...
diff --git a/llvm/test/CodeGen/PISA/pisa-scope-selector.mir b/llvm/test/CodeGen/PISA/pisa-scope-selector.mir
new file mode 100644
index 0000000000000..e092e4eb0145b
--- /dev/null
+++ b/llvm/test/CodeGen/PISA/pisa-scope-selector.mir
@@ -0,0 +1,105 @@
+# RUN: llc -march=pisa -run-pass=pisa-scope-selector -verify-machineinstrs \
+# RUN: -o - %s | FileCheck %s
+
+# Test 1: an atomic with scope already set to workgroup (value 2) must not be
+# overwritten by the pass.
+# CHECK-LABEL: name: scope_preset
+# CHECK: atomic_load_add_impl_i32_global_acq_rel_add_ri {{.*}}, 2, 0
+
+# Test 2: a non-atomic store has no $scope operand; the pass should skip it.
+# CHECK-LABEL: name: no_scope_operand
+# CHECK: st_global_i32_ri {{.*}}, 0
+
+# Test 3: monotonic ordering does not trigger a scope upgrade; scope stays at
+# 255 (NONE).
+# CHECK-LABEL: name: monotonic_no_change
+# CHECK: atomic_load_add_impl_i32_global_monotonic_add_ri {{.*}}, 255, 0
+
+--- |
+ define void @scope_preset(ptr addrspace(1) %ptr, i32 %a) {
+ %res = atomicrmw add ptr addrspace(1) %ptr, i32 %a acq_rel, align 4
+ ret void
+ }
+ define void @no_scope_operand(ptr addrspace(1) %ptr, i32 %a) {
+ store i32 %a, ptr addrspace(1) %ptr, align 4
+ ret void
+ }
+ define void @monotonic_no_change(ptr addrspace(1) %ptr, i32 %a) {
+ %res = atomicrmw add ptr addrspace(1) %ptr, i32 %a monotonic, align 4
+ ret void
+ }
+...
+---
+name: scope_preset
+alignment: 1
+legalized: true
+regBankSelected: true
+selected: true
+tracksRegLiveness: true
+noPhis: false
+isSSA: true
+noVRegs: false
+hasFakeUses: false
+registers:
+ - { id: 0, class: reg64b }
+ - { id: 1, class: reg32b }
+ - { id: 2, class: reg32b }
+frameInfo:
+ maxAlignment: 1
+machineFunctionInfo: {}
+body: |
+ bb.1 (%ir-block.0):
+ %0:reg64b = functionParameter_i64 0
+ %1:reg32b = functionParameter_i32 1
+ %2:reg32b = atomic_load_add_impl_i32_global_acq_rel_add_ri %0, 0, %1, 2, 0 :: (load store acq_rel (i32) on %ir.ptr, addrspace 1)
+ ret
+...
+---
+name: no_scope_operand
+alignment: 1
+legalized: true
+regBankSelected: true
+selected: true
+tracksRegLiveness: true
+noPhis: false
+isSSA: true
+noVRegs: false
+hasFakeUses: false
+registers:
+ - { id: 0, class: reg64b }
+ - { id: 1, class: reg32b }
+frameInfo:
+ maxAlignment: 1
+machineFunctionInfo: {}
+body: |
+ bb.1 (%ir-block.0):
+ %0:reg64b = functionParameter_i64 0
+ %1:reg32b = functionParameter_i32 1
+ st_global_i32_ri %0, 0, %1, 0 :: (store release (i32) into %ir.ptr, addrspace 1)
+ ret
+...
+---
+name: monotonic_no_change
+alignment: 1
+legalized: true
+regBankSelected: true
+selected: true
+tracksRegLiveness: true
+noPhis: false
+isSSA: true
+noVRegs: false
+hasFakeUses: false
+registers:
+ - { id: 0, class: reg64b }
+ - { id: 1, class: reg32b }
+ - { id: 2, class: reg32b }
+frameInfo:
+ maxAlignment: 1
+machineFunctionInfo: {}
+body: |
+ bb.1 (%ir-block.0):
+ %0:reg64b = functionParameter_i64 0
+ %1:reg32b = functionParameter_i32 1
+ %2:reg32b = atomic_load_add_impl_i32_global_monotonic_add_ri %0, 0, %1, 255, 0 :: (load store monotonic (i32) on %ir.ptr, addrspace 1)
+ ret
+...
diff --git a/llvm/test/CodeGen/PISA/propagate-null.ll b/llvm/test/CodeGen/PISA/propagate-null.ll
new file mode 100644
index 0000000000000..5a190dea3e5b0
--- /dev/null
+++ b/llvm/test/CodeGen/PISA/propagate-null.ll
@@ -0,0 +1,173 @@
+; RUN: llc < %s -march=pisa -stop-after=pisa-propagate-null-pointers | FileCheck %s
+
+; CHECK: @global_null_ptr = dso_local local_unnamed_addr addrspace(1) global ptr addrspace(3) inttoptr (i32 -1 to ptr addrspace(3)), align 4
+; CHECK-DAG: @shared_null = {{.*}} addrspace(3) inttoptr (i32 -1 to ptr addrspace(3))
+; CHECK-DAG: @private_null = {{.*}} addrspace(4) inttoptr (i32 -1 to ptr addrspace(4))
+ at global_null_ptr = dso_local local_unnamed_addr addrspace(1) global ptr addrspace(3) addrspacecast (ptr null to ptr addrspace(3)), align 4
+ at global_shared = addrspace(3) global i32 0, align 4
+
+; CHECK-LABEL: @test_generic_to_shared
+define ptr addrspace(3) @test_generic_to_shared(ptr %arg) {
+ ; CHECK: [[CAST:%.*]] = addrspacecast ptr %arg to ptr addrspace(3)
+ ; CHECK-NEXT: [[PRED:%.*]] = icmp ne ptr %arg, null
+ ; CHECK-NEXT: [[SEL:%.*]] = select i1 [[PRED]], ptr addrspace(3) [[CAST]], ptr addrspace(3) inttoptr (i32 -1 to ptr addrspace(3))
+ ; CHECK-NEXT: ret ptr addrspace(3) [[SEL]]
+ %ptr = addrspacecast ptr %arg to ptr addrspace(3)
+ ret ptr addrspace(3) %ptr
+}
+
+; CHECK-LABEL: @test_shared_to_generic
+define ptr @test_shared_to_generic(ptr addrspace(3) %arg) {
+ ; CHECK: [[CAST:%.*]] = addrspacecast ptr addrspace(3) %arg to ptr
+ ; CHECK-NEXT: [[PRED:%.*]] = icmp ne ptr addrspace(3) %arg, inttoptr (i32 -1 to ptr addrspace(3))
+ ; CHECK-NEXT: [[SEL:%.*]] = select i1 [[PRED]], ptr [[CAST]], ptr null
+ ; CHECK-NEXT: ret ptr [[SEL]]
+ %ptr = addrspacecast ptr addrspace(3) %arg to ptr
+ ret ptr %ptr
+}
+
+; CHECK-LABEL: @test_generic_to_private
+define ptr addrspace(4) @test_generic_to_private(ptr %arg) {
+ ; CHECK: [[CAST:%.*]] = addrspacecast ptr %arg to ptr
+ ; CHECK-NEXT: [[PRED:%.*]] = icmp ne ptr %arg, null
+ ; CHECK-NEXT: [[SEL:%.*]] = select i1 [[PRED]], ptr addrspace(4) [[CAST]], ptr addrspace(4) inttoptr (i32 -1 to ptr addrspace(4))
+ ; CHECK-NEXT: ret ptr addrspace(4) [[SEL]]
+ %ptr = addrspacecast ptr %arg to ptr addrspace(4)
+ ret ptr addrspace(4) %ptr
+}
+
+; CHECK-LABEL: @test_private_to_generic
+define ptr @test_private_to_generic(ptr addrspace(4) %arg) {
+ ; CHECK: [[CAST:%.*]] = addrspacecast ptr addrspace(4) %arg to ptr
+ ; CHECK-NEXT: [[PRED:%.*]] = icmp ne ptr addrspace(4) %arg, inttoptr (i32 -1 to ptr addrspace(4))
+ ; CHECK-NEXT: [[SEL:%.*]] = select i1 [[PRED]], ptr [[CAST]], ptr null
+ ; CHECK-NEXT: ret ptr [[SEL]]
+ %ptr = addrspacecast ptr addrspace(4) %arg to ptr
+ ret ptr %ptr
+}
+
+; CHECK-LABEL: @test_generic_to_global
+define ptr addrspace(1) @test_generic_to_global(ptr %arg) {
+ ; CHECK: %ptr = addrspacecast ptr %arg to ptr addrspace(1)
+ ; CHECK-NEXT: ret ptr addrspace(1) %ptr
+ %ptr = addrspacecast ptr %arg to ptr addrspace(1)
+ ret ptr addrspace(1) %ptr
+}
+
+; CHECK-LABEL: @test_global_to_generic
+define ptr @test_global_to_generic(ptr addrspace(1) %arg) {
+ ; CHECK: %ptr = addrspacecast ptr addrspace(1) %arg to ptr
+ ; CHECK-NEXT: ret ptr %ptr
+ %ptr = addrspacecast ptr addrspace(1) %arg to ptr
+ ret ptr %ptr
+}
+
+; CHECK-LABEL: @test_alloca_private
+define ptr @test_alloca_private() {
+ ; CHECK: %var = alloca i32, align 4, addrspace(4)
+ ; CHECK-NEXT: %ptr = addrspacecast ptr addrspace(4) %var to ptr
+ ; CHECK-NEXT: ret ptr %ptr
+ %var = alloca i32, align 4, addrspace(4)
+ %ptr = addrspacecast ptr addrspace(4) %var to ptr
+ ret ptr %ptr
+}
+
+; CHECK-LABEL: @test_global_in_shared
+define ptr @test_global_in_shared() {
+ ; CHECK: %ptr = addrspacecast ptr addrspace(3) @global_shared to ptr
+ ; CHECK-NEXT: ret ptr %ptr
+ %ptr = addrspacecast ptr addrspace(3) @global_shared to ptr
+ ret ptr %ptr
+}
+
+; CHECK-LABEL: @test_nonnull_arg
+define ptr @test_nonnull_arg(ptr addrspace(4) noundef nonnull %arg) {
+ ; CHECK: %ptr = addrspacecast ptr addrspace(4) %arg to ptr
+ ; CHECK-NEXT: ret ptr %ptr
+ %ptr = addrspacecast ptr addrspace(4) %arg to ptr
+ ret ptr %ptr
+}
+
+; CHECK-LABEL: @test_kernel_arg_shared
+define pisa_kernel void @test_kernel_arg_shared(ptr addrspace(3) %arg, ptr addrspace(1) %res) {
+ ; CHECK: %ptr = addrspacecast ptr addrspace(3) %arg to ptr
+ ; CHECK-NEXT: store ptr %ptr, ptr addrspace(1) %res, align 4
+ ; CHECK-NEXT: ret void
+ %ptr = addrspacecast ptr addrspace(3) %arg to ptr
+ store ptr %ptr, ptr addrspace(1) %res, align 4
+ ret void
+}
+
+%struct.byval = type { i32, i16, i64, i8 }
+
+; CHECK-LABEL: @test_kernel_arg_private
+define pisa_kernel void @test_kernel_arg_private(ptr addrspace(4) byval(%struct.byval) %arg, ptr addrspace(1) %res) {
+ ; CHECK: %ptr = addrspacecast ptr addrspace(4) %arg to ptr
+ ; CHECK-NEXT: store ptr %ptr, ptr addrspace(1) %res, align 4
+ ; CHECK-NEXT: ret void
+ %ptr = addrspacecast ptr addrspace(4) %arg to ptr
+ store ptr %ptr, ptr addrspace(1) %res, align 4
+ ret void
+}
+
+; CHECK-LABEL: @test_kernel_arg_different_memory
+define pisa_kernel void @test_kernel_arg_different_memory(ptr addrspace(1) %arg, ptr addrspace(1) %res) {
+ ; CHECK: [[CAST:%.*]] = addrspacecast ptr %cast to ptr addrspace(3)
+ ; CHECK-NEXT: [[PRED:%.*]] = icmp ne ptr %cast, null
+ ; CHECK-NEXT: [[RES:%.*]] = select i1 [[PRED]], ptr addrspace(3) [[CAST]], ptr addrspace(3) inttoptr (i32 -1 to ptr addrspace(3))
+ ; CHECK-NEXT: store ptr addrspace(3) [[RES]], ptr addrspace(1) %res, align 4
+ %cast = addrspacecast ptr addrspace(1) %arg to ptr
+ %cast_2 = addrspacecast ptr %cast to ptr addrspace(3)
+ store ptr addrspace(3) %cast_2, ptr addrspace(1) %res, align 4
+ ret void
+}
+
+; CHECK-LABEL: @test_constexpr_shared_null
+define i1 @test_constexpr_shared_null(i32 %val) {
+ ; CHECK: %cmp = icmp ne i32 %val, ptrtoint (ptr addrspace(3) inttoptr (i32 -1 to ptr addrspace(3)) to i32)
+ ; CHECK-NEXT: ret i1 %cmp
+ %cmp = icmp ne i32 %val, ptrtoint (ptr addrspace(3) addrspacecast (ptr null to ptr addrspace(3)) to i32)
+ ret i1 %cmp
+}
+
+; CHECK-LABEL: @test_constexpr_private_null
+define i1 @test_constexpr_private_null(i32 %val) {
+ ; CHECK: %cmp = icmp ne i32 %val, ptrtoint (ptr addrspace(4) inttoptr (i32 -1 to ptr addrspace(4)) to i32)
+ ; CHECK-NEXT: ret i1 %cmp
+ %cmp = icmp ne i32 %val, ptrtoint (ptr addrspace(4) addrspacecast (ptr null to ptr addrspace(4)) to i32)
+ ret i1 %cmp
+}
+
+; CHECK-LABEL: @test_complex_expression
+define ptr @test_complex_expression(i1 %cond) {
+ ; CHECK: [[CAST:%.*]] = addrspacecast ptr addrspace(3) %val to ptr
+ ; CHECK-NEXT: [[PRED:%.*]] = icmp ne ptr addrspace(3) %val, inttoptr (i32 -1 to ptr addrspace(3))
+ ; CHECK-NEXT: [[SEL:%.*]] = select i1 [[PRED]], ptr [[CAST]], ptr null
+ %val = select i1 %cond, ptr addrspace(3) @global_shared, ptr addrspace(3) addrspacecast (ptr null to ptr addrspace(3))
+ %res = addrspacecast ptr addrspace(3) %val to ptr
+ ret ptr %res
+}
+
+; Both globals use constant expression casts from generic null to private/shared.
+; This ensures both GenericToPrivateCast and GenericToSharedCast are non-empty,
+; so the pass marks the module as changed and replaces const expr casts.
+
+ at shared_null = addrspace(1) global ptr addrspace(3) addrspacecast (ptr null to ptr addrspace(3)), align 4
+
+ at private_null = addrspace(1) global ptr addrspace(4) addrspacecast (ptr null to ptr addrspace(4)), align 4
+
+; A function that compares against both shared and private null constant exprs,
+; keeping both uses alive so the pass must replace both addrspacecasts.
+; CHECK-LABEL: @test_both_constexpr_used
+define i1 @test_both_constexpr_used(i32 %val, i32 %val2) {
+ ; CHECK: %cmp_shared = icmp ne i32 %val, ptrtoint (ptr addrspace(3) inttoptr (i32 -1 to ptr addrspace(3)) to i32)
+ %cmp_shared = icmp ne i32 %val, ptrtoint (ptr addrspace(3) addrspacecast (ptr null to ptr addrspace(3)) to i32)
+
+ ; CHECK-NEXT: %cmp_private = icmp ne i32 %val2, ptrtoint (ptr addrspace(4) inttoptr (i32 -1 to ptr addrspace(4)) to i32)
+ %cmp_private = icmp ne i32 %val2, ptrtoint (ptr addrspace(4) addrspacecast (ptr null to ptr addrspace(4)) to i32)
+
+ ; CHECK-NEXT: %result = and i1 %cmp_shared, %cmp_private
+ %result = and i1 %cmp_shared, %cmp_private
+ ; CHECK-NEXT: ret i1 %result
+ ret i1 %result
+}
diff --git a/llvm/test/CodeGen/PISA/verify-types.mir b/llvm/test/CodeGen/PISA/verify-types.mir
new file mode 100644
index 0000000000000..b9e0911e9fb35
--- /dev/null
+++ b/llvm/test/CodeGen/PISA/verify-types.mir
@@ -0,0 +1,35 @@
+# The verify-types pass iterates pre-ISel instructions and checks for scalar
+# types when -pisa-verify-extended-types is enabled. A fully-selected function
+# with no pre-ISel instructions skips the inner loop and passes cleanly.
+
+# RUN: llc -march=pisa -run-pass=pisa-verify-types \
+# RUN: -pisa-verify-extended-types -verify-machineinstrs -o - %s \
+# RUN: | FileCheck %s
+
+# CHECK-LABEL: name: no_preisel
+# CHECK: ret
+
+--- |
+ define void @no_preisel() {
+ ret void
+ }
+...
+---
+name: no_preisel
+alignment: 1
+legalized: true
+regBankSelected: true
+selected: true
+tracksRegLiveness: true
+noPhis: true
+isSSA: false
+noVRegs: false
+hasFakeUses: false
+registers:
+frameInfo:
+ maxAlignment: 1
+machineFunctionInfo: {}
+body: |
+ bb.0:
+ ret
+...
>From 70d4135a72484fce494a3f34a4bc72b35d3fd2c9 Mon Sep 17 00:00:00 2001
From: Michal Paszkowski <michal.paszkowski at intel.com>
Date: Thu, 6 Aug 2026 22:53:38 -0700
Subject: [PATCH 2/3] Review fixes
---
llvm/lib/Target/PISA/CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/llvm/lib/Target/PISA/CMakeLists.txt b/llvm/lib/Target/PISA/CMakeLists.txt
index a790f0abe2312..8c85277a615ca 100644
--- a/llvm/lib/Target/PISA/CMakeLists.txt
+++ b/llvm/lib/Target/PISA/CMakeLists.txt
@@ -28,6 +28,7 @@ add_llvm_target(PISACodeGen
PISAInstrInfo.cpp
PISAInstructionSelector.cpp
PISAKernelByValArgsLowering.cpp
+ PISALayout.cpp
PISALegalizeCalls.cpp
PISALegalizePredicates.cpp
PISALegalizeSubregAccess.cpp
@@ -50,7 +51,6 @@ add_llvm_target(PISACodeGen
PISAUtils.cpp
PISAVerifier.cpp
PISAVerifyTypes.cpp
- PISALayout.cpp
LINK_COMPONENTS
Analysis
>From a64bd8c78d734027950706ff865d0a4157c00961 Mon Sep 17 00:00:00 2001
From: Michal Paszkowski <michal.paszkowski at intel.com>
Date: Thu, 6 Aug 2026 22:59:05 -0700
Subject: [PATCH 3/3] Formatting fix
---
llvm/lib/Target/PISA/PISAKernelByValArgsLowering.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.cpp b/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.cpp
index 03b1d6141001a..0e201bef6ccc3 100644
--- a/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.cpp
+++ b/llvm/lib/Target/PISA/PISAKernelByValArgsLowering.cpp
@@ -30,7 +30,8 @@ using namespace llvm::PISAAS;
namespace {
class PISAKernelByValArgsLowering {
public:
- explicit PISAKernelByValArgsLowering(Function &F) : F(F), Ctx(F.getContext()) {
+ explicit PISAKernelByValArgsLowering(Function &F)
+ : F(F), Ctx(F.getContext()) {
assert(F.getCallingConv() == CallingConv::PISA_KERNEL &&
"Expected PISA_KERNEL calling convention");
assert(F.getReturnType()->isVoidTy() && "Expected void return type");
@@ -90,8 +91,8 @@ bool PISAKernelByValArgsLowering::run() {
return true;
}
-AttributeList
-PISAKernelByValArgsLowering::getNewAttributes(const AttributeList &Attrs) const {
+AttributeList PISAKernelByValArgsLowering::getNewAttributes(
+ const AttributeList &Attrs) const {
AttributeList NewAttrs;
// Copy function attributes.
More information about the llvm-branch-commits
mailing list