Skip to content

Indexing glam vectors: "Using pointers with OpPhi requires capability VariablePointers or VariablePointersStorageBuffer" #432

Open

Description

@Firestar99

Compiletest

// build-pass
// compile-flags: -C llvm-args=--disassemble

use spirv_std::glam::*;
use spirv_std::spirv;

#[spirv(compute(threads(32)))]
pub fn main(
    #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] input: &UVec4,
    #[spirv(descriptor_set = 0, binding = 1, storage_buffer)] output: &mut [u32],
) {
    // works
    // output[0] = input.x;
    // fails
    output[0] = input[0];
    // works
    // output[0] = unsafe { spirv_std::arch::vector_extract_dynamic(*input, 0) };
}

Error

error: error:0:0 - Using pointers with OpPhi requires capability VariablePointers or VariablePointersStorageBuffer
         %37 = OpPhi %_ptr_StorageBuffer_uint %33 %29 %34 %30 %35 %31 %36 %32
Full logs and disassembly

; SPIR-V
; Version: 1.5
; Generator: rspirv
; Bound: 44
OpCapability Shader
OpCapability VulkanMemoryModel
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %1 "main" %2 %3
OpExecutionMode %1 LocalSize 32 1 1
%4 = OpString "/home/firestar99/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/glam-0.30.5/src/u32/uvec4.rs"
%5 = OpString "$DIR/minify.rs"
OpSource Unknown 0 %4 "<removed>"
OpSource Unknown 0 %5 "<removed>"
OpName %2 "input"
OpName %3 "output"
OpDecorate %6 Block
OpMemberDecorate %6 0 Offset 0
OpDecorate %7 ArrayStride 4
OpDecorate %8 Block
OpMemberDecorate %8 0 Offset 0
OpDecorate %2 NonWritable
OpDecorate %2 Binding 0
OpDecorate %2 DescriptorSet 0
OpDecorate %3 Binding 1
OpDecorate %3 DescriptorSet 0
%9 = OpTypeInt 32 0
%10 = OpTypeVector %9 4
%6 = OpTypeStruct %10
%11 = OpTypePointer StorageBuffer %6
%7 = OpTypeRuntimeArray %9
%8 = OpTypeStruct %7
%12 = OpTypePointer StorageBuffer %8
%13 = OpTypeVoid
%14 = OpTypeFunction %13
%15 = OpTypePointer StorageBuffer %10
%2 = OpVariable  %11  StorageBuffer
%16 = OpConstant  %9  0
%17 = OpTypePointer StorageBuffer %7
%3 = OpVariable  %12  StorageBuffer
%18 = OpTypePointer StorageBuffer %9
%19 = OpConstant  %9  1
%20 = OpConstant  %9  2
%21 = OpConstant  %9  3
%22 = OpTypeBool
%1 = OpFunction  %13  None %14
%23 = OpLabel
OpLine %5 9 4
%24 = OpInBoundsAccessChain  %15  %2 %16
OpLine %5 10 4
%25 = OpInBoundsAccessChain  %17  %3 %16
%26 = OpArrayLength  %9  %3 0
OpNoLine
OpSelectionMerge %27 None
OpSwitch %16 %28 0 %29 1 %30 2 %31 3 %32
%28 = OpLabel
OpReturn
%29 = OpLabel
OpLine %4 2910 17
%33 = OpInBoundsAccessChain  %18  %24 %16
OpNoLine
OpBranch %27
%30 = OpLabel
OpLine %4 2911 17
%34 = OpInBoundsAccessChain  %18  %24 %19
OpNoLine
OpBranch %27
%31 = OpLabel
OpLine %4 2912 17
%35 = OpInBoundsAccessChain  %18  %24 %20
OpNoLine
OpBranch %27
%32 = OpLabel
OpLine %4 2913 17
%36 = OpInBoundsAccessChain  %18  %24 %21
OpNoLine
OpBranch %27
%27 = OpLabel
%37 = OpPhi  %18  %33 %29 %34 %30 %35 %31 %36 %32
OpLine %5 15 16
%38 = OpLoad  %9  %37
OpLine %5 15 4
%39 = OpULessThan  %22  %16 %26
OpNoLine
OpSelectionMerge %40 None
OpBranchConditional %39 %41 %42
%41 = OpLabel
OpBranch %40
%42 = OpLabel
OpReturn
%40 = OpLabel
OpLine %5 15 4
%43 = OpInBoundsAccessChain  %18  %25 %16
OpStore %43 %38
OpNoLine
OpReturn
OpFunctionEnd
error: error:0:0 - Using pointers with OpPhi requires capability VariablePointers or VariablePointersStorageBuffer
         %37 = OpPhi %_ptr_StorageBuffer_uint %33 %29 %34 %30 %35 %31 %36 %32
  |
  = note: spirv-val failed
  = note: module `$TEST_BUILD_DIR/arch/shared/minify.vulkan1.2`

error: aborting due to 1 previous error

Notes

Analysis

  • The assembly looks very much like this function:
