Skip to content

Conversation

@sarnex
Copy link
Member

@sarnex sarnex commented Dec 15, 2025

This adds a legalization pass to convert zero size arrays to legal types for common cases. It doesn't handle all cases, but if we see real use cases for other cases, we can add them in the future.

For globals, and their initializers, we generally replace [0 x T] with ptr.

For instructions, we either replace [0 x T] with poision, for alloca we just allocate T.

This is motivated by IR generated by the OpenMP front end.

Issue: #170150

@github-actions
Copy link

github-actions bot commented Dec 15, 2025

✅ With the latest revision this PR passed the undef deprecator.

Signed-off-by: Nick Sarnie <nick.sarnie@intel.com>
@sarnex sarnex marked this pull request as ready for review December 15, 2025 22:33
@llvmbot
Copy link
Member

llvmbot commented Dec 15, 2025

@llvm/pr-subscribers-backend-spir-v

Author: Nick Sarnie (sarnex)

Changes

This adds a legalization pass to convert zero size arrays to legal types for common cases. It doesn't handle all cases, but if we see real use cases for it, we can add them in the future.

For globals, and their initializers, we generally replace [0 x T] with ptr.

For instructions, we either replace [0 x T] with poision, for alloca we just allocate T.

This is motivated by IR generated by the OpenMP front end.

Issue: #170150


Patch is 24.65 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/172367.diff

18 Files Affected:

  • (modified) llvm/lib/Target/SPIRV/CMakeLists.txt (+1)
  • (modified) llvm/lib/Target/SPIRV/SPIRV.h (+2)
  • (added) llvm/lib/Target/SPIRV/SPIRVLegalizeZeroSizeArrays.cpp (+334)
  • (added) llvm/lib/Target/SPIRV/SPIRVLegalizeZeroSizeArrays.h (+24)
  • (modified) llvm/lib/Target/SPIRV/SPIRVPassRegistry.def (+1)
  • (modified) llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp (+3)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-alloca-count.ll (+12)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-alloca.ll (+14)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-extractvalue.ll (+12)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-global.ll (+8)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-insertvalue.ll (+16)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-load.ll (+13)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-nested.ll (+8)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-select.ll (+12)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-store.ll (+13)
  • (added) llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-struct.ll (+11)
  • (modified) llvm/test/CodeGen/SPIRV/llc-pipeline.ll (+2)
  • (modified) llvm/test/CodeGen/SPIRV/zero-length-array.ll (+1-1)
