Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 39 additions & 8 deletions IGC/Compiler/CISACodeGen/LowerGEPForPrivMem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1122,9 +1122,10 @@ std::pair<unsigned int, Type *> TransposeHelper::getArrSizeAndEltType(Type *T) {
return std::make_pair(arr_sz, retTy);
}

Type *TransposeHelper::getFirstNonScalarSourceElementType(const GetElementPtrInst &GEP) {
Type *TransposeHelper::getFirstNonScalarSourceElementType(const GetElementPtrInst &GEP,
bool ignoreSourceElementType) {
Type *currTy = GEP.getSourceElementType();
if (getArrSizeAndEltType(currTy).first > 1)
if (!ignoreSourceElementType && getArrSizeAndEltType(currTy).first > 1)
return currTy;

const Value *base = GEP.getPointerOperand()->stripPointerCasts();
Expand Down Expand Up @@ -1161,22 +1162,52 @@ void TransposeHelper::handleGEPInst(llvm::GetElementPtrInst *pGEP, llvm::Value *
IRBuilder<> IRB(pGEP);
Value *pScalarizedIdx = IRB.getInt32(0);

// If the GEP is on i8, its index is a byte offset and must be converted to an element index of the underlying base
// type.
if (pGEP->getSourceElementType()->isIntegerTy(8)) {
// A GEP indexes bytes when the innermost scalar of its source element type is i8: either
// bare i8, or an (possibly nested) array of i8 as produced by LLVM's canonicalization of
// `gep T, ptr, %i` into `gep [sizeof(T) x i8], ptr, %i`. Its accumulated offset is a byte
// offset and must be converted to an element index of the underlying base type.
Type *gepSrcTy = pGEP->getSourceElementType();
Type *gepInnermostTy = gepSrcTy;
while (gepInnermostTy->isArrayTy())
gepInnermostTy = gepInnermostTy->getArrayElementType();

if (gepInnermostTy->isIntegerTy(8)) {
const bool isByteArrayGEP = !gepSrcTy->isIntegerTy(8);
uint32_t elementBytes = m_idxUnitBytes;
// if elementBytes is 0, it means that scalarized index counts innermost scalas
if (elementBytes == 0) {
Type *elementTy = getFirstNonScalarSourceElementType(*pGEP);
Type *elementTy = getFirstNonScalarSourceElementType(*pGEP, isByteArrayGEP);
while (elementTy->isStructTy() || elementTy->isArrayTy() || elementTy->isVectorTy()) {
elementTy = getArrSizeAndEltType(elementTy).second;
}
elementTy = elementTy->getScalarType();
elementBytes = (uint32_t)m_DL.getTypeAllocSize(elementTy);
}

// The 1st operand is the byte offset, convert bytes to element count.
Value *byteIndex = IRB.CreateZExtOrTrunc(pGEP->getOperand(1), IRB.getInt32Ty());
// Accumulate the GEP's byte offset. Since the innermost element occupies one byte, the
// aggregate walk yields bytes directly: `gep [4 x i8], ptr, %i` gives %i * 4. For bare
// i8 the walk degenerates to the single index operand.
Value *byteIndex = IRB.getInt32(0);
Type *ByteTy = gepSrcTy;
for (unsigned i = 0, e = pGEP->getNumIndices(); i < e; ++i) {
auto GepOpnd = IRB.CreateZExtOrTrunc(pGEP->getOperand(i + 1), IRB.getInt32Ty());
auto [arr_sz, eltTy] = getArrSizeAndEltType(ByteTy);

byteIndex = IRB.CreateAdd(byteIndex, GepOpnd);
byteIndex = IRB.CreateMul(byteIndex, IRB.getInt32(arr_sz));

ByteTy = eltTy;
}
// Fewer indices than nesting levels: the remaining dimensions still scale the offset.
while (ByteTy->isArrayTy()) {
auto [arr_sz, eltTy] = getArrSizeAndEltType(ByteTy);

byteIndex = IRB.CreateMul(byteIndex, IRB.getInt32(arr_sz));

ByteTy = eltTy;
}

// Convert bytes to element count.
if (elementBytes > 1)
byteIndex = IRB.CreateUDiv(byteIndex, IRB.getInt32(elementBytes));

Expand Down
6 changes: 5 additions & 1 deletion IGC/Compiler/CISACodeGen/LowerGEPForPrivMem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ class TransposeHelper {
private:
bool m_vectorIndex;
std::pair<unsigned int, llvm::Type *> getArrSizeAndEltType(llvm::Type *T);
llvm::Type *getFirstNonScalarSourceElementType(const llvm::GetElementPtrInst &GEP);
// \p ignoreSourceElementType skips the GEP's own source element type and recovers the
// type from the base object instead. A byte-indexing GEP's source element type says
// nothing about the element size of the object it indexes into.
llvm::Type *getFirstNonScalarSourceElementType(const llvm::GetElementPtrInst &GEP,
bool ignoreSourceElementType = false);
};
} // namespace IGC
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
;=========================== begin_copyright_notice ============================
;
; Copyright (C) 2026 Intel Corporation
;
; SPDX-License-Identifier: MIT
;
;============================ end_copyright_notice =============================

; RUN: igc_opt --opaque-pointers --igc-private-mem-resolution --platformbmg -S %s | FileCheck %s

; A GEP whose source element type is an array of i8 carries a byte offset, exactly
; like a GEP on bare i8: LLVM canonicalizes `gep T, ptr, %i` into
; `gep [sizeof(T) x i8], ptr, %i`. Here the offset is 3 * 4 = 12 bytes into an
; alloca of i32 lanes, so the scalarized index must be 12 / 4 = 3 lanes, not 12.

; CHECK: mul i32 %{{.*}}, 4
; CHECK-NOT: mul i32 12,
; CHECK: mul i32 3,

define spir_kernel void @test() {
%a = alloca [8 x i32], align 4
%p = getelementptr inbounds [4 x i8], ptr %a, i64 3
%q = getelementptr inbounds i32, ptr %p, i64 0
%v = load i32, ptr %q, align 4
ret void
}

!igc.functions = !{!1}
!1 = !{ptr @test, !2}
!2 = !{!3}
!3 = !{!"function_type", i32 0}