impl Index<usize> for UVec4 {
    type Output = u32;
    #[inline]
    fn index(&self, index: usize) -> &Self::Output {
        match index {
            0 => &self.x,
            1 => &self.y,
            2 => &self.z,
            3 => &self.w,
            _ => panic!("index out of bounds"),
        }
    }
}
  • The function is inlined, but the returned &u32 is still forwarded as a pointer
  • The OpPhi is after the switch, merging the different branches all returning the &u32
  • The deref to u32 only happens afterwards

System Info

rust-gpu main dbf3737

More compiletests

vec

// build-pass

use spirv_std::arch::workgroup_memory_barrier_with_group_sync;
use spirv_std::glam::*;
use spirv_std::spirv;

#[spirv(compute(threads(32)))]
pub fn main(
    #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] input: &UVec4,
    #[spirv(descriptor_set = 0, binding = 1, storage_buffer)] output: &mut [u32],
    #[spirv(workgroup)] shared: &mut UVec4,
    #[spirv(local_invocation_index)] inv_id: UVec3,
) {
    unsafe {
        // copy from input to shared
        if (inv_id.x == 0) {
            *shared = *input;
        }
        workgroup_memory_barrier_with_group_sync();

        // accumulate random values
        let mut accum = 0;
        for i in 0..inv_id.x {
            accum += shared[i as usize % 4];
        }

        // write out
        output[inv_id.x as usize] = accum;
    }
}

BigStruct

// build-pass

use spirv_std::arch::workgroup_memory_barrier_with_group_sync;
use spirv_std::glam::*;
use spirv_std::spirv;

#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct MyStruct {
    pub a: Nested,
    pub b: Mat4,
    pub c: UVec2,
}

#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct Nested(pub f32);

#[spirv(compute(threads(32)))]
pub fn main(
    #[spirv(descriptor_set = 0, binding = 0, storage_buffer)] input: &MyStruct,
    #[spirv(descriptor_set = 0, binding = 1, storage_buffer)] output: &mut [MyStruct],
    #[spirv(workgroup)] shared: &mut MyStruct,
    #[spirv(local_invocation_index)] inv_id: UVec3,
) {
    unsafe {
        // copy from input to shared
        if (inv_id.x == 0) {
            *shared = *input;
        }
        workgroup_memory_barrier_with_group_sync();

        // accumulate random values
        let mut accum = 0.;
        accum += shared.a.0;
        accum += shared.c.x as f32;
        for axis_id in 0..4 {
            let axis = [shared.b.x_axis, shared.b.y_axis, shared.b.z_axis, shared.b.w_axis][axis_id];
            accum += axis[inv_id.x as usize % 4];
        }

        // write out
        output[inv_id.x as usize].a.0 = accum;
    }
}

Activity

  1. changed the title [-]shared memory: Using pointers with OpPhi requires capability VariablePointers or VariablePointersStorageBuffer[/-] [+]Indexing glam vectors: "Using pointers with OpPhi requires capability VariablePointers or VariablePointersStorageBuffer"[/+] on Oct 9, 2025
  2. nazar-pc commented on Oct 10, 2025

    @nazar-pc
    Contributor

    Just hit this as well with the following code:

    let table_positions = if level == 1 {
        table_6_positions
    } else if level == 2 {
        table_5_positions
    } else if level == 3 {
        table_4_positions
    } else if level == 4 {
        table_3_positions
    } else {
        table_2_positions
    };
    
    next_positions[j] = table_positions[parent_pos as usize][child];

    Rewriting it like this helped to bypass this issue:

    next_positions[j] = if level == 1 {
        table_6_positions[parent_pos as usize][child]
    } else if level == 2 {
        table_5_positions[parent_pos as usize][child]
    } else if level == 3 {
        table_4_positions[parent_pos as usize][child]
    } else if level == 4 {
        table_3_positions[parent_pos as usize][child]
    } else {
        table_2_positions[parent_pos as usize][child]
    };

    The variables look like this:

        table_2_positions: &[[u32; 2]; REDUCED_MATCHES_COUNT * NUM_MATCH_BUCKETS],
        table_3_positions: &[[u32; 2]; REDUCED_MATCHES_COUNT * NUM_MATCH_BUCKETS],
        table_4_positions: &[[u32; 2]; REDUCED_MATCHES_COUNT * NUM_MATCH_BUCKETS],
        table_5_positions: &[[u32; 2]; REDUCED_MATCHES_COUNT * NUM_MATCH_BUCKETS],
        table_6_positions: &[[u32; 2]; REDUCED_MATCHES_COUNT * NUM_MATCH_BUCKETS],
  3. Firestar99 commented on Oct 10, 2025

    @Firestar99
    MemberAuthor

    Since [u32; 2] is Copy, this should also work:

    next_positions[j] = if level == 1 {
        *table_6_positions[parent_pos as usize]
    } else if level == 2 {
        *table_5_positions[parent_pos as usize]
    } else if level == 3 {
        *table_4_positions[parent_pos as usize]
    } else if level == 4 {
        *table_3_positions[parent_pos as usize]
    } else {
        *table_2_positions[parent_pos as usize]
    }[child];

    And you can ofc also use match, rustc desugars it into ifs before we see it iirc.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions