https://github.com/MaxBrandtner updated https://github.com/llvm/llvm-project/pull/225647
>From 8d34d1e37343238eb34fd3bbbe59682e33612b7b Mon Sep 17 00:00:00 2001 From: Amr Hesham <[email protected]> Date: Tue, 3 Nov 2026 18:08:26 +0100 Subject: [PATCH 1/2] [CIR] Implement HLSL vector element lvalue scalar swizzles (#238425) Implement HLSL support for ext vector element lvalue scalar swizzles --- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 13 ++- .../test/CIR/CodeGenHLSL/scalar-swizzles.hlsl | 95 +++++++++++++++++++ 2 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 clang/test/CIR/CodeGenHLSL/scalar-swizzles.hlsl diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index ee842ae0d195cb..68a06cf7e04ab5 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -883,8 +883,11 @@ RValue CIRGenFunction::emitLoadOfExtVectorElementLValue(LValue lv) { // IR value to a vector here allows the rest of codegen to behave as normal. if (getLangOpts().HLSL && !mlir::isa<cir::VectorType, cir::MatrixType>(vec.getType())) { - cgm.errorNYI(loc, "emitLoadOfExtVectorElementLValue: HLSL"); - return {}; + auto dstTy = cir::VectorType::get( + vec.getType(), llvm::ElementCount::get(1, /*Scalable=*/false)); + cir::ConstantOp poison = + builder.getConstant(loc, cir::PoisonAttr::get(dstTy)); + vec = builder.createInsertElement(loc, poison, vec, /*idx=*/0); } assert((mlir::isa<cir::VectorType, cir::MatrixType>(vec.getType())) && @@ -1848,11 +1851,7 @@ LValue CIRGenFunction::emitCastLValue(const CastExpr *e) { case CK_AtomicToNonAtomic: case CK_ObjCObjectLValueCast: case CK_VectorSplat: { - cgm.errorNYI(e->getSourceRange(), - std::string("emitCastLValue for unhandled cast kind: ") + - e->getCastKindName()); - - return {}; + return emitLValue(e->getSubExpr()); } case CK_AddressSpaceConversion: { diff --git a/clang/test/CIR/CodeGenHLSL/scalar-swizzles.hlsl b/clang/test/CIR/CodeGenHLSL/scalar-swizzles.hlsl new file mode 100644 index 00000000000000..49a598a8b7e64c --- /dev/null +++ b/clang/test/CIR/CodeGenHLSL/scalar-swizzles.hlsl @@ -0,0 +1,95 @@ +// RUN: %clang_cc1 -x hlsl -std=hlsl2021 -triple spirv-unknown-vulkan-library -finclude-default-header -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR +// RUN: %clang_cc1 -x hlsl -std=hlsl2021 -triple spirv-unknown-vulkan-library -finclude-default-header -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM +// RUN: %clang_cc1 -x hlsl -std=hlsl2021 -triple spirv-unknown-vulkan-library -finclude-default-header -emit-llvm -disable-llvm-passes %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM + +int2 ToTwoInts(int V) { + return V.xx; +} + +// CIR: %[[V_ADDR:.*]] = cir.alloca "V" {{.*}} init : !cir.ptr<!s32i> +// CIR: cir.store %[[ARG_V:.*]], %[[V_ADDR]] : !s32i, !cir.ptr<!s32i> +// CIR: %[[TMP_V:.*]] = cir.load {{.*}} %[[V_ADDR]] : !cir.ptr<!s32i>, !s32i +// CIR: %[[POISON:.*]] = cir.const #cir.poison : !cir.vector<1 x !s32i> +// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !u64i +// CIR: %[[CAST_SPLAT:.*]] = cir.vec.insert %[[TMP_V]], %[[POISON]][%4 : !u64i] : !cir.vector<1 x !s32i> +// CIR: %[[POISON:.*]] = cir.const #cir.poison : !cir.vector<1 x !s32i> +// CIR: %[[RESULT:.*]] = cir.vec.shuffle(%[[CAST_SPLAT]], %[[POISON]] : !cir.vector<1 x !s32i>) [#cir.int<0> : !s32i, #cir.int<0> : !s32i] : !cir.vector<2 x !s32i> + +// LLVM: %[[V_ADDR:.*]] = alloca i32, align 4 +// LLVM: store i32 %[[ARG_V:.*]], ptr %[[V_ADDR]], align 4 +// LLVM: %[[TMP_V:.*]] = load i32, ptr %[[V_ADDR]], align 4 +// LLVM: %[[CAST_SPLAT:.*]] = insertelement <1 x i32> poison, i32 %[[TMP_V]], i64 0 +// LLVM: %[[RESULT:.*]] = shufflevector <1 x i32> %[[CAST_SPLAT]], <1 x i32> poison, <2 x i32> zeroinitializer + +float4 ToFourFloats(float V) { + return V.rrrr; +} + +// CIR: %[[V_ADDR:.*]] = cir.alloca "V" {{.*}} init : !cir.ptr<!cir.float> +// CIR: cir.store %[[ARG_V:.*]], %[[V_ADDR]] : !cir.float, !cir.ptr<!cir.float> +// CIR: %[[TMP_V:.*]] = cir.load {{.*}} %[[V_ADDR]] : !cir.ptr<!cir.float>, !cir.float +// CIR: %[[POISON:.*]] = cir.const #cir.poison : !cir.vector<1 x !cir.float> +// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !u64i +// CIR: %[[CAST_SPLAT:.*]] = cir.vec.insert %[[TMP_V]], %[[POISON]][%[[ZERO]] : !u64i] : !cir.vector<1 x !cir.float> +// CIR: %[[POISON:.*]] = cir.const #cir.poison : !cir.vector<1 x !cir.float> +// CIR: %[[RESULT:.*]] = cir.vec.shuffle(%[[CAST_SPLAT]], %[[POISON]] : !cir.vector<1 x !cir.float>) [#cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i] : !cir.vector<4 x !cir.float> + +// LLVM: %[[V_ADDR:.*]] = alloca float, align 4 +// LLVM: store float %[[ARG_V:.*]], ptr %[[V_ADDR]], align 4 +// LLVM: %[[TMP_V:.*]] = load float, ptr %[[V_ADDR]], align 4 +// LLVM: %[[CAST_SPLAT:.*]] = insertelement <1 x float> poison, float %[[TMP_V]], i64 0 +// LLVM: %[[RESULT:.*]] = shufflevector <1 x float> %[[CAST_SPLAT]], <1 x float> poison, <4 x i32> zeroinitializer + +int2 FillOne() { + return 1.xx; +} + +// CIR: %[[TMP_ADDR:.*]] = cir.alloca "tmp" {{.*}} : !cir.ptr<!cir.vector<1 x !s32i>> +// CIR: %[[CONST_1:.*]] = cir.const #cir.int<1> : !s32i +// CIR: %[[SPLAT:.*]] = cir.vec.splat %[[CONST_1]] : !s32i, !cir.vector<1 x !s32i> +// CIR: cir.store {{.*}} %[[SPLAT]], %[[TMP_ADDR]] : !cir.vector<1 x !s32i>, !cir.ptr<!cir.vector<1 x !s32i>> +// CIR: %[[TMP:.*]] = cir.load {{.*}} %[[TMP_ADDR]] : !cir.ptr<!cir.vector<1 x !s32i>>, !cir.vector<1 x !s32i> +// CIR: %[[POISON:.*]] = cir.const #cir.poison : !cir.vector<1 x !s32i> +// CIR: %[[RESULT:.*]] = cir.vec.shuffle(%[[TMP]], %[[POISON]] : !cir.vector<1 x !s32i>) [#cir.int<0> : !s32i, #cir.int<0> : !s32i] : !cir.vector<2 x !s32i> + +// LLVM: %[[TMP_ADDR:.*]] = alloca <1 x i32>, align 4 +// LLVM: store <1 x i32> splat (i32 1), ptr %[[TMP_ADDR]], align 4 +// LLVM: %[[TMP:.*]] = load <1 x i32>, ptr %[[TMP_ADDR]], align 4 +// LLVM: %[[RESULT:.*]] = shufflevector <1 x i32> %[[TMP]], <1 x i32> poison, <2 x i32> zeroinitializer + +uint3 FillOneUnsigned() { + return 1u.xxx; +} + +// CIR: %[[TMP_ADDR:.*]] = cir.alloca "tmp" {{.*}} : !cir.ptr<!cir.vector<1 x !u32i>> +// CIR: %[[CONST_1:.*]] = cir.const #cir.int<1> : !u32i +// CIR: %[[SPLAT:.*]] = cir.vec.splat %[[CONST_1]] : !u32i, !cir.vector<1 x !u32i> +// CIR: cir.store {{.*}} %[[SPLAT]], %[[TMP_ADDR]] : !cir.vector<1 x !u32i>, !cir.ptr<!cir.vector<1 x !u32i>> +// CIR: %[[TMP:.*]] = cir.load {{.*}} %[[TMP_ADDR]] : !cir.ptr<!cir.vector<1 x !u32i>>, !cir.vector<1 x !u32i> +// CIR: %[[POISON:.*]] = cir.const #cir.poison : !cir.vector<1 x !u32i> +// CIR: %[[RESULT:.*]] = cir.vec.shuffle(%[[TMP]], %[[POISON]] : !cir.vector<1 x !u32i>) [#cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i] : !cir.vector<3 x !u32i> + +// LLVM: %[[TMP_ADDR:.*]] = alloca <1 x i32>, align 4 +// LLVM: store <1 x i32> splat (i32 1), ptr %[[TMP_ADDR]], align 4 +// LLVM: %[[TMP:.*]] = load <1 x i32>, ptr %[[TMP_ADDR]], align 4 +// LLVM: %[[RESULT:.*]] = shufflevector <1 x i32> %[[TMP]], <1 x i32> poison, <3 x i32> zeroinitializer + +vector<uint64_t,4> FillOneUnsignedLong() { + return 1ul.xxxx; +} + +// CIR: %[[TMP_ADDR:.*]] = cir.alloca "tmp" {{.*}} : !cir.ptr<!cir.vector<1 x !u64i>> +// CIR: %[[CONST_1:.*]] = cir.const #cir.int<1> : !u64i +// CIR: %[[SPLAT:.*]] = cir.vec.splat %[[CONST_1]] : !u64i, !cir.vector<1 x !u64i> +// CIR: cir.store {{.*}} %[[SPLAT]], %[[TMP_ADDR]] : !cir.vector<1 x !u64i>, !cir.ptr<!cir.vector<1 x !u64i>> +// CIR: %[[TMP:.*]] = cir.load {{.*}} %[[TMP_ADDR]] : !cir.ptr<!cir.vector<1 x !u64i>>, !cir.vector<1 x !u64i> +// CIR: %[[POISON:.*]] = cir.const #cir.poison : !cir.vector<1 x !u64i> +// CIR: %[[RESULT:.*]] = cir.vec.shuffle(%[[TMP]], %[[POISON]] : !cir.vector<1 x !u64i>) [#cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i, #cir.int<0> : !s32i] : !cir.vector<4 x !u64i> + +// LLVM: %[[TMP_ADDR:.*]] = alloca <1 x i64>, align 8 +// LLVM: store <1 x i64> splat (i64 1), ptr %[[TMP_ADDR]], align 8 +// LLVM: %[[TMP:.*]] = load <1 x i64>, ptr %[[TMP_ADDR]], align 8 +// LLVM: %[[RESULT:.*]] = shufflevector <1 x i64> %[[TMP]], <1 x i64> poison, <4 x i32> zeroinitializer >From 65554e7ae2e09ae21ee9048ce76a8e2257142cf2 Mon Sep 17 00:00:00 2001 From: Max Brandtner <[email protected]> Date: Thu, 17 Sep 2026 20:03:05 +0200 Subject: [PATCH 2/2] [SimplifyCFG] Skip phi scans when folded block shares no predecessors Functions with many early returns end up with a single return block with one phi entry per exit. TryToSimplifyUncondBranchFromEmptyBlock() folds the empty predecessors into it one at a time. Each fold walks the phis of the successor and its predecessor list, resulting in the runtime scaling quadratically by the number of exits. These checks are only needed if the folded block shares a predecessor with its successor, because only those can produce conflicting phi values or duplicate edges. Detect shared predecessors with a heuristic, and skip the checks when there are none. The remaining walk in replaceAllUsesWith() is avoided by erasing the terminator first. No functional change; check-llvm and llvm-test-suite output are unchanged. Partially addresses #90419 Signed-off-by: Max Brandtner <[email protected]> --- llvm/lib/Transforms/Utils/Local.cpp | 110 ++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 32 deletions(-) diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp index c5864e1a514f78..315b1817f21a35 100644 --- a/llvm/lib/Transforms/Utils/Local.cpp +++ b/llvm/lib/Transforms/Utils/Local.cpp @@ -841,6 +841,25 @@ static bool CanMergeValues(Value *First, Value *Second) { return First == Second || isa<UndefValue>(First) || isa<UndefValue>(Second); } +/// Return true if one of \p BBPreds may branch to \p Succ. At most 16 +/// successors are inspected; beyond that, this conservatively returns true, so +/// the check stays cheap even if a predecessor ends in a large switch. +static bool +mayHaveSharedPredecessor(BasicBlock *Succ, + const SmallPtrSetImpl<BasicBlock *> &BBPreds) { + unsigned Budget = 16; + for (BasicBlock *Pred : BBPreds) { + const Instruction *TI = Pred->getTerminator(); + unsigned NumSuccs = TI->getNumSuccessors(); + if (NumSuccs > Budget) + return true; + Budget -= NumSuccs; + if (is_contained(successors(TI), Succ)) + return true; + } + return false; +} + /// Return true if we can fold BB, an almost-empty BB ending in an unconditional /// branch to Succ, into Succ. /// @@ -850,8 +869,6 @@ CanPropagatePredecessorsForPHIs(BasicBlock *BB, BasicBlock *Succ, const SmallPtrSetImpl<BasicBlock *> &BBPreds) { assert(*succ_begin(BB) == Succ && "Succ is not successor of BB!"); - LLVM_DEBUG(dbgs() << "Looking to fold " << BB->getName() << " into " - << Succ->getName() << "\n"); // Shortcut, if there is only a single predecessor it must be BB and merging // is always safe if (Succ->getSinglePredecessor()) @@ -935,22 +952,13 @@ static Value *selectIncomingValueForBlock(Value *OldVal, BasicBlock *BB, return OldVal; } -/// Create a map from block to value for the operands of a -/// given phi. +/// Update a block-to-value map with the non-undef values found in a phi. +/// Only blocks that are already in the map are updated. /// -/// This function initializes the map with UndefValue for all predecessors -/// in BBPreds, and then updates the map with concrete non-undef values -/// found in the PHI node. -/// -/// \param PN The phi we are collecting the map for. -/// \param BBPreds The list of all predecessor blocks to initialize with Undef. -/// \param IncomingValues [out] The map from block to value for this phi. +/// \param PN The phi we are collecting the values of. +/// \param IncomingValues [in,out] The map from block to value for this phi. static void gatherIncomingValuesToPhi(PHINode *PN, - const PredBlockVector &BBPreds, IncomingValueMap &IncomingValues) { - for (BasicBlock *Pred : BBPreds) - IncomingValues[Pred] = nullptr; - for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) { Value *V = PN->getIncomingValue(i); if (isa<UndefValue>(V)) @@ -963,6 +971,27 @@ static void gatherIncomingValuesToPhi(PHINode *PN, } } +/// If the incoming values of a phi at \p UndefOps are a mix of undef and +/// poison, replace the poison values with undef. +/// +/// \param PN The phi whose incoming values are replaced. +/// \param UndefOps The indices of incoming values that are undef or poison. +static void replacePoisonWithUndefIfMixed(PHINode *PN, + ArrayRef<unsigned> UndefOps) { + // If there are both undef and poison values incoming, then convert those + // values to undef. It is invalid to have different values for the same + // incoming block. + unsigned PoisonCount = count_if(UndefOps, [&](unsigned I) { + return isa<PoisonValue>(PN->getIncomingValue(I)); + }); + + if (PoisonCount == 0 || PoisonCount == UndefOps.size()) + return; + + for (unsigned I : UndefOps) + PN->setIncomingValue(I, UndefValue::get(PN->getType())); +} + /// Replace the incoming undef values to a phi with the values /// from a block-to-value map. /// @@ -995,16 +1024,7 @@ static void replaceUndefValuesInPhi(PHINode *PN, PN->setIncomingValue(i, It->second); } - // If there are both undef and poison values incoming, then convert those - // values to undef. It is invalid to have different values for the same - // incoming block. - unsigned PoisonCount = count_if(TrueUndefOps, [&](unsigned i) { - return isa<PoisonValue>(PN->getIncomingValue(i)); - }); - if (PoisonCount != 0 && PoisonCount != TrueUndefOps.size()) { - for (unsigned i : TrueUndefOps) - PN->setIncomingValue(i, UndefValue::get(PN->getType())); - } + replacePoisonWithUndefIfMixed(PN, TrueUndefOps); } // Only when they shares a single common predecessor, return true. @@ -1076,15 +1096,21 @@ static bool introduceTooManyPhiEntries(BasicBlock *BB, BasicBlock *Succ) { /// \param BBPreds The predecessors of BB. /// \param PN The phi that we are updating. /// \param CommonPred The common predecessor of BB and PN's BasicBlock +/// \param HasSharedPreds Whether BB and PN's BasicBlock may share a +/// predecessor. static void redirectValuesFromPredecessorsToPhi(BasicBlock *BB, const PredBlockVector &BBPreds, PHINode *PN, - BasicBlock *CommonPred) { + BasicBlock *CommonPred, + bool HasSharedPreds) { + assert((HasSharedPreds || !CommonPred) && "Unexpected CommonPred!"); Value *OldVal = PN->removeIncomingValue(BB, false); assert(OldVal && "No entry in PHI for Pred BB!"); // Map BBPreds to defined values or nullptr (representing undefined values). IncomingValueMap IncomingValues; + for (BasicBlock *Pred : BBPreds) + IncomingValues[Pred] = nullptr; // We are merging two blocks - BB, and the block containing PN - and // as a result we need to redirect edges from the predecessors of BB @@ -1094,8 +1120,11 @@ static void redirectValuesFromPredecessorsToPhi(BasicBlock *BB, // and where some of those common predecessors might have undef // values flowing into PN, we want to rewrite those values to be // consistent with the non-undef values. + // Without shared predecessors, PN has no incoming values from BBPreds yet. + if (HasSharedPreds) + gatherIncomingValuesToPhi(PN, IncomingValues); - gatherIncomingValuesToPhi(PN, BBPreds, IncomingValues); + SmallVector<unsigned> NewUndefOps; // If this incoming value is one of the PHI nodes in BB, the new entries // in the PHI node are the entries from the old PHI. @@ -1118,6 +1147,8 @@ static void redirectValuesFromPredecessorsToPhi(BasicBlock *BB, // And add a new incoming value for this predecessor for the // newly retargeted branch. + if (!HasSharedPreds && isa<UndefValue>(Selected)) + NewUndefOps.push_back(PN->getNumIncomingValues()); PN->addIncoming(Selected, PredBB); } if (CommonPred) @@ -1135,13 +1166,18 @@ static void redirectValuesFromPredecessorsToPhi(BasicBlock *BB, // And add a new incoming value for this predecessor for the // newly retargeted branch. + if (!HasSharedPreds && isa<UndefValue>(Selected)) + NewUndefOps.push_back(PN->getNumIncomingValues()); PN->addIncoming(Selected, PredBB); } if (CommonPred) PN->addIncoming(OldVal, BB); } - replaceUndefValuesInPhi(PN, IncomingValues); + if (HasSharedPreds) + replaceUndefValuesInPhi(PN, IncomingValues); + else + replacePoisonWithUndefIfMixed(PN, NewUndefOps); } bool llvm::TryToSimplifyUncondBranchFromEmptyBlock(BasicBlock *BB, @@ -1159,7 +1195,15 @@ bool llvm::TryToSimplifyUncondBranchFromEmptyBlock(BasicBlock *BB, // The single common predecessor of BB and Succ when BB cannot be killed BasicBlock *CommonPred = nullptr; - bool BBKillable = CanPropagatePredecessorsForPHIs(BB, Succ, BBPreds); + // Without a predecessor shared with Succ, folding BB can't create conflicting + // phi entries, so the checks below can be skipped. + bool HasSharedPreds = mayHaveSharedPredecessor(Succ, BBPreds); + + LLVM_DEBUG(dbgs() << "Looking to fold " << BB->getName() << " into " + << Succ->getName() << "\n"); + + bool BBKillable = + !HasSharedPreds || CanPropagatePredecessorsForPHIs(BB, Succ, BBPreds); // Even if we can not fold BB into Succ, we may be able to redirect the // predecessors of BB to Succ. @@ -1289,8 +1333,9 @@ bool llvm::TryToSimplifyUncondBranchFromEmptyBlock(BasicBlock *BB, // All predecessors of BB (except the common predecessor) will be moved to // Succ. Updates.reserve(Updates.size() + 2 * pred_size(BB) + 1); - SmallPtrSet<BasicBlock *, 16> SuccPreds(llvm::from_range, - predecessors(Succ)); + SmallPtrSet<BasicBlock *, 16> SuccPreds; + if (HasSharedPreds) + SuccPreds.insert_range(predecessors(Succ)); for (auto *PredOfBB : predecessors(BB)) { // Do not modify those common predecessors of BB and Succ if (!SuccPreds.contains(PredOfBB)) @@ -1319,7 +1364,8 @@ bool llvm::TryToSimplifyUncondBranchFromEmptyBlock(BasicBlock *BB, // Loop over all of the PHI nodes in the successor of BB. for (BasicBlock::iterator I = Succ->begin(); isa<PHINode>(I); ++I) { PHINode *PN = cast<PHINode>(I); - redirectValuesFromPredecessorsToPhi(BB, BBPreds, PN, CommonPred); + redirectValuesFromPredecessorsToPhi(BB, BBPreds, PN, CommonPred, + HasSharedPreds); } } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
