From 71688f993b52c6fc2d717298e346f92dd47d1402 Mon Sep 17 00:00:00 2001 From: Tim Besard Date: Fri, 9 Oct 2026 13:55:56 +0200 Subject: [PATCH] Don't promote PromoteSubByte's own replacement instructions PromoteSubByte visits the module with an InstVisitor while promoting it. Promoting an instruction can promote instructions further down its block (a PHI node's back-edge value, for example), inserting their replacements ahead of the visitor. The visitor then promotes those replacements again, which records them in the promotion cache, and cleanUp() erases them while other replacements still use them. Their users end up with undef operands: a loop that carries an i1 through a PHI node loses the value. Visit only the instructions that existed before promotion started. Signed-off-by: Tim Besard --- .../preprocess_spvir/PromoteSubByte.cpp | 11 ++++- .../i1/phinodes-loop-carried.ll | 49 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 IGC/Compiler/tests/PromoteSubByte/i1/phinodes-loop-carried.ll diff --git a/IGC/AdaptorOCL/preprocess_spvir/PromoteSubByte.cpp b/IGC/AdaptorOCL/preprocess_spvir/PromoteSubByte.cpp index d7325fa1303f..65f24a7adb90 100644 --- a/IGC/AdaptorOCL/preprocess_spvir/PromoteSubByte.cpp +++ b/IGC/AdaptorOCL/preprocess_spvir/PromoteSubByte.cpp @@ -97,7 +97,16 @@ bool PromoteSubByte::run(Module &module) { changed |= getOrCreatePromotedValue(&globalVariable) != &globalVariable; } - visit(module); + // Promoting an instruction can recursively promote instructions further down in its block + // (through PHI nodes) and insert their replacements there. Visit only the instructions that + // existed before promotion: visiting a replacement would promote it again and make cleanUp() + // erase it while it still has users. + SmallVector originalInstructions; + for (auto &F : module) + for (auto &I : llvm::instructions(F)) + originalInstructions.push_back(&I); + for (auto *I : originalInstructions) + visit(*I); while (!promotionQueue.empty()) { auto value = promotionQueue.front(); diff --git a/IGC/Compiler/tests/PromoteSubByte/i1/phinodes-loop-carried.ll b/IGC/Compiler/tests/PromoteSubByte/i1/phinodes-loop-carried.ll new file mode 100644 index 000000000000..f43fab2a29df --- /dev/null +++ b/IGC/Compiler/tests/PromoteSubByte/i1/phinodes-loop-carried.ll @@ -0,0 +1,49 @@ +;=========================== begin_copyright_notice ============================ +; +; Copyright (C) 2026 Intel Corporation +; +; SPDX-License-Identifier: MIT +; +;============================ end_copyright_notice ============================= + +; REQUIRES: llvm-16-plus +; RUN: igc_opt --opaque-pointers -igc-promote-sub-byte -S %s -o %t.ll +; RUN: FileCheck %s --input-file=%t.ll + +; Visiting %u promotes the PHI node %p, which promotes its back-edge value %v further down the +; block. The replacement of %v must not be promoted again when the visitor reaches it, or +; cleanUp() erases it and leaves its user %w with an undefined operand. + +; CHECK-LABEL: define void @loop_carried_i1( +; CHECK: loop: +; CHECK: [[P:%.*]] = phi i8 [ 1, %entry ], [ [[V8:%.*]], %loop ] +; CHECK: [[U:%.*]] = icmp ne i8 [[P]], 1 +; CHECK: [[U8:%.*]] = zext i1 [[U]] to i8 +; CHECK: [[V:%.*]] = icmp ne i8 %x, 0 +; CHECK: [[V8]] = zext i1 [[V]] to i8 +; CHECK: [[W:%.*]] = icmp ne i8 [[V8]], [[U8]] +; CHECK-NOT: undef +; CHECK-NOT: poison +; CHECK: ret void + +define void @loop_carried_i1(ptr %out, ptr %in, i64 %n) { +entry: + br label %loop + +loop: + %i = phi i64 [ 0, %entry ], [ %i.next, %loop ] + %p = phi i1 [ true, %entry ], [ %v, %loop ] + %u = icmp ne i1 %p, true + %ptr = getelementptr i8, ptr %in, i64 %i + %x = load i8, ptr %ptr + %v = icmp ne i8 %x, 0 + %w = icmp ne i1 %v, %u + %i.next = add i64 %i, 1 + %done = icmp eq i64 %i.next, %n + br i1 %done, label %exit, label %loop + +exit: + %r = zext i1 %w to i8 + store i8 %r, ptr %out + ret void +}