diff --git a/llvm/lib/Target/SPIRV/CMakeLists.txt b/llvm/lib/Target/SPIRV/CMakeLists.txt
index 79b76165cd57a..bb8307c2d2d36 100644
--- a/llvm/lib/Target/SPIRV/CMakeLists.txt
+++ b/llvm/lib/Target/SPIRV/CMakeLists.txt
@@ -27,6 +27,7 @@ add_llvm_target(SPIRVCodeGen
   SPIRVInstrInfo.cpp
   SPIRVInstructionSelector.cpp
   SPIRVLegalizeImplicitBinding.cpp
+  SPIRVLegalizeZeroSizeArrays.cpp
   SPIRVStripConvergentIntrinsics.cpp
   SPIRVLegalizePointerCast.cpp
   SPIRVMergeRegionExitTargets.cpp
diff --git a/llvm/lib/Target/SPIRV/SPIRV.h b/llvm/lib/Target/SPIRV/SPIRV.h
index fa85ee781c249..b6188adfdb5b3 100644
--- a/llvm/lib/Target/SPIRV/SPIRV.h
+++ b/llvm/lib/Target/SPIRV/SPIRV.h
@@ -25,6 +25,7 @@ ModulePass *createSPIRVCBufferAccessLegacyPass();
 FunctionPass *createSPIRVMergeRegionExitTargetsPass();
 FunctionPass *createSPIRVStripConvergenceIntrinsicsPass();
 ModulePass *createSPIRVLegalizeImplicitBindingPass();
+ModulePass *createSPIRVLegalizeZeroSizeArraysPass();
 FunctionPass *createSPIRVLegalizePointerCastPass(SPIRVTargetMachine *TM);
 FunctionPass *createSPIRVRegularizerPass();
 FunctionPass *createSPIRVPreLegalizerCombiner();
@@ -55,6 +56,7 @@ void initializeSPIRVPrepareFunctionsPass(PassRegistry &);
 void initializeSPIRVPrepareGlobalsPass(PassRegistry &);
 void initializeSPIRVStripConvergentIntrinsicsPass(PassRegistry &);
 void initializeSPIRVLegalizeImplicitBindingPass(PassRegistry &);
+void initializeSPIRVLegalizeZeroSizeArraysLegacyPass(PassRegistry &);
 } // namespace llvm
 
 #endif // LLVM_LIB_TARGET_SPIRV_SPIRV_H
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeZeroSizeArrays.cpp b/llvm/lib/Target/SPIRV/SPIRVLegalizeZeroSizeArrays.cpp
new file mode 100644
index 0000000000000..a1eb6ca3f6aa3
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeZeroSizeArrays.cpp
@@ -0,0 +1,334 @@
+//===- SPIRVLegalizeZeroSizeArrays.cpp - Legalize zero-size arrays -------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// SPIR-V does not support zero-size arrays unless it is within a shader. This
+// pass legalizes zero-size arrays ([0 x T]) in unsupported cases.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SPIRVLegalizeZeroSizeArrays.h"
+#include "SPIRV.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/IR/IRBuilder.h"
+#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/InstVisitor.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/TargetParser/Triple.h"
+
+#define DEBUG_TYPE "spirv-legalize-zero-size-arrays"
+
+using namespace llvm;
+
+namespace {
+
+bool hasZeroSizeArray(const Type *Ty) {
+  if (const ArrayType *ArrTy = dyn_cast<ArrayType>(Ty)) {
+    if (ArrTy->getNumElements() == 0)
+      return true;
+    return hasZeroSizeArray(ArrTy->getElementType());
+  }
+
+  if (const StructType *StructTy = dyn_cast<StructType>(Ty)) {
+    for (Type *ElemTy : StructTy->elements()) {
+      if (hasZeroSizeArray(ElemTy))
+        return true;
+    }
+  }
+
+  return false;
+}
+
+class SPIRVLegalizeZeroSizeArraysImpl
+    : public InstVisitor<SPIRVLegalizeZeroSizeArraysImpl> {
+  friend class InstVisitor<SPIRVLegalizeZeroSizeArraysImpl>;
+
+public:
+  bool runOnModule(Module &M);
+
+  // TODO: Handle GEP, PHI
+  void visitAllocaInst(AllocaInst &AI);
+  void visitLoadInst(LoadInst &LI);
+  void visitStoreInst(StoreInst &SI);
+  void visitSelectInst(SelectInst &Sel);
+  void visitExtractValueInst(ExtractValueInst &EVI);
+  void visitInsertValueInst(InsertValueInst &IVI);
+
+private:
+  Type *legalizeType(Type *Ty);
+  Constant *legalizeConstant(Constant *C);
+
+  DenseMap<Type *, Type *> TypeMap;
+  DenseMap<GlobalVariable *, GlobalVariable *> GlobalMap;
+  SmallVector<Instruction *, 16> ToErase;
+  bool Modified = false;
+};
+
+class SPIRVLegalizeZeroSizeArraysLegacy : public ModulePass {
+public:
+  static char ID;
+  SPIRVLegalizeZeroSizeArraysLegacy() : ModulePass(ID) {}
+  StringRef getPassName() const override {
+    return "SPIRV Legalize Zero-Size Arrays";
+  }
+  bool runOnModule(Module &M) override {
+    SPIRVLegalizeZeroSizeArraysImpl Impl;
+    return Impl.runOnModule(M);
+  }
+};
+
+Type *SPIRVLegalizeZeroSizeArraysImpl::legalizeType(Type *Ty) {
+  auto It = TypeMap.find(Ty);
+  if (It != TypeMap.end())
+    return It->second;
+
+  Type *LegalizedTy = Ty;
+
+  if (ArrayType *ArrTy = dyn_cast<ArrayType>(Ty)) {
+    if (ArrTy->getNumElements() == 0) {
+      LegalizedTy = PointerType::getUnqual(Ty->getContext());
+    } else if (Type *ElemTy = legalizeType(ArrTy->getElementType());
+               ElemTy != ArrTy->getElementType()) {
+      LegalizedTy = ArrayType::get(ElemTy, ArrTy->getNumElements());
+    }
+  } else if (StructType *StructTy = dyn_cast<StructType>(Ty)) {
+    SmallVector<Type *, 8> ElemTypes;
+    bool Changed = false;
+    for (Type *ElemTy : StructTy->elements()) {
+      Type *LegalizedElemTy = legalizeType(ElemTy);
+      ElemTypes.push_back(LegalizedElemTy);
+      Changed |= LegalizedElemTy != ElemTy;
+    }
+    if (Changed) {
+      LegalizedTy =
+          StructTy->hasName()
+              ? StructType::create(StructTy->getContext(), ElemTypes,
+                                   (StructTy->getName() + ".legalized").str(),
+                                   StructTy->isPacked())
+              : StructType::get(StructTy->getContext(), ElemTypes,
+                                StructTy->isPacked());
+    }
+  }
+
+  TypeMap[Ty] = LegalizedTy;
+  return LegalizedTy;
+}
+
+Constant *SPIRVLegalizeZeroSizeArraysImpl::legalizeConstant(Constant *C) {
+  if (!C || !hasZeroSizeArray(C->getType()))
+    return C;
+
+  if (GlobalVariable *GV = dyn_cast<GlobalVariable>(C))
+    return GlobalMap.lookup(GV) ? GlobalMap[GV] : C;
+
+  Type *NewTy = legalizeType(C->getType());
+  if (isa<UndefValue>(C) || isa<PoisonValue>(C))
+    return PoisonValue::get(NewTy);
+  if (isa<ConstantAggregateZero>(C))
+    return Constant::getNullValue(NewTy);
+
+  if (ConstantArray *CA = dyn_cast<ConstantArray>(C)) {
+    SmallVector<Constant *, 8> Elems;
+    for (Use &U : CA->operands())
+      Elems.push_back(legalizeConstant(cast<Constant>(U)));
+    return ConstantArray::get(cast<ArrayType>(NewTy), Elems);
+  }
+
+  if (ConstantStruct *CS = dyn_cast<ConstantStruct>(C)) {
+    SmallVector<Constant *, 8> Fields;
+    for (Use &U : CS->operands())
+      Fields.push_back(legalizeConstant(cast<Constant>(U)));
+    return ConstantStruct::get(cast<StructType>(NewTy), Fields);
+  }
+
+  if (ConstantExpr *CE = dyn_cast<ConstantExpr>(C)) {
+    // Don't legalize GEP constant expressions, the backend deals with them
+    // fine.
+    if (CE->getOpcode() == Instruction::GetElementPtr)
+      return CE;
+    SmallVector<Constant *, 4> Ops;
+    bool Changed = false;
+    for (Use &U : CE->operands()) {
+      Constant *LegalizedOp = legalizeConstant(cast<Constant>(U));
+      Ops.push_back(LegalizedOp);
+      Changed |= LegalizedOp != cast<Constant>(U.get());
+    }
+    if (Changed)
+      return CE->getWithOperands(Ops);
+  }
+
+  return C;
+}
+
+void SPIRVLegalizeZeroSizeArraysImpl::visitAllocaInst(AllocaInst &AI) {
+  if (!hasZeroSizeArray(AI.getAllocatedType()))
+    return;
+
+  // TODO: Handle nested arrays and structs containing zero-size arrays
+  ArrayType *ArrTy = dyn_cast<ArrayType>(AI.getAllocatedType());
+  if (ArrTy && ArrTy->getNumElements() == 0) {
+    IRBuilder<> Builder(&AI);
+    AllocaInst *NewAI = Builder.CreateAlloca(ArrTy->getElementType(),
+                                             AI.getArraySize(), AI.getName());
+    NewAI->setAlignment(AI.getAlign());
+    NewAI->setDebugLoc(AI.getDebugLoc());
+    AI.replaceAllUsesWith(NewAI);
+    ToErase.push_back(&AI);
+    Modified = true;
+  }
+}
+
+void SPIRVLegalizeZeroSizeArraysImpl::visitLoadInst(LoadInst &LI) {
+  if (!hasZeroSizeArray(LI.getType()))
+    return;
+
+  // TODO: Handle nested arrays and structs containing zero-size arrays
+  ArrayType *ArrTy = dyn_cast<ArrayType>(LI.getType());
+  if (ArrTy && ArrTy->getNumElements() == 0) {
+    LI.replaceAllUsesWith(PoisonValue::get(LI.getType()));
+    ToErase.push_back(&LI);
+    Modified = true;
+  }
+}
+
+void SPIRVLegalizeZeroSizeArraysImpl::visitStoreInst(StoreInst &SI) {
+  Type *StoreTy = SI.getValueOperand()->getType();
+
+  // TODO: Handle nested arrays and structs containing zero-size arrays
+  ArrayType *ArrTy = dyn_cast<ArrayType>(StoreTy);
+  if (ArrTy && ArrTy->getNumElements() == 0) {
+    ToErase.push_back(&SI);
+    Modified = true;
+  }
+}
+
+void SPIRVLegalizeZeroSizeArraysImpl::visitSelectInst(SelectInst &Sel) {
+  if (!hasZeroSizeArray(Sel.getType()))
+    return;
+
+  // TODO: Handle nested arrays and structs containing zero-size arrays
+  ArrayType *ArrTy = dyn_cast<ArrayType>(Sel.getType());
+  if (ArrTy && ArrTy->getNumElements() == 0) {
+    Sel.replaceAllUsesWith(PoisonValue::get(Sel.getType()));
+    ToErase.push_back(&Sel);
+    Modified = true;
+  }
+}
+
+void SPIRVLegalizeZeroSizeArraysImpl::visitExtractValueInst(
+    ExtractValueInst &EVI) {
+  if (!hasZeroSizeArray(EVI.getAggregateOperand()->getType()))
+    return;
+
+  // TODO: Handle nested arrays and structs containing zero-size arrays
+  ArrayType *ArrTy = dyn_cast<ArrayType>(EVI.getType());
+  if (ArrTy && ArrTy->getNumElements() == 0) {
+    EVI.replaceAllUsesWith(PoisonValue::get(EVI.getType()));
+    ToErase.push_back(&EVI);
+    Modified = true;
+  }
+}
+
+void SPIRVLegalizeZeroSizeArraysImpl::visitInsertValueInst(
+    InsertValueInst &IVI) {
+  if (!hasZeroSizeArray(IVI.getAggregateOperand()->getType()))
+    return;
+
+  // TODO: Handle nested arrays and structs containing zero-size arrays
+  ArrayType *ArrTy =
+      dyn_cast<ArrayType>(IVI.getInsertedValueOperand()->getType());
+  if (ArrTy && ArrTy->getNumElements() == 0) {
+    IVI.replaceAllUsesWith(IVI.getAggregateOperand());
+    ToErase.push_back(&IVI);
+    Modified = true;
+  }
+}
+
+bool SPIRVLegalizeZeroSizeArraysImpl::runOnModule(Module &M) {
+  TypeMap.clear();
+  GlobalMap.clear();
+  ToErase.clear();
+  Modified = false;
+
+  // Runtime arrays are allowed for shaders, so we don't need to do anything.
+  Triple Triple(M.getTargetTriple());
+  if (Triple.getOS() == Triple::Vulkan)
+    return false;
+
+  // First pass: create new globals and track mapping (don't erase old ones
+  // yet).
+  SmallVector<GlobalVariable *, 8> OldGlobals;
+  for (GlobalVariable &GV : M.globals()) {
+    if (!hasZeroSizeArray(GV.getValueType()))
+      continue;
+
+    // llvm.embedded.module is handled by SPIRVPrepareGlobals
+    if (GV.getName() == "llvm.embedded.module")
+      continue;
+
+    Type *NewTy = legalizeType(GV.getValueType());
+    // Use an empty name and initializer for now, we will update them in the
+    // following steps.
+    GlobalVariable *NewGV = new GlobalVariable(
+        M, NewTy, GV.isConstant(), GV.getLinkage(), /*Initializer=*/nullptr,
+        /*Name=*/"", &GV, GV.getThreadLocalMode(), GV.getAddressSpace(),
+        GV.isExternallyInitialized());
+    NewGV->copyAttributesFrom(&GV);
+    NewGV->copyMetadata(&GV, 0);
+    NewGV->setComdat(GV.getComdat());
+    NewGV->setAlignment(GV.getAlign());
+    GlobalMap[&GV] = NewGV;
+    OldGlobals.push_back(&GV);
+    Modified = true;
+  }
+
+  // Second pass: set initializers now that all globals are mapped.
+  for (GlobalVariable *GV : OldGlobals) {
+    GlobalVariable *NewGV = cast<GlobalVariable>(GlobalMap[GV]);
+    if (GV->hasInitializer())
+      NewGV->setInitializer(legalizeConstant(GV->getInitializer()));
+  }
+
+  // Third pass: replace uses, transfer names, and erase old globals.
+  for (GlobalVariable *GV : OldGlobals) {
+    GlobalVariable *NewGV = GlobalMap[GV];
+    GV->replaceAllUsesWith(ConstantExpr::getBitCast(NewGV, GV->getType()));
+    NewGV->takeName(GV);
+    GV->eraseFromParent();
+  }
+
+  for (Function &F : M)
+    for (Instruction &I : instructions(F))
+      visit(I);
+
+  for (Instruction *I : ToErase)
+    I->eraseFromParent();
+
+  return Modified;
+}
+
+} // namespace
+
+PreservedAnalyses SPIRVLegalizeZeroSizeArrays::run(Module &M,
+                                                   ModuleAnalysisManager &AM) {
+  SPIRVLegalizeZeroSizeArraysImpl Impl;
+  if (Impl.runOnModule(M))
+    return PreservedAnalyses::none();
+  return PreservedAnalyses::all();
+}
+
+char SPIRVLegalizeZeroSizeArraysLegacy::ID = 0;
+
+INITIALIZE_PASS(SPIRVLegalizeZeroSizeArraysLegacy,
+                "spirv-legalize-zero-size-arrays",
+                "Legalize SPIR-V zero-size arrays", false, false)
+
+ModulePass *llvm::createSPIRVLegalizeZeroSizeArraysPass() {
+  return new SPIRVLegalizeZeroSizeArraysLegacy();
+}
diff --git a/llvm/lib/Target/SPIRV/SPIRVLegalizeZeroSizeArrays.h b/llvm/lib/Target/SPIRV/SPIRVLegalizeZeroSizeArrays.h
new file mode 100644
index 0000000000000..7fe653c4a0d78
--- /dev/null
+++ b/llvm/lib/Target/SPIRV/SPIRVLegalizeZeroSizeArrays.h
@@ -0,0 +1,24 @@
+//===- SPIRVLegalizeZeroSizeArrays.h - Legalize zero-size arrays *- C++ -*-===//
+//
+// 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_SPIRV_SPIRVLEGALIZEZEROSIZE_ARRAYS_H_
+#define LLVM_LIB_TARGET_SPIRV_SPIRVLEGALIZEZEROSIZE_ARRAYS_H_
+
+#include "llvm/IR/PassManager.h"
+
+namespace llvm {
+
+class SPIRVLegalizeZeroSizeArrays
+    : public PassInfoMixin<SPIRVLegalizeZeroSizeArrays> {
+public:
+  PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
+};
+
+} // namespace llvm
+
+#endif // LLVM_LIB_TARGET_SPIRV_SPIRVLEGALIZEZEROSIZE_ARRAYS_H_
diff --git a/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def b/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
index 1ce131fe7b1bf..90dac17ec54b2 100644
--- a/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
+++ b/llvm/lib/Target/SPIRV/SPIRVPassRegistry.def
@@ -17,6 +17,7 @@
 #define MODULE_PASS(NAME, CREATE_PASS)
 #endif
 MODULE_PASS("spirv-cbuffer-access", SPIRVCBufferAccess())
+MODULE_PASS("spirv-legalize-zero-size-arrays", SPIRVLegalizeZeroSizeArrays())
 #undef MODULE_PASS
 
 #ifndef FUNCTION_PASS
diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index 10bbca225b20a..86bef3255f532 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -14,6 +14,7 @@
 #include "SPIRV.h"
 #include "SPIRVCBufferAccess.h"
 #include "SPIRVGlobalRegistry.h"
+#include "SPIRVLegalizeZeroSizeArrays.h"
 #include "SPIRVLegalizerInfo.h"
 #include "SPIRVStructurizerWrapper.h"
 #include "SPIRVTargetObjectFile.h"
@@ -52,6 +53,7 @@ extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTarget() {
   initializeSPIRVCBufferAccessLegacyPass(PR);
   initializeSPIRVPreLegalizerCombinerPass(PR);
   initializeSPIRVLegalizePointerCastPass(PR);
+  initializeSPIRVLegalizeZeroSizeArraysLegacyPass(PR);
   initializeSPIRVRegularizerPass(PR);
   initializeSPIRVPreLegalizerPass(PR);
   initializeSPIRVPostLegalizerPass(PR);
@@ -210,6 +212,7 @@ void SPIRVPassConfig::addISelPrepare() {
 
   addPass(createSPIRVStripConvergenceIntrinsicsPass());
   addPass(createSPIRVLegalizeImplicitBindingPass());
+  addPass(createSPIRVLegalizeZeroSizeArraysPass());
   addPass(createSPIRVCBufferAccessLegacyPass());
   addPass(createSPIRVEmitIntrinsicsPass(&getTM<SPIRVTargetMachine>()));
   if (TM.getSubtargetImpl()->isLogicalSPIRV())
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-alloca-count.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-alloca-count.ll
new file mode 100644
index 0000000000000..2d9aa12fce8d1
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-alloca-count.ll
@@ -0,0 +1,12 @@
+; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown -spirv-ext=+SPV_INTEL_variable_length_array %s -o - -filetype=obj | spirv-val %}
+
+; Test that zero-size array alloca with dynamic count allocates element type with count
+
+define void @test_alloca_with_count(i32 %n) {
+; CHECK-LABEL: @test_alloca_with_count(
+; CHECK-NEXT:    [[ARR:%.*]] = alloca i32, i32 [[N:%.*]], align 4
+; CHECK-NEXT:    ret void
+  %arr = alloca [0 x i32], i32 %n, align 4
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-alloca.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-alloca.ll
new file mode 100644
index 0000000000000..58d6453a71c7e
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-alloca.ll
@@ -0,0 +1,14 @@
+; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Test that alloca of zero-size array allocates element type instead
+
+define void @test_alloca_zero_array() {
+; CHECK-LABEL: @test_alloca_zero_array(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[ARR:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    ret void
+entry:
+  %arr = alloca [0 x i32], align 4
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-extractvalue.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-extractvalue.ll
new file mode 100644
index 0000000000000..b1e7968c7603d
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-extractvalue.ll
@@ -0,0 +1,12 @@
+; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
+
+; Test that extractvalue of zero-size array is replaced with poison
+
+; Can't run spirv-val as function signatures aren't handled
+
+define [0 x i32] @test_extractvalue_zero_array() {
+; CHECK-LABEL: @test_extractvalue_zero_array(
+; CHECK-NEXT:    ret [0 x i32] poison
+  %arr = extractvalue [1 x [0 x i32]] zeroinitializer, 0
+  ret [0 x i32] %arr
+}
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-global.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-global.ll
new file mode 100644
index 0000000000000..e90478d4c86d6
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-global.ll
@@ -0,0 +1,8 @@
+; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}
+
+; Test that a global variable with zero-size array is transformed to ptr type
+
+@global_zero_array = global [0 x i32] zeroinitializer
+
+; CHECK: @global_zero_array = global ptr null
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-insertvalue.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-insertvalue.ll
new file mode 100644
index 0000000000000..b20b84190b092
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-insertvalue.ll
@@ -0,0 +1,16 @@
+; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
+
+; Test that insertvalue of zero-size array is removed
+
+%struct.with_zero = type { i32, [0 x i32], i64 }
+
+define void @test_insertvalue_zero_array(ptr %ptr, %struct.with_zero %s) {
+; CHECK-LABEL: @test_insertvalue_zero_array(
+; CHECK-NEXT:    [[AGG:%.*]] = insertvalue %struct.with_zero %s, i32 42, 0
+; CHECK-NEXT:    store %struct.with_zero [[AGG]], ptr %ptr
+; CHECK-NEXT:    ret void
+  %agg = insertvalue %struct.with_zero %s, i32 42, 0
+  %result = insertvalue %struct.with_zero %agg, [0 x i32] zeroinitializer, 1
+  store %struct.with_zero %result, ptr %ptr
+  ret void
+}
diff --git a/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-load.ll b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-load.ll
new file mode 100644
index 0000000000000..4209fc27d579d
--- /dev/null
+++ b/llvm/test/CodeGen/SPIRV/legalize-zero-size-arrays-load.ll
@@ -0,0 +1,13 @@
+; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
+; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unk...
[truncated]

@jmmartinez
Copy link
Contributor

Hello,
Just to get the full picture, what is the code that generates a [0 x T] alloca? What does it represent?

Could you share a simple source code and compile command to play with?

Copy link
Contributor

@jmmartinez jmmartinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some minor comments. I'll do a deeper read later.

DenseMap<Type *, Type *> TypeMap;
DenseMap<GlobalVariable *, GlobalVariable *> GlobalMap;
SmallVector<Instruction *, 16> ToErase;
bool Modified = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't fully checked the code but I think !ToErase.empty() == Modified


if (ArrayType *ArrTy = dyn_cast<ArrayType>(Ty)) {
if (ArrTy->getNumElements() == 0) {
LegalizedTy = PointerType::getUnqual(Ty->getContext());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unqual gives you a pointer to function in SPIRV. I think that you'd rather want a pointer to generic (AS 4)

Comment on lines +127 to +128
if (GlobalVariable *GV = dyn_cast<GlobalVariable>(C))
return GlobalMap.lookup(GV) ? GlobalMap[GV] : C;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid doing 2 lookups.

Suggested change
if (GlobalVariable *GV = dyn_cast<GlobalVariable>(C))
return GlobalMap.lookup(GV) ? GlobalMap[GV] : C;
if (GlobalVariable *GV = dyn_cast<GlobalVariable>(C)) {
if(GlobalVariable *NewGV = GlobalMap.lookup(GV))
return NewGV;
return C;
}

return GlobalMap.lookup(GV) ? GlobalMap[GV] : C;

Type *NewTy = legalizeType(C->getType());
if (isa<UndefValue>(C) || isa<PoisonValue>(C))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PoisonValue is an instance of UndefValue.

Suggested change
if (isa<UndefValue>(C) || isa<PoisonValue>(C))
if (isa<UndefValue>(C))

bool Modified = false;
};

class SPIRVLegalizeZeroSizeArraysLegacy : public ModulePass {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why Legacy?


// TODO: Handle nested arrays and structs containing zero-size arrays
ArrayType *ArrTy = dyn_cast<ArrayType>(AI.getAllocatedType());
if (ArrTy && ArrTy->getNumElements() == 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this check redundant with the early return?

Modified = false;

// Runtime arrays are allowed for shaders, so we don't need to do anything.
Triple Triple(M.getTargetTriple());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!hasZeroSizeArray(GV.getValueType()))
continue;

// llvm.embedded.module is handled by SPIRVPrepareGlobals
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// llvm.embedded.module is handled by SPIRVPrepareGlobals
// llvm.embedded.module is handled by SPIRVPrepareGlobals.

; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown -spirv-ext=+SPV_INTEL_variable_length_array %s -o - -filetype=obj | spirv-val %}

; Test that zero-size array alloca with dynamic count allocates element type with count
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
; Test that zero-size array alloca with dynamic count allocates element type with count
; Test that zero-size array alloca with dynamic count allocates element type with count.

; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}

; Test that nested zero-size arrays are legalized to pointers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
; Test that nested zero-size arrays are legalized to pointers
; Test that nested zero-size arrays are legalized to pointers.

@@ -0,0 +1,12 @@
; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s

; Test that select of zero-size array is replaced with poison
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
; Test that select of zero-size array is replaced with poison
; Test that select of zero-size array is replaced with poison.


; Test that select of zero-size array is replaced with poison

; Can't run spirv-val as function signatures are not handled
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, add the line as RUNx or at least TODO.

; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}

; Test that store of zero-size array is removed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
; Test that store of zero-size array is removed
; Test that store of zero-size array is removed.

; RUN: opt -S -passes=spirv-legalize-zero-size-arrays -mtriple=spirv64-unknown-unknown < %s | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %}

; Test that struct with zero-size array field becomes pointer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
; Test that struct with zero-size array field becomes pointer
; Test that struct with zero-size array field becomes pointer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants