ziglang/zig
259
6.0k
Fork
You've already forked zig
772

Extend @Vector for SPIR-V #35376

Open
opened 2026年05月21日 06:56:04 +02:00 by MasonRemaley · 28 comments

We want to improve support for authoring SPIR-V shaders in Zig. The accepted proposal #32032 suggests adding vec2, vec3, and vec4 types to the language to support this goal. This proposal is my suggestion for how exactly we do that.

Happy to work on the implementation if accepted.

Problem Statement

SPIR-V supports vectors of two or more scalars, as well as a number of instructions that operate on vectors.

Popular languages that target SPIR-V (GLSL, HLSL, and Slang) have language level support for vectors. This allows programmers to write readable code that lowers to vector instructions. Shader authors lean heavily on this functionality.

IIUC these instructions typically end up scalarized when compiled by the graphics driver from SPIR-V to the ISA for a particular GPU (e.g. RDNA4). However, playing nice with the IR keeps shaders small, likely makes the optimizer's job easier, and gives us an easy path forward on the accepted proposal to add a matrix type to the language since these types will need to interact with vectors.

(Relying on the optimizer to vectorize scalar code is probably not ideal here, as an example Khronos' SPIR-V optimizer fails to autovecotrize scalar code in seemingly easy cases. I am not criticizing their implementation, seemingly easy does not necessarily mean actually easy.)

Additionally, by providing a built-in way for users to access vector functionality:

  1. One of the main motivations for asking for operator overloading is resolved
  2. Libraries with public APIs using vectors don't have to agree on a math library

Point 2. comes up a lot in game dev libraries. I have about 20 different Zig game dev libraries, I counted and about half of them want to use vectors in their public APIs, often just for a vec2. Each of those needs to make the tough decision of whether to accept vectors as [2]f32, or bite the bullet and pull in my whole math library to get a type with field names.

Proposal

Zig's SPIR-V backend already compiles @Vector(...) types to SPIR-V vectors today. This status quo will continue to serve us well when we add a matrix type to the language, as LLVM represents matrices as vectors.

I propose that instead of adding new types for vectors, we extend the existing vector type with two goals:

  1. Make it easier to lower to optimal SPIR-V operations
  2. Make it easier to read and write the type of vectorized code often seen in shaders, whether targeting a CPU or a GPU

This means that we would reject gh/7305 rename SIMD type from @Vector to @Simd since we would be embracing SPIR-V as a use case for this type, and SPIR-V vectors are not SIMD vectors. Additionally, as a side effect of this proposal, the builtin vectors will become usable as general purpose vectors, so it is not as important that we distinguish between these use cases.

That being said, this proposal is only suggesting that we add operations actually available on targets we support. Higher level operations like quaternion multiplication are out of scope and can easily be provided by users in library code.

New Functionality

@Vector Takes A Layout Argument

Currently, @Vector is sized and aligned for aligned loads into SIMD registers. We don't recommend that users store these types long term, since this results in space wasted to padding. Instead, we suggest that they coerce vectors to arrays for this purpose.

From a processing perspective, this is the best of both worlds: tightly packed data in long term storage, aligned data in the hot loop.

As vectors gain support for field access and swizzles, there's increased temptation to store them long term to gain access to convenient syntax. Rather than shoo people away from using these convenient features, we should accept this use case as valid by adding a layout argument to the @Vector builtin:

@Vector(len:comptime_int,Element:type,layout:enum{auto,array})type

The auto layout is the current layout. It tells the compiler to pick the most efficient layout for vector operations on the target, and to pass the vector via SIMD registers to functions when possible.

The array layout tells the compiler to layout and pass the vector like an array of its components.

In GLSL (and I believe HLSL/Slang), vectors may or may not have extra padding depending on the struct layout (scalar, std140, std430, etc). This is essentially a more flexible version of that feature.

Note that while this makes declaring vector types more verbose, vector types were already very verbose if you used the builtin every time you needed to name them. We can suggest that users alias frequently used vector types under short names for themselves:

constvec2=@Vector(2,f32,.array);constvec3=@Vector(3,f32,.array);constvec4=@Vector(4,f32,.array);constsimd2=@Vector(2,f32,.auto);constsimd3=@Vector(3,f32,.auto);constsimd4=@Vector(4,f32,.auto);

Vector Length

We should accept gh/#17886 add .len to vector values.

Component Access

In GLSL, the components of a vec4 v can be accessed in the following ways:

  • v.x v.y, v.z, v.w
  • v.r v.g, v.b, v.a
  • v.s v.t, v.p, v.q (rarely used)
  • v[0] v[1], v[2], v[3]

In HLSL and Slang, the components of a floatx4 v can be accessed in the following ways:

  • v.x v.y, v.z, v.w
  • v.r v.g, v.b, v.a
  • v[0] v[1], v[2], v[3]

If you haven't worked with a shading language before, this might seem odd. Why not just use indices? They're necessary for vectors with more than four components either way.

However, supporting only indexing makes a lot of common vector operations hard to read/write. For example, here's a snippet of a random ShaderToy with formatting cleaned up. I did not look hard for this example, I just picked a shader at random from the home-page:

vec3 rain = pow(texture(iChannel0, uv2 + iTime * 7.25468).rgb, vec3(1.5));
color = mix(rain, color, clamp(iTime * 0.5 - 0.5, 0.0, 1.0));
color *= 1.0 - pow(length(uv2 * uv2 * uv2 * uv2) * 1.1, 6.0);
uv2.y *= iResolution.y / 360.0;
color.r *= (0.5 + abs(0.5 - mod(uv2.y, 0.021) / 0.021) * 0.5) * 1.5;
color.g *= (0.5 + abs(0.5 - mod(uv2.y + 0.007, 0.021) / 0.021) * 0.5) * 1.5;
color.b *= (0.5 + abs(0.5 - mod(uv2.y + 0.014, 0.021) / 0.021) * 0.5) * 1.5;
color *= 0.9 + rain * 0.35;

I encourage the reader to rewrite this snippet to use only indices. Whenever you see swizzle syntax (e.g. .xyz), you'll have to replace it with a shuffle, or invent a swizzle syntax that doesn't rely on named fields. When you're done, I encourage you to do a separate pass where you instead rewrite it to keep xyz, but never use rgb.

We should support indexing vectors with xyzw, and rgba. We should not support stpq.

A downside of this feature is that it encourages component access which is efficient in shaders, but is likely inefficient if done in between SIMD operations on a typical CPU. Users will need to understand the hardware they're targeting to make optimal choices.

Vector literals do not need to be changed. Initializing vectors with named fields creates visual noise without clarifying meaning, feel free to convert some of the linked shader to this form to see for yourself.

Swizzle Syntax

Languages like HLSL/GLSL/Slang support swizzling vectors. This lowers to a shuffle, but is given special syntax since it's a very common operation.

For example, in GLSL v.xxy is logically equivalent to vec3(v.x, v.x, v.y).

Here's an example of swizzles in practice to demonstrate why having a shorthand is important. This is from the same shader as earlier randomly picked from the ShaderToy home page. Rewriting this snippet to use shuffles is left as an exercise for the reader:

uv.y *= iResolution.y / iResolution.x;
vec2 mouse = (iMouse.xy / iResolution.xy - 0.5) * 3.0;
if (iMouse.z < 1.0) mouse = vec2(0.0);
mat2 rotview1, rotview2;
vec3 from = origin + move(rotview1, rotview2);
vec3 dir = normalize(vec3(uv * 0.8, 1.0));
dir.yz *= rot(mouse.y);
dir.xz *= rot(mouse.x);
dir.yz *= rotview2;
dir.xz *= rotview1;

Notice that swizzles can be both l-values and r-values.

I propose that we support the same syntax as HLSL, GLSL, and Slang for swizzles. Pointers to swizzles should result in a compiler error since it's unclear how to lower these. This syntax only works for up to 4 components, above that and the user must use @shuffle.

Vector Concatenation

I propose that, similarly to arrays, vectors can be concatenated with ++. This should also work on a mix of arrays and vectors.

constresult:@Vector(f32,4)=color.rgb*b++.{mask.a};

This allows flexible construction of vectors, without needing to add support for the slightly more complex construction syntax offered by GLSL/HLSL/Slang.

Without this feature, the example from above generates worse code (more scalar ops) and is harder to read:

constresult:@Vector(f32,4)=.{b*color.r,b*color.g,b*color.b,mask.a};

You could avoid the extra scalar ops by creating a local, but it would still easy to miss the fact that the last component is handled differently:

constrgb=b*color.rgb;constresult:@Vector(f32,4)=.{rgb.r,rgb.g,rgb.b,mask.a};

* Operator

Currently, the * operator supports scalar x scalar multiplication, and vector x vector multiplication. It does not support mixing scalars and vectors. This makes sense, since the current design is targeting SIMD and AFAIK there are no SIMD instructions that multiply scalars by vectors.

However, SPIR-V does have a OpVectorTimesScalar instruction. As such, it would make sense for Zig to support this operation. On targets where it's unsupported it would lower two a vector vector multiplication with a splat.

This also has the advantage of making these operations much easier to read and write. You may or may not have noticed that I've been assuming this feature exists in my previous snippets to keep them from becoming overly verbose.

This code:

particle.position+=particle.velocity*@as(@Vector(3,f32),@splat(dt));particle.acceleration+=particle.velocity*@as(@Vector(3,f32),@splat(dt));

Becomes this:

particle.position+=particle.velocity*dt;particle.acceleration+=particle.velocity*dt;

Many of the other examples in this proposal assume this syntax exists to avoid becoming hard to read due to the splat + cast.

Builtins

We should split @mulAdd into @mulAdd and @fma. Other than that, our existing vector builtins for the most part map well to what SPIR-V offers. There are some additional arithmetic operations we may want to consider supporting.

I'll need to go through these one by one. I suspect that many of them don't map to actual hardware instructions, if the implementations are short and straightforward it may be okay to leave them out. See also the next section on extended instruction sets.

Extended Instruction Sets

SPIR-V supports extended instruction sets via OpExtInstImport. GLSL.std.450 is an extended instruction set that provides additional, commonly used functionality.

I don't think that we want to adopt builtins for everything in GLSL.std.450. However, we also don't want to re-implement (or ask users to re-implement) all of this functionality since that would inflate the size of the generated shaders.

I think the path forward here is to call into extended instruction sets via inline assembly. Zig modules can be created for common extended instruction sets such as GLSL.std.450, and they can be provided either as part of std or as third party libraries. There may be existing work already done along these lines, I'm not sure, if not this is something I'll need to dig into further.


Thanks for reading!

@Snektron @mlugg @SpexGuy

We want to improve support for authoring SPIR-V shaders in Zig. The accepted proposal #32032 suggests adding `vec2`, `vec3`, and `vec4` types to the language to support this goal. This proposal is my suggestion for how exactly we do that. Happy to work on the implementation if accepted. ## Problem Statement SPIR-V supports vectors of [two or more scalars](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_types), as well as a number of [instructions](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#_instructions) that operate on vectors. Popular languages that target SPIR-V ([GLSL](https://registry.khronos.org/OpenGL/specs/gl/GLSLangSpec.4.60.html), [HLSL](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl), and [Slang](https://shader-slang.org/)) have language level support for vectors. This allows programmers to write readable code that lowers to vector instructions. Shader authors lean heavily on this functionality. IIUC these instructions typically end up scalarized when compiled by the graphics driver from SPIR-V to the ISA for a particular GPU (e.g. [RDNA4](https://docs.amd.com/v/u/en-US/rdna4-instruction-set-architecture)). However, playing nice with the IR keeps shaders small, likely makes the optimizer's job easier, and gives us an easy path forward on the accepted proposal to [add a matrix type to the language](https://github.com/ziglang/zig/issues/4960) since these types will need to interact with vectors. (Relying on the optimizer to vectorize scalar code is probably not ideal here, as an example Khronos' SPIR-V optimizer fails to autovecotrize scalar code in [seemingly easy cases](https://godbolt.org/z/jz6zoY7Ye). I am not criticizing their implementation, seemingly easy does not necessarily mean actually easy.) Additionally, by providing a built-in way for users to access vector functionality: 1. One of the main motivations for asking for operator overloading is resolved 2. Libraries with public APIs using vectors don't have to agree on a math library *Point 2. comes up a lot in game dev libraries. I have about [20 different Zig game dev libraries](https://codeberg.org/Games-by-Mason), I counted and about half of them want to use vectors in their public APIs, often just for a vec2. Each of those needs to make the tough decision of whether to accept vectors as `[2]f32`, or bite the bullet and pull in my whole math library to get a type with field names.* ## Proposal **Zig's SPIR-V backend already compiles `@Vector(...)` types to SPIR-V vectors today.** This status quo will continue to serve us well when we [add a matrix type to the language](https://github.com/ziglang/zig/issues/4960), as [LLVM represents matrices as vectors](https://llvm.org/docs/LangRef.html#matrix-intrinsics). I propose that instead of adding new types for vectors, we extend the existing vector type with two goals: 1. Make it easier to lower to optimal SPIR-V operations 2. Make it easier to read and write the type of vectorized code often seen in shaders, whether targeting a CPU or a GPU This means that we would reject [gh/7305 rename SIMD type from @Vector to @Simd](https://github.com/ziglang/zig/issues/7305) since we would be embracing SPIR-V as a use case for this type, and SPIR-V vectors are not SIMD vectors. Additionally, as a side effect of this proposal, the builtin vectors will become usable as general purpose vectors, so it is not as important that we distinguish between these use cases. *That being said, this proposal is only suggesting that we add operations actually available on targets we support. Higher level operations like quaternion multiplication are out of scope and can easily be provided by users in library code.* ## New Functionality ### `@Vector` Takes A Layout Argument Currently, `@Vector` is sized and aligned for aligned loads into SIMD registers. We don't recommend that users store these types long term, since this results in space wasted to padding. Instead, we suggest that they coerce vectors to arrays for this purpose. From a processing perspective, this is the best of both worlds: tightly packed data in long term storage, aligned data in the hot loop. As vectors gain support for field access and swizzles, there's increased temptation to store them long term to gain access to convenient syntax. Rather than shoo people away from using these convenient features, we should accept this use case as valid by adding a `layout` argument to the `@Vector` builtin: ```zig @Vector(len: comptime_int, Element: type, layout: enum { auto, array }) type ``` The auto layout is the current layout. It tells the compiler to pick the most efficient layout for vector operations on the target, and to pass the vector via SIMD registers to functions when possible. The array layout tells the compiler to layout and pass the vector like an array of its components. In GLSL (and I believe HLSL/Slang), vectors may or may not have extra padding depending on the struct layout (scalar, std140, std430, etc). This is essentially a more flexible version of that feature. Note that while this makes declaring vector types more verbose, vector types were **already** very verbose if you used the builtin every time you needed to name them. We can suggest that users alias frequently used vector types under short names for themselves: ```zig const vec2 = @Vector(2, f32, .array); const vec3 = @Vector(3, f32, .array); const vec4 = @Vector(4, f32, .array); const simd2 = @Vector(2, f32, .auto); const simd3 = @Vector(3, f32, .auto); const simd4 = @Vector(4, f32, .auto); ``` ### Vector Length We should accept [gh/#17886 add .len to vector values](https://github.com/ziglang/zig/issues/17886). ### Component Access In GLSL, the components of a `vec4 v` can be accessed in the following ways: * `v.x` `v.y`, `v.z`, `v.w` * `v.r` `v.g`, `v.b`, `v.a` * `v.s` `v.t`, `v.p`, `v.q` (rarely used) * `v[0]` `v[1]`, `v[2]`, `v[3]` In HLSL and Slang, the components of a `floatx4 v` can be accessed in the following ways: * `v.x` `v.y`, `v.z`, `v.w` * `v.r` `v.g`, `v.b`, `v.a` * `v[0]` `v[1]`, `v[2]`, `v[3]` If you haven't worked with a shading language before, this might seem odd. Why not just use indices? They're necessary for vectors with more than four components either way. However, supporting only indexing makes a lot of common vector operations hard to read/write. For example, here's a snippet of [a random ShaderToy](https://www.shadertoy.com/view/Xtf3Rn) with formatting cleaned up. I did not look hard for this example, I just picked a shader at random from the home-page: ```glsl vec3 rain = pow(texture(iChannel0, uv2 + iTime * 7.25468).rgb, vec3(1.5)); color = mix(rain, color, clamp(iTime * 0.5 - 0.5, 0.0, 1.0)); color *= 1.0 - pow(length(uv2 * uv2 * uv2 * uv2) * 1.1, 6.0); uv2.y *= iResolution.y / 360.0; color.r *= (0.5 + abs(0.5 - mod(uv2.y, 0.021) / 0.021) * 0.5) * 1.5; color.g *= (0.5 + abs(0.5 - mod(uv2.y + 0.007, 0.021) / 0.021) * 0.5) * 1.5; color.b *= (0.5 + abs(0.5 - mod(uv2.y + 0.014, 0.021) / 0.021) * 0.5) * 1.5; color *= 0.9 + rain * 0.35; ``` I encourage the reader to rewrite this snippet to use only indices. Whenever you see swizzle syntax (e.g. `.xyz`), you'll have to replace it with a shuffle, or invent a swizzle syntax that doesn't rely on named fields. When you're done, I encourage you to do a separate pass where you instead rewrite it to keep `xyz`, but never use `rgb`. **We should support indexing vectors with `xyzw`, and `rgba`.** We should *not* support `stpq`. *A downside of this feature is that it encourages component access which is efficient in shaders, but is likely inefficient if done in between SIMD operations on a typical CPU. Users will need to understand the hardware they're targeting to make optimal choices.* *Vector literals do not need to be changed. Initializing vectors with named fields creates visual noise without clarifying meaning, feel free to convert some of the linked shader to this form to see for yourself.* ### Swizzle Syntax Languages like HLSL/GLSL/Slang support swizzling vectors. This lowers to a shuffle, but is given special syntax since it's a very common operation. For example, in GLSL `v.xxy` is logically equivalent to `vec3(v.x, v.x, v.y)`. Here's an example of swizzles in practice to demonstrate why having a shorthand is important. This is from the [same shader as earlier](https://www.shadertoy.com/view/Xtf3Rn) randomly picked from the ShaderToy home page. Rewriting this snippet to use shuffles is left as an exercise for the reader: ```glsl uv.y *= iResolution.y / iResolution.x; vec2 mouse = (iMouse.xy / iResolution.xy - 0.5) * 3.0; if (iMouse.z < 1.0) mouse = vec2(0.0); mat2 rotview1, rotview2; vec3 from = origin + move(rotview1, rotview2); vec3 dir = normalize(vec3(uv * 0.8, 1.0)); dir.yz *= rot(mouse.y); dir.xz *= rot(mouse.x); dir.yz *= rotview2; dir.xz *= rotview1; ``` Notice that swizzles can be both l-values and r-values. **I propose that we support the same syntax as HLSL, GLSL, and Slang for swizzles.** Pointers to swizzles should result in a compiler error since it's unclear how to lower these. This syntax only works for up to 4 components, above that and the user must use `@shuffle`. ### Vector Concatenation I propose that, similarly to arrays, vectors can be concatenated with `++`. This should also work on a mix of arrays and vectors. ```zig const result: @Vector(f32, 4) = color.rgb * b ++ .{ mask.a }; ``` This allows flexible construction of vectors, without needing to add support for the slightly more complex construction syntax offered by GLSL/HLSL/Slang. Without this feature, the example from above generates worse code (more scalar ops) and is harder to read: ```zig const result: @Vector(f32, 4) = .{ b * color.r, b * color.g, b * color.b, mask.a }; ``` You could avoid the extra scalar ops by creating a local, but it would still easy to miss the fact that the last component is handled differently: ```zig const rgb = b * color.rgb; const result: @Vector(f32, 4) = .{ rgb.r, rgb.g, rgb.b, mask.a }; ``` ## `*` Operator Currently, the `*` operator supports scalar x scalar multiplication, and vector x vector multiplication. It does not support mixing scalars and vectors. This makes sense, since the current design is targeting SIMD and AFAIK there are no SIMD instructions that multiply scalars by vectors. However, SPIR-V *does* have a [`OpVectorTimesScalar`](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Arithmetic) instruction. As such, it would make sense for Zig to support this operation. On targets where it's unsupported it would lower two a vector vector multiplication with a splat. This also has the advantage of making these operations much easier to read and write. You may or may not have noticed that I've been assuming this feature exists in my previous snippets to keep them from becoming overly verbose. This code: ```zig particle.position += particle.velocity * @as(@Vector(3, f32), @splat(dt)); particle.acceleration += particle.velocity * @as(@Vector(3, f32), @splat(dt)); ``` Becomes this: ```zig particle.position += particle.velocity * dt; particle.acceleration += particle.velocity * dt; ``` Many of the other examples in this proposal assume this syntax exists to avoid becoming hard to read due to the splat + cast. ## Builtins We should [split `@mulAdd` into `@mulAdd` and `@fma`](https://codeberg.org/ziglang/zig/issues/35312). Other than that, our existing vector builtins for the most part map well to what SPIR-V offers. There are some additional [arithmetic operations](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Arithmetic) we may want to consider supporting. I'll need to go through these one by one. I suspect that many of them don't map to actual hardware instructions, if the implementations are short and straightforward it may be okay to leave them out. See also the next section on extended instruction sets. ## Extended Instruction Sets SPIR-V supports extended instruction sets via `OpExtInstImport`. [`GLSL.std.450`](https://registry.khronos.org/SPIR-V/specs/unified1/GLSL.std.450.html) is an extended instruction set that provides additional, commonly used functionality. I don't think that we want to adopt builtins for everything in `GLSL.std.450`. However, we also don't want to re-implement (or ask users to re-implement) all of this functionality since that would inflate the size of the generated shaders. I think the path forward here is to call into extended instruction sets via inline assembly. Zig modules can be created for common extended instruction sets such as `GLSL.std.450`, and they can be provided either as part of `std` or as third party libraries. There may be existing work already done along these lines, I'm not sure, if not this is something I'll need to dig into further. *** Thanks for reading! @Snektron @mlugg @SpexGuy

Overall, I think I like this. While reading through the proposal, I thought of a few new clarifying questions that I'm curious about:

  1. How do swizzle channel names behave with vector sizes larger than 4? Are xyzw/rgba still allowed on a vec7? Is there a way to swizzle with channel 4/5/6 on this vector? (Maybe a swizzle builtin with comptime known indexes?)

  2. On supporting vector * scalar, did you consider allowing scalars to coerce to vectors through an implicit splat? This doesn't solve the existing problems with type inference inside binary operators, but does it introduce other undesirable behavior?

  3. With layout types, it feels like implicit coercion should be allowed between simd and array vectors. E.g. you might do a bunch of math with a simd vector and then store it at the end in an array vector in a struct. But what happens with peer type resolution? Is the result of simd plus array simd or array? Or can we leave this as a compile error without causing overly sharp edges? Should we disallow coercion to avoid an inconsistency?

Overall, I think I like this. While reading through the proposal, I thought of a few new clarifying questions that I'm curious about: 1. How do swizzle channel names behave with vector sizes larger than 4? Are xyzw/rgba still allowed on a vec7? Is there a way to swizzle with channel 4/5/6 on this vector? (Maybe a swizzle builtin with comptime known indexes?) 2. On supporting vector * scalar, did you consider allowing scalars to coerce to vectors through an implicit splat? This doesn't solve the existing problems with type inference inside binary operators, but does it introduce other undesirable behavior? 3. With layout types, it feels like implicit coercion should be allowed between simd and array vectors. E.g. you might do a bunch of math with a simd vector and then store it at the end in an array vector in a struct. But what happens with peer type resolution? Is the result of simd plus array simd or array? Or can we leave this as a compile error without causing overly sharp edges? Should we disallow coercion to avoid an inconsistency?
Author
Owner
Copy link

Good questions.

  1. How do swizzle channel names behave with vector sizes larger than 4? Are xyzw/rgba still allowed on a vec7? Is there a way to swizzle with channel 4/5/6 on this vector? (Maybe a swizzle builtin with comptime known indexes?)

Since swizzle lowers to a shuffle (including on SPIR-V, see the note on OpVectorShuffle), I think it’s okay to require users just use @shuffle for components above 4.

I do think that the named fields and swizzles should continue to work on the first four components of large vectors, but I could be talked out of this. My current rationale is that I don’t see a reason not to support this.

  1. On supporting vector * scalar, did you consider allowing scalars to coerce to vectors through an implicit splat? This doesn't solve the existing problems with type inference inside binary operators, but does it introduce other undesirable behavior?

I was going to suggest this, but IMO it does lead to undesirable behavior. For example, this would allow for vector + scalar.

That being said I do think we should consider whether @splat should be able to infer the result length when used as part of a concatenation. I decided to elide this idea from the proposal to keep thing simple for now, but supporting this would make constructing vectors from pieces much nicer.

  1. With layout types, it feels like implicit coercion should be allowed between simd and array vectors. E.g. you might do a bunch of math with a simd vector and then store it at the end in an array vector in a struct. But what happens with peer type resolution? Is the result of simd plus array simd or array? Or can we leave this as a compile error without causing overly sharp edges? Should we disallow coercion to avoid an inconsistency?

Good point. My intention is to support coercion between the layouts, IMO it would be a shame to require casts here. However, I didn't consider what this means for peer type resolution.

If Zig's type system allows for it, I think we should allow coercion in either direction but forbid peer type resolution since it's ambiguous which layout the user wants to win. @mlugg can probably tell me whether or not I'm asking for type system crimes here.

Good questions. > 1. How do swizzle channel names behave with vector sizes larger than 4? Are xyzw/rgba still allowed on a vec7? Is there a way to swizzle with channel 4/5/6 on this vector? (Maybe a swizzle builtin with comptime known indexes?) Since swizzle lowers to a shuffle ([including on SPIR-V](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#Composite), see the note on `OpVectorShuffle`), I think it’s okay to require users just use `@shuffle` for components above 4. I do think that the named fields and swizzles should continue to work on the first four components of large vectors, but I could be talked out of this. My current rationale is that I don’t see a reason not to support this. > 2. On supporting vector * scalar, did you consider allowing scalars to coerce to vectors through an implicit splat? This doesn't solve the existing problems with type inference inside binary operators, but does it introduce other undesirable behavior? I was going to suggest this, but IMO it does lead to undesirable behavior. For example, this would allow for `vector + scalar`. That being said I do think we should consider whether `@splat` should be able to infer the result length when used as part of a concatenation. I decided to elide this idea from the proposal to keep thing simple for now, but supporting this would make constructing vectors from pieces much nicer. > 3. With layout types, it feels like implicit coercion should be allowed between simd and array vectors. E.g. you might do a bunch of math with a simd vector and then store it at the end in an array vector in a struct. But what happens with peer type resolution? Is the result of simd plus array simd or array? Or can we leave this as a compile error without causing overly sharp edges? Should we disallow coercion to avoid an inconsistency? Good point. My intention is to support coercion between the layouts, IMO it would be a shame to require casts here. However, I didn't consider what this means for peer type resolution. If Zig's type system allows for it, I think we should allow coercion in either direction but forbid peer type resolution since it's ambiguous which layout the user wants to win. @mlugg can probably tell me whether or not I'm asking for type system crimes here.
andrewrk added this to the Urgent milestone 2026年05月21日 20:03:46 +02:00

I think this is well specified and well thought out for a green light. Feel free to continue to discuss and refine it but it's enough that an implementation is welcome.

I think this is well specified and well thought out for a green light. Feel free to continue to discuss and refine it but it's enough that an implementation is welcome.
Author
Owner
Copy link

Great! I'll get started on that. :)

Great! I'll get started on that. :)

Hello @pals, I haven't seen you around before. Welcome. What zig project(s) do you work on that led you to develop this perspective?

Hello @pals, I haven't seen you around before. Welcome. What zig project(s) do you work on that led you to develop this perspective?
Author
Owner
Copy link

@SpexGuy: On supporting vector * scalar, did you consider allowing scalars to coerce to vectors through an implicit splat? This doesn't solve the existing problems with type inference inside binary operators, but does it introduce other undesirable behavior?

@MasonRemaley: I was going to suggest this, but IMO it does lead to undesirable behavior. For example, this would allow for vector + scalar.

I just noticed while working on a shader that GLSL does in fact allow vector + scalar operations. HLSL and Slang do as well. I'm not sure how I managed to write GLSL for like 15 years without realizing this.

It's just a language level feature, there's no SPIR-V opcode for it so supporting it wouldn't make a difference in terms of lowering to SPIR-V. However, the fact that these languages support it means it isn't fair for me to just assume such functionality is undesirable.

I'm still slightly against it--I think just calling splat explicitly is fine--but I'll start using it in my GLSL shaders to see if this affects my opinion. Open to hearing others thoughts.

> > @SpexGuy: On supporting vector * scalar, did you consider allowing scalars to coerce to vectors through an implicit splat? This doesn't solve the existing problems with type inference inside binary operators, but does it introduce other undesirable behavior? > > @MasonRemaley: I was going to suggest this, but IMO it does lead to undesirable behavior. For example, this would allow for vector + scalar. I just noticed while working on a shader that GLSL does in fact allow vector + scalar operations. HLSL and Slang do as well. I'm not sure how I managed to write GLSL for like 15 years without realizing this. It's just a language level feature, there's no SPIR-V opcode for it so supporting it wouldn't make a difference in terms of lowering to SPIR-V. However, the fact that these languages support it means it isn't fair for me to just assume such functionality is undesirable. I'm still slightly against it--I think just calling splat explicitly is fine--but I'll start using it in my GLSL shaders to see if this affects my opinion. Open to hearing others thoughts.

Minor thing about naming - I propose we choose between child and Element and have it be consistent.

In the Vector struct you get from @typeInfo it's len and child.

 /// This data structure is used by the Zig language code generation and
 /// therefore must be kept in sync with the compiler implementation.
 pub const Vector = struct {
 len: comptime_int,
 child: type,
 };

However, the builtin function is len and Element.

@Vector(len: comptime_int, Element: type) type 

No strong emotions towards either name, it could be a good idea to also choose a name that makes it clear it must be an number type.

Also, what about layout: enum {array, SIMD} instead of auto? If I saw auto for the first time in my life It wouldn't immediately come to my mind that it's refering to a SIMD vector.

Minor thing about naming - I propose we choose between child and Element and have it be consistent. In the Vector struct you get from @typeInfo it's len and child. ``` /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. pub const Vector = struct { len: comptime_int, child: type, }; ``` However, the builtin function is len and Element. ``` @Vector(len: comptime_int, Element: type) type ``` No strong emotions towards either name, it could be a good idea to also choose a name that makes it clear it must be an number type. Also, what about layout: enum {array, SIMD} instead of auto? If I saw auto for the first time in my life It wouldn't immediately come to my mind that it's refering to a SIMD vector.

@pals wrote in #35376 (comment):

What I'd propose instead

It'll work better to have canonical vec names. It lets the mathematical vector-types pick up the; field access, swizzles, concatenation and SPIR-V lowering - all of which genuinely belongs to that type.

This proposal allows you to do these things in userland, You don't have to stare at @Vector if you don't like it.

fn vec3(child: type) type {
 return @Vector(3, child, .array);
}
@pals wrote in https://codeberg.org/ziglang/zig/issues/35376#issuecomment-15648467: > **What I'd propose instead** > > It'll work better to have canonical `vec` names. It lets the mathematical vector-types pick up the; field access, swizzles, concatenation and SPIR-V lowering - all of which genuinely belongs to that type. This proposal allows you to do these things in userland, You don't have to stare at @Vector if you don't like it. ``` fn vec3(child: type) type { return @Vector(3, child, .array); } ```
Author
Owner
Copy link

@Pals: On the layout enum; .auto wants SIMD-register padding and alignment; .array wants tight packing for storage and GPU interop.
It's clear as day to me, when two variants of "the same type" disagree on layout, disagree on calling convention, disagree on what operations are efficient, and can't cleanly resolve against each other.. they aren't really the same type.They're two types now sharing a constructor.

A major benefit of having a SPIR-V backend is that the executable running on the CPU can share code with the shaders. I personally have about 2.5k LOC and growing of color conversions, noise functions, easing functions, etc that I've had to hand write in two languages up until now. Once I can just write everything in Zig, this goes away, which is very exciting.

If we split the type up the types based on whether they lower to SPIR-V or not this benefit vanishes, so that's a no go. It's unclear to me if that's what you're suggesting, or if you're suggesting that we split the types based on the layout. Splitting the types based on the layout is more tempting--early versions of this proposal did exactly that--but it turns out that this also leads to problems.

Storing vectors padded+aligned versus packed+unaligned has performance implications on both classes of hardware. If we were to create separate types for each layout, and then only add the convenient syntax to one of them, this would push users towards favoring that type which isn't what we want. We want users to be able to trivially switch to the alignment and padding that is optimal for their use case without rewriting all of their code.

It of course remains true that different operations will be efficient on a CPU vs the GPU. Optimizing for each target individually will always produce the best results, but nothing in this proposal is preventing you from doing that. This same tradeoff exists everywhere that a language abstracts over varying hardware, we routinely accept this tradeoff because it lets us build software for a variety of systems.

(It's also worth noting that the question around peer type resolution is not a new one. It exists today with SIMD vectors vs arrays--the compiler currently breaks the tie by choosing the vector type--we just need to decide whether the status quo is desirable or not.)

@Plebosaur: Minor thing about naming - I propose we choose between child and Element and have it be consistent.

Agreed. I frequently find myself accessing fields on type info from inline else statements, but this only works if they're named consistently. Coincidentally I'll end up deleting much of that code once this feature is implemented, but regardless it doesn't hurt to allow for that use case.

> @Pals: On the layout enum; .auto wants SIMD-register padding and alignment; .array wants tight packing for storage and GPU interop. > It's clear as day to me, when two variants of "the same type" disagree on layout, disagree on calling convention, disagree on what operations are efficient, and can't cleanly resolve against each other.. they aren't really the same type.They're two types now sharing a constructor. A major benefit of having a SPIR-V backend is that the executable running on the CPU can share code with the shaders. I personally have about 2.5k LOC and growing of color conversions, noise functions, easing functions, etc that I've had to hand write in two languages up until now. Once I can just write everything in Zig, this goes away, which is very exciting. If we split the type up the types based on whether they lower to SPIR-V or not this benefit vanishes, so that's a no go. It's unclear to me if that's what you're suggesting, or if you're suggesting that we split the types based on the layout. Splitting the types based on the layout is more tempting--early versions of this proposal did exactly that--but it turns out that this also leads to problems. Storing vectors padded+aligned versus packed+unaligned has performance implications on both classes of hardware. If we were to create separate types for each layout, and then only add the convenient syntax to one of them, this would push users towards favoring that type which isn't what we want. We want users to be able to trivially switch to the alignment and padding that is optimal for their use case without rewriting all of their code. It of course remains true that different operations will be efficient on a CPU vs the GPU. Optimizing for each target individually will always produce the best results, but nothing in this proposal is preventing you from doing that. This same tradeoff exists everywhere that a language abstracts over varying hardware, we routinely accept this tradeoff because it lets us build software for a variety of systems. (It's also worth noting that the question around peer type resolution is not a new one. It exists today with SIMD vectors vs arrays--the compiler currently breaks the tie by choosing the vector type--we just need to decide whether the status quo is desirable or not.) > @Plebosaur: Minor thing about naming - I propose we choose between child and Element and have it be consistent. Agreed. I frequently find myself accessing fields on type info from inline else statements, but this only works if they're named consistently. Coincidentally I'll end up deleting much of that code once this feature is implemented, but regardless it doesn't hurt to allow for that use case.

This makes sense, since the current design is targeting SIMD and AFAIK there are no SIMD instructions that multiply scalars by vectors.

While there are no instructions which multiply scalars by vectors, there are some which multiply vectors by scalars.

For example in RISC-V we have vmul.vv for vector*vector and vmul.vx for vector*scalar. In case of addition there's even a variant for immediate values (ending with .vi).

Here's a link to the spec if somebody is interested: https://docs.riscv.org/reference/isa/extensions/vector/_attachments/riscv-v-spec.pdf

> This makes sense, since the current design is targeting SIMD and AFAIK there are no SIMD instructions that multiply scalars by vectors. While there are no instructions which multiply scalars by vectors, there are some which multiply vectors by scalars. For example in RISC-V we have `vmul.vv` for vector\*vector and `vmul.vx` for vector\*scalar. In case of addition there's even a variant for immediate values (ending with `.vi`). Here's a link to the spec if somebody is interested: https://docs.riscv.org/reference/isa/extensions/vector/_attachments/riscv-v-spec.pdf
Author
Owner
Copy link

@KilianHanich thanks that's good to know on both accounts! In light of this, I may be coming around to @SpexGuy's suggestion WRT splats.

@KilianHanich thanks that's good to know on both accounts! In light of this, I may be coming around to @SpexGuy's suggestion WRT splats.

I don't think this might be that good of an idea.

If one writes e.g. a RISC-V backend (this likely also includes SPIR-V, I guess), one would like to have the knowledge if the other operand is also a vector or a scalar. With @splat one would have two vectors and need to figure out which case one has by looking if the vector is a splat in place (and not a vector variable).

I don't think this might be that good of an idea. If one writes e.g. a RISC-V backend (this likely also includes SPIR-V, I guess), one would like to have the knowledge if the other operand is also a vector or a scalar. With `@splat` one would have two vectors and need to figure out which case one has by looking if the vector is a splat in place (and not a vector variable).

The core of this proposal seems fine to me but there are a few specifics that I don't like.

First, I don't really buy the technical justification that this is required for the SPIR-V backend. Like you say, GPUs don't really have instructions for this (I think Terascale might have had some, or other mostly fixed function hardware). There are packed operations, though, but these really function more like SIMD (usually packed half or bf16 ops, but RDNA3+ can express SIMD as VOPD instructions as well). I also don't really buy the shader compression argument, I suppose it would save a few bytes, but I doubt if it were a significant amount.

The current vector handling in the SPIR-V backend is complicated and for little effect. I spoke to other SPIR-V generator authors and they don't consider it. I would rip it out.

Second, I don't like how this proposal attempts to merge SIMD vectors and math vectors. They solve different purposes: one is for math and the other for parallelization. The former needs lots of horizontal access while that is the explicit opposite of what you want with the latter. I think we should separate them. I'm fine with reusing @Vector for this proposal, but it would be a confusing change for users.

The layout argument confuses me. One of the issues with GLSL layouts was that vendors didn't know how to implement the memory layout of vec3. I don't know if that got better, but if not, Id argue to make both math and SIMD vectors an opaque type and require users to always store vectors as arrays. This makes memory layout always explicit.

Currently, the * operator supports scalar x scalar multiplication, and vector x vector multiplication. It does not support mixing scalars and vectors. This makes sense, since the current design is targeting SIMD and AFAIK there are no SIMD instructions that multiply scalars by vectors.

In most languages that support vectors natively, vector (op) scalar is well defined. It's commonly accepted that the scalar auto-splats to the appropriate vector type for all binary operators supported by the vector element type. That said, if vectors support @splat, I don't think this is gravely necessary. Perhaps something to reevaluate after trying it for a while? To be explicit: I think the same handling should be done for all binary operators in Zig too, whethers that's auto-splat or explicit-splat, however uncommon those operations may be. This simply reduces the amount of special cases in the language.

Many of the other examples in this proposal assume this syntax exists to avoid becoming hard to read due to the splat + cast.

Ideally the coercion shouldn't be required here:

particle.position+=particle.velocity*@splat(dt);particle.acceleration+=particle.velocity*@splat(dt);
The core of this proposal seems fine to me but there are a few specifics that I don't like. First, I don't really buy the technical justification that this is required for the SPIR-V backend. Like you say, GPUs don't really have instructions for this (I think Terascale might have had some, or other mostly fixed function hardware). There are packed operations, though, but these really function more like SIMD (usually packed half or bf16 ops, but RDNA3+ can express SIMD as VOPD instructions as well). I also don't really buy the shader compression argument, I suppose it would save a few bytes, but I doubt if it were a significant amount. The current vector handling in the SPIR-V backend is complicated and for little effect. I spoke to other SPIR-V generator authors and they don't consider it. I would rip it out. Second, I don't like how this proposal attempts to merge SIMD vectors and math vectors. They solve different purposes: one is for math and the other for parallelization. The former needs lots of horizontal access while that is the explicit opposite of what you want with the latter. I think we should *separate* them. I'm fine with reusing `@Vector` for this proposal, but it would be a confusing change for users. The layout argument confuses me. One of the issues with GLSL layouts was that vendors didn't know how to implement the memory layout of vec3. I don't know if that got better, but if not, Id argue to make both math and SIMD vectors an opaque type and require users to always store vectors as arrays. This makes memory layout always explicit. > Currently, the * operator supports scalar x scalar multiplication, and vector x vector multiplication. It does not support mixing scalars and vectors. This makes sense, since the current design is targeting SIMD and AFAIK there are no SIMD instructions that multiply scalars by vectors. In most languages that support vectors natively, vector (op) scalar is well defined. It's commonly accepted that the scalar auto-splats to the appropriate vector type for _all_ binary operators supported by the vector element type. That said, if vectors support `@splat`, I don't think this is gravely necessary. Perhaps something to reevaluate after trying it for a while? To be explicit: I think the same handling should be done for all binary operators in Zig too, whethers that's auto-splat or explicit-splat, however uncommon those operations may be. This simply reduces the amount of special cases in the language. > Many of the other examples in this proposal assume this syntax exists to avoid becoming hard to read due to the splat + cast. Ideally the coercion shouldn't be required here: ```zig particle.position += particle.velocity * @splat(dt); particle.acceleration += particle.velocity * @splat(dt); ```
Author
Owner
Copy link

@Snektron thanks for your feedback!

I also don't really buy the shader compression argument, I suppose it would save a few bytes, but I doubt if it were a significant amount.

The IR size isn't of particular concern for my use case since I only ship a few shaders, but I'm concerned about large games that ship a huge number of shaders having to spend 4x the opcodes on common math operations. Maybe this goes away with lossless compression though.

(I also personally suspect that large games will stop doing this as graphics APIs improve, which may or may not affect the weight of this argument.)

The current vector handling in the SPIR-V backend is complicated and for little effect. I spoke to other SPIR-V generator authors and they don't consider it. I would rip it out.

I haven't dug into the code yet--where does the complexity in the current handling come from?

I'm also interested in which generators you looked at, my only experience is using glslang to compile GLSL -> SPIRV which does emit the vector instructions. It would be interesting to see what the code generated by other compilers looks like.

The layout argument confuses me. One of the issues with GLSL layouts was that vendors didn't know how to implement the memory layout of vec3. I don't know if that got better, but if not, Id argue to make both math and SIMD vectors an opaque type and require users to always store vectors as arrays. This makes memory layout always explicit.

I tried converting some of my code to this form and found it very difficult to work with. For example, consider the example code from later in your comment:

particle.position += particle.velocity * @splat(dt);
particle.acceleration += particle.velocity * @splat(dt);

This wouldn't compile if position and acceleration were stored as arrays. If they're instead stored as vectors with the .array layout, then the compiler is free to decide whether the unaligned load into a SIMD register is worth the parallelism or not, but regardless the convenient syntax is available to the user.

(That is, unless the suggestion is to add the vector syntax to arrays, but this seems problematic as it means that either arrays of some element types are special, or all arrays of all element types need to support these operations.)

WRT GLSL layouts: I'm not suggesting we support the GLSL layout types specifically, I'm just claiming that they support a similar tradeoff where you may want to store vectors aligned & padded or unaligned & unpadded in a shader. That being said I think I might be misunderstanding your point and you may have some background knowledge I don't here, feel free to elaborate if that's the case!

In most languages that support vectors natively, vector (op) scalar is well defined. It's commonly accepted that the scalar auto-splats to the appropriate vector type for all binary operators supported by the vector element type. That said, if vectors support @splat, I don't think this is gravely necessary. Perhaps something to reevaluate after trying it for a while? To be explicit: I think the same handling should be done for all binary operators in Zig too, whethers that's auto-splat or explicit-splat, however uncommon those operations may be. This simply reduces the amount of special cases in the language.

I think that's a good idea. I'll leave this off at first, and see what it's like in practice when I start updating my game & libraries to use these types.

@Snektron thanks for your feedback! > I also don't really buy the shader compression argument, I suppose it would save a few bytes, but I doubt if it were a significant amount. The IR size isn't of particular concern for my use case since I only ship a few shaders, but I'm concerned about large games that ship a huge number of shaders having to spend 4x the opcodes on common math operations. Maybe this goes away with lossless compression though. (I also personally suspect that large games will *stop* doing this as graphics APIs improve, which may or may not affect the weight of this argument.) > The current vector handling in the SPIR-V backend is complicated and for little effect. I spoke to other SPIR-V generator authors and they don't consider it. I would rip it out. I haven't dug into the code yet--where does the complexity in the current handling come from? I'm also interested in which generators you looked at, my only experience is using glslang to compile GLSL -> SPIRV which does emit the vector instructions. It would be interesting to see what the code generated by other compilers looks like. > The layout argument confuses me. One of the issues with GLSL layouts was that vendors didn't know how to implement the memory layout of vec3. I don't know if that got better, but if not, Id argue to make both math and SIMD vectors an opaque type and require users to always store vectors as arrays. This makes memory layout always explicit. I tried converting some of my code to this form and found it very difficult to work with. For example, consider the example code from later in your comment: ``` particle.position += particle.velocity * @splat(dt); particle.acceleration += particle.velocity * @splat(dt); ``` This wouldn't compile if position and acceleration were stored as arrays. If they're instead stored as vectors with the `.array` layout, then the compiler is free to decide whether the unaligned load into a SIMD register is worth the parallelism or not, but regardless the convenient syntax is available to the user. (That is, unless the suggestion is to add the vector syntax to arrays, but this seems problematic as it means that either arrays of some element types are special, or all arrays of all element types need to support these operations.) WRT GLSL layouts: I'm not suggesting we support the GLSL layout types specifically, I'm just claiming that they support a similar tradeoff where you may want to store vectors aligned & padded or unaligned & unpadded in a shader. That being said I think I might be misunderstanding your point and you may have some background knowledge I don't here, feel free to elaborate if that's the case! > In most languages that support vectors natively, vector (op) scalar is well defined. It's commonly accepted that the scalar auto-splats to the appropriate vector type for all binary operators supported by the vector element type. That said, if vectors support @splat, I don't think this is gravely necessary. Perhaps something to reevaluate after trying it for a while? To be explicit: I think the same handling should be done for all binary operators in Zig too, whethers that's auto-splat or explicit-splat, however uncommon those operations may be. This simply reduces the amount of special cases in the language. I think that's a good idea. I'll leave this off at first, and see what it's like in practice when I start updating my game & libraries to use these types.

The IR size isn't of particular concern for my use case since I only ship a few shaders, but I'm concerned about large games that ship a huge number of shaders having to spend 4x the opcodes on common math operations. Maybe this goes away with lossless compression though.

Sounds like an easy investigation. Isn't there a mesa repo with samples of real-world shader SPIR-V somewhere?

I haven't dug into the code yet--where does the complexity in the current handling come from?

One part is that many operations which can be vectorized in Zig cannot be vectorized in SPIR-V, leading to many patterns in the generated code which extract all components, then perform the scalar operation on all components, then re-pack them as vector. The current implementation tries to be smart and reuse unpacked/packed form to reduce binary size, but it's kind of an annoying problem. Of course, unpacking and repacking for every operation causes lots of extra code as well, and the final benefit is unclear. I'm not saying that this shouldn't be implemented as you suggest, though, just that I think it's a mistake to drive language design by something of seemingly little consequence. As you say, lossless compression is probably much more effective.

I'm also interested in which generators you looked at, my only experience is using glslang to compile GLSL -> SPIRV which does emit the vector instructions. It would be interesting to see what the code generated by other compilers looks like.

I mostly heard that as an anecdote from other authors, so take it with a grain of salt. As for other reference generators, you can consider Shady(Thorin/AnyDSL/VCC), cg2c, rust-gpu, as well as the LLVM SPIR-V backend. You can get a decent idea by introspecting the source languages enum contents from the SPIR-V OpSourceLanguage instruction.

I tried converting some of my code to this form and found it very difficult to work with. For example, consider the example code from later in your comment:

Ah, I didn't consider that. I don't have any further feedback than that I don't like it at this time, let me think about it for a bit.

> The IR size isn't of particular concern for my use case since I only ship a few shaders, but I'm concerned about large games that ship a huge number of shaders having to spend 4x the opcodes on common math operations. Maybe this goes away with lossless compression though. Sounds like an easy investigation. Isn't there a mesa repo with samples of real-world shader SPIR-V somewhere? > I haven't dug into the code yet--where does the complexity in the current handling come from? One part is that many operations which can be vectorized in Zig cannot be vectorized in SPIR-V, leading to many patterns in the generated code which extract all components, then perform the scalar operation on all components, then re-pack them as vector. The current implementation tries to be smart and reuse unpacked/packed form to reduce binary size, but it's kind of an annoying problem. Of course, unpacking and repacking for every operation causes lots of extra code as well, and the final benefit is unclear. I'm not saying that this shouldn't be implemented as you suggest, though, just that I think it's a mistake to drive language design by something of seemingly little consequence. As you say, lossless compression is probably much more effective. > I'm also interested in which generators you looked at, my only experience is using glslang to compile GLSL -> SPIRV which does emit the vector instructions. It would be interesting to see what the code generated by other compilers looks like. I mostly heard that as an anecdote from other authors, so take it with a grain of salt. As for other reference generators, you can consider [Shady(Thorin/AnyDSL/VCC)](https://shady-gang.github.io/vcc/), [cg2c](https://gitlab.com/nanokatze/cg2c), rust-gpu, as well as the LLVM SPIR-V backend. You can get a decent idea by introspecting the source languages enum contents from the SPIR-V OpSourceLanguage instruction. > I tried converting some of my code to this form and found it very difficult to work with. For example, consider the example code from later in your comment: Ah, I didn't consider that. I don't have any further feedback than that I don't like it at this time, let me think about it for a bit.
Author
Owner
Copy link

Sounds like an easy investigation. Isn't there a mesa repo with samples of real-world shader SPIR-V somewhere?

Good point. I'm gonna punt on doing this analysis for now since this point alone wouldn't push me in one direction or another, but if it comes down to it I'll find that repo and run some tests.

One part is that many operations which can be vectorized in Zig cannot be vectorized in SPIR-V, leading to many patterns in the generated code which extract all components, then perform the scalar operation on all components, then re-pack them as vector.

Gotcha, that makes a lot of sense. Thanks for the explanation!

I mostly heard that as an anecdote from other authors, so take it with a grain of salt. As for other reference generators, you can consider Shady(Thorin/AnyDSL/VCC), cg2c, rust-gpu, as well as the LLVM SPIR-V backend. You can get a decent idea by introspecting the source languages enum contents from the SPIR-V OpSourceLanguage instruction.

Will do, good tip about OpSource I missed that when reading over the spec.

> Sounds like an easy investigation. Isn't there a mesa repo with samples of real-world shader SPIR-V somewhere? Good point. I'm gonna punt on doing this analysis for now since this point alone wouldn't push me in one direction or another, but if it comes down to it I'll find that repo and run some tests. > One part is that many operations which can be vectorized in Zig cannot be vectorized in SPIR-V, leading to many patterns in the generated code which extract all components, then perform the scalar operation on all components, then re-pack them as vector. Gotcha, that makes a lot of sense. Thanks for the explanation! > I mostly heard that as an anecdote from other authors, so take it with a grain of salt. As for other reference generators, you can consider Shady(Thorin/AnyDSL/VCC), cg2c, rust-gpu, as well as the LLVM SPIR-V backend. You can get a decent idea by introspecting the source languages enum contents from the SPIR-V OpSourceLanguage instruction. Will do, good tip about `OpSource` I missed that when reading over the spec.
Contributor
Copy link

If I understand correctly, there are two main motivators for .array-layout vectors: to better map to native shader vector types than status quo @Vector when targeting e.g. SPIR-V, and to be more useful as general-purpose math vector types in non-shader/GPU programs.

If the goal is to encourage users to use .array-layout vectors as ordinary variables/parameters/fields, I think it's important to clearly specify exactly how interchangeable they are with regular arrays, for example:

  • Is @sizeOf(@Vector(3, f32, .array)) 12 or 16?
  • Is @alignOf(@Vector(3, f32, .array)) 16 or 4?
  • Is @Vector(N, bool, .array) a vector of N bits or N bytes?
  • Is @Vector(n, T, .array) always safely @bitCast-able to [n]T?

It's quite common to define structs like extern struct { pos: [4]f32, rgba: [4]f32 } and upload them to the GPU. Being able to replace such fields with @Vector(n, T, .array) would be nice for doing client-side math and transformations on them directly, but it also requires well-defined size/alignment semantics (preferably not tied to the target, so that the semantics of the client and GPU don't differ). As already mentioned, vec3 is especially problematic because it often has a size of 12 but an alignment of 16.

If I understand correctly, there are two main motivators for `.array`-layout vectors: to better map to native shader vector types than status quo `@Vector` when targeting e.g. SPIR-V, and to be more useful as general-purpose math vector types in non-shader/GPU programs. If the goal is to encourage users to use `.array`-layout vectors as ordinary variables/parameters/fields, I think it's important to clearly specify exactly how interchangeable they are with regular arrays, for example: - Is `@sizeOf(@Vector(3, f32, .array))` 12 or 16? - Is `@alignOf(@Vector(3, f32, .array))` 16 or 4? - Is `@Vector(N, bool, .array)` a vector of `N` bits or `N` bytes? - Is `@Vector(n, T, .array)` always safely `@bitCast`-able to `[n]T`? It's quite common to define structs like `extern struct { pos: [4]f32, rgba: [4]f32 }` and upload them to the GPU. Being able to replace such fields with `@Vector(n, T, .array)` would be nice for doing client-side math and transformations on them directly, but it also requires well-defined size/alignment semantics (preferably not tied to the target, so that the semantics of the client and GPU don't differ). As already mentioned, vec3 is especially problematic because it often has a size of 12 but an alignment of 16.

Second, I don't like how this proposal attempts to merge SIMD vectors and math vectors. They solve different purposes

Thanks to the existence of variable length SIMD (like RISC-V), merging them also creates certain problems imo.

I'm going to go a bit into how RISC-V's vector extension works:

The vector register size is a hardware implementation detail. This means that the load instruction for vector registers receives an address to take data from, a desired length (which is used as a maximum) and saves the actually loaded amount into a register.

You can calculate the actual hardware register size from that (or read that in your CPU spec), but this depends on the actual hardware. For example the Zig standard library has written that 256bit as register size is quite common, but there are also CPUs with 1024bit or some with only 64bit and 128bit is actually also quite common.

And additionally you can combine multiple vector registers (see 3.4.2. Vector Register Grouping in the spec). This means that you can suddenly operate on e.g. 8192bits in one instruction or even just 1024bits, depending on the CPU.

That means that a RISC-V backend/programmer who wants to target any RISC-V CPU with the vector extension has to basically do guesswork in addition to having suboptimal code generation.

Let's take this example code (using a naive matrix memory strategy instead of something more complicated like BlockCompressedRowStorage) with probably more asserts than necessary:

fnmatrix_vector_multiplication(result:[]i32,matrix:[]const[]consti32,vector:[]consti32)void{std.debug.assert(matrix.len>0);std.debug.assert(matrix[0].len==vector.len);std.debug.assert(result.len==matrix.len);constsimd_length=std.simd.suggestVectorLength(i32)orelse8;constVec=@Vector(simd_length,i32);constsimd_part=vector.len-vector.len%simd_length;for(matrix,result)|row,*r|{r.*=0;std.debug.assert(row.len==vector.len);vari:usize=0;while(i<simd_part):(i+=simd_length){constrow_vec:Vec=row[i..][0..simd_length].*;constcol:Vec=vector[i..][0..simd_length].*;r.*+=@reduce(.Add,row_vec*col);}while(i<vector.len):(i+=1){r.*+=row[i]*vector[i];}}}

On fixed sized vector architectures, this will likely do what you expected in the assembly.
On variable sized vector architectures like RISC-V, you will either have an additional loop inside of the first while-loop because of correctness or the code was substantially massaged by the backend to rectify that. But that's hard for the backend and something you normally don't want when you are programming explicitly with SIMD as a programmer.

Now, let's rewrite this as if there would be a SIMD type without an element count which acts like a simd register:

fnmatrix_vector_multiplication(result:[]i32,matrix:[]const[]consti32,vector:[]consti32)void{std.debug.assert(matrix.len>0);std.debug.assert(matrix[0].len==vector.len);std.debug.assert(result.len==matrix.len);for(matrix,result)|row,*r|{std.debug.assert(row.len==vector.len);constvec:@Simd(i32)=vector.*;constrow_vec:@Simd(i32)=row.*;r.*=@reduce(.Add,row_vec*vec);}}

On fixed sized vector architectures this would insert the loop you have to have for it to work.
On variable sized vector architectures, this could be implemented with just two loops too. Here's how this could roughly look like in RISC-V Assembly (without the asserts and assuming len, ptr order in ram with 32bit architecture for slices and possibly with bugs since apparently the RISC-V simulator which I use has a bug which causes a crash which I run into here):

# parameters:
# a0: result.len
# a1: result.ptr
# a2: matrix.len
# a3: matrix.ptr
# a4: vector.len
# a5: vector.ptr
matrix_vector_multiplication:
 beqz a0, end
 # operating vector length
 mv t0, a4
 # operating vector pointer
 mv t1, a5
 # operating row pointer
 lw t2, (a3)
 # result element value
 li t3, 0
inner_loop:
 # element count
 vsetvli t4, t0, e32, m1, ta, ma
 # load vector
 vle32.v v0, (t1)
 # load row
 vle32.v v1, (a3)
 # mul
 vmul.vv v0, v0, v1
 # reduction
 vmv.v.i v1, 0
 vredsum.vs v1, v0, v1
 vmv.x.s t5, v1
 # add to vector value
 add t3, t3, t5
 # modify pointers and lengths
 sub t0, t0, t4
 slli t4, t4, 2
 add t1, t1, t4
 # check if needing to loop
 beqz t0, inner_loop
 sw t3, (a1)
 addi a1, a1, 4
 addi a3, a3, 4
 addi a0, a0, -1
 addi a2, a2, -1
 j matrix_vector_multiplication
end:
 ret
> Second, I don't like how this proposal attempts to merge SIMD vectors and math vectors. They solve different purposes Thanks to the existence of variable length SIMD (like RISC-V), merging them also creates certain problems imo. I'm going to go a bit into how RISC-V's vector extension works: The vector register size is a hardware implementation detail. This means that the load instruction for vector registers receives an address to take data from, a desired length (which is used as a maximum) and saves the actually loaded amount into a register. You can calculate the actual hardware register size from that (or read that in your CPU spec), but this depends on the actual hardware. For example the Zig standard library has written that [256bit as register size](https://github.com/ziglang/zig/blob/738d2be9d6b6ef3ff3559130c05159ef53336224/lib/std/simd.zig#L46) is quite common, but there are also CPUs with [1024bit](https://milkv.io/jupiter2) or some with only 64bit and 128bit is actually also quite common. And additionally you can combine multiple vector registers (see 3.4.2. Vector Register Grouping in the [spec](https://docs.riscv.org/reference/isa/extensions/vector/_attachments/riscv-v-spec.pdf)). This means that you can suddenly operate on e.g. 8192bits in one instruction or even just 1024bits, depending on the CPU. That means that a RISC-V backend/programmer who wants to target any RISC-V CPU with the vector extension has to basically do guesswork in addition to having suboptimal code generation. Let's take this example code (using a naive matrix memory strategy instead of something more complicated like BlockCompressedRowStorage) with probably more asserts than necessary: ```Zig fn matrix_vector_multiplication(result: []i32, matrix: []const []const i32, vector: []const i32) void { std.debug.assert(matrix.len > 0); std.debug.assert(matrix[0].len == vector.len); std.debug.assert(result.len == matrix.len); const simd_length = std.simd.suggestVectorLength(i32) orelse 8; const Vec = @Vector(simd_length, i32); const simd_part = vector.len - vector.len % simd_length; for (matrix, result) |row, *r| { r.* = 0; std.debug.assert(row.len == vector.len); var i: usize = 0; while (i < simd_part) : (i += simd_length) { const row_vec: Vec = row[i..][0..simd_length].*; const col: Vec = vector[i..][0..simd_length].*; r.* += @reduce(.Add, row_vec * col); } while (i < vector.len) : (i += 1) { r.* += row[i] * vector[i]; } } } ``` On fixed sized vector architectures, this will likely do what you expected in the assembly. On variable sized vector architectures like RISC-V, you will either have an additional loop inside of the first while-loop because of correctness or the code was substantially massaged by the backend to rectify that. But that's hard for the backend and something you normally don't want when you are programming explicitly with SIMD as a programmer. Now, let's rewrite this as if there would be a SIMD type without an element count which acts like a simd register: ```Zig fn matrix_vector_multiplication(result: []i32, matrix: []const []const i32, vector: []const i32) void { std.debug.assert(matrix.len > 0); std.debug.assert(matrix[0].len == vector.len); std.debug.assert(result.len == matrix.len); for (matrix, result) |row, *r| { std.debug.assert(row.len == vector.len); const vec: @Simd(i32) = vector.*; const row_vec: @Simd(i32) = row.*; r.* = @reduce(.Add, row_vec * vec); } } ``` On fixed sized vector architectures this would insert the loop you have to have for it to work. On variable sized vector architectures, this could be implemented with just two loops too. Here's how this could roughly look like in RISC-V Assembly (without the asserts and assuming len, ptr order in ram with 32bit architecture for slices and possibly with bugs since apparently the RISC-V simulator which I use has a bug which causes a crash which I run into here): ```asm # parameters: # a0: result.len # a1: result.ptr # a2: matrix.len # a3: matrix.ptr # a4: vector.len # a5: vector.ptr matrix_vector_multiplication: beqz a0, end # operating vector length mv t0, a4 # operating vector pointer mv t1, a5 # operating row pointer lw t2, (a3) # result element value li t3, 0 inner_loop: # element count vsetvli t4, t0, e32, m1, ta, ma # load vector vle32.v v0, (t1) # load row vle32.v v1, (a3) # mul vmul.vv v0, v0, v1 # reduction vmv.v.i v1, 0 vredsum.vs v1, v0, v1 vmv.x.s t5, v1 # add to vector value add t3, t3, t5 # modify pointers and lengths sub t0, t0, t4 slli t4, t4, 2 add t1, t1, t4 # check if needing to loop beqz t0, inner_loop sw t3, (a1) addi a1, a1, 4 addi a3, a3, 4 addi a0, a0, -1 addi a2, a2, -1 j matrix_vector_multiplication end: ret ```

Thanks to the existence of variable length SIMD (like RISC-V), merging them also creates certain problems imo.

I have some ideas about this: turn SIMD vectors into an opaque or runtime-sized type, allow expressing parallel for-loops (something OPM-style?). That all requires SIMD vectors to be a different type too.

> Thanks to the existence of variable length SIMD (like RISC-V), merging them also creates certain problems imo. I have some ideas about this: turn SIMD vectors into an opaque or runtime-sized type, allow expressing parallel for-loops (something OPM-style?). That all requires SIMD vectors to be a different type too.
Author
Owner
Copy link

@castholm: If I understand correctly, there are two main motivators for .array-layout vectors: to better map to native shader vector types than status quo @Vector when targeting e.g. SPIR-V, and to be more useful as general-purpose math vector types in non-shader/GPU programs.
[...]

Agreed on all counts, I intend to use the type this way as well. @Vector(T, N, .array) will have identical in memory representation to [T]N to allow for this usage.

@KilianHanich: Thanks to the existence of variable length SIMD (like RISC-V), merging them also creates certain problems imo.
[...]

Thanks for the explanation of variable size vectors, I didn't realize there were architectures that worked this way!

There are a lot of motivating use cases for fixed sized vector types so we want support for these, but that doesn't mean that this has to be the only way SIMD is done in the language.

Writing those sorts of loops where you get all the aligned data to do SIMD operations on in chunks and then have to deal with the remainder at the end is always a pain. I can see how a feature like this would result in better codegen on RISC-V, and generally make these types of loops easier to write.

It's out of scope for this proposal and I'd have to think more deeply about it to form a real opinion, but I imagine I would end up in favor of something along these lines as a separate feature.

> @castholm: If I understand correctly, there are two main motivators for .array-layout vectors: to better map to native shader vector types than status quo @Vector when targeting e.g. SPIR-V, and to be more useful as general-purpose math vector types in non-shader/GPU programs. > [...] Agreed on all counts, I intend to use the type this way as well. `@Vector(T, N, .array)` will have identical in memory representation to `[T]N` to allow for this usage. > @KilianHanich: Thanks to the existence of variable length SIMD (like RISC-V), merging them also creates certain problems imo. > [...] Thanks for the explanation of variable size vectors, I didn't realize there were architectures that worked this way! There are a lot of motivating use cases for fixed sized vector types so we want support for these, but that doesn't mean that this has to be the only way SIMD is done in the language. Writing those sorts of loops where you get all the aligned data to do SIMD operations on in chunks and then have to deal with the remainder at the end is always a pain. I can see how a feature like this would result in better codegen on RISC-V, and generally make these types of loops easier to write. It's out of scope for this proposal and I'd have to think more deeply about it to form a real opinion, but I imagine I would end up in favor of something along these lines as a separate feature.

Thanks for the explanation of variable size vectors, I didn't realize there were architectures that worked this way!

ARM went this route too, but I am not really knowledgeable about ARM Assembly, so I used RISC-V here. I also stumble upon some marketing material from ARM that they are working on a Scalable Matrix Extension which is apparently implement by some rare CPUs. But I didn't read up on it much.

There are a lot of motivating use cases for fixed sized vector types so we want support for these, but that doesn't mean that this has to be the only way SIMD is done in the language.

That's why imo splitting @Vector into one type for mathematical vectors and one for hardware simd would be a good idea.

This would also make it clear for which purpose you use these types in your code which I personally consider quite important.

> Thanks for the explanation of variable size vectors, I didn't realize there were architectures that worked this way! ARM went this route too, but I am not really knowledgeable about ARM Assembly, so I used RISC-V here. I also stumble upon some marketing material from ARM that they are working on a Scalable Matrix Extension which is apparently implement by some rare CPUs. But I didn't read up on it much. > There are a lot of motivating use cases for fixed sized vector types so we want support for these, but that doesn't mean that this has to be the only way SIMD is done in the language. That's why imo splitting `@Vector` into one type for mathematical vectors and one for hardware simd would be a good idea. This would also make it clear for which purpose you use these types in your code which I personally consider quite important.
Contributor
Copy link

i concur with the others here, while the two different layouts expressed here are both types of "vectors" they are fundamentally different types and hiding the difference in a property of a type feels like a loaded footgun to me

this especially coming from my c# background where we have Vector3/Vector4 for math and also simultaneously Vector128/Vector256/Vector512/Vector<T> for simd (the last being a best-guess just give me a usable vector option that nobody uses because the codegen isnt good with it compared to using the jit equivalent of comptime ifs to pick the right vector size directly)

the amount of times i and others have mixed up Vector3/4 with Vector in particular is damning of the naming

i concur with the others here, while the two different layouts expressed here are both types of "vectors" they are fundamentally different types and hiding the difference in a property of a type feels like a loaded footgun to me this especially coming from my c# background where we have `Vector3`/`Vector4` for math and also simultaneously `Vector128`/`Vector256`/`Vector512`/`Vector<T>` for simd (the last being a best-guess just give me a usable vector option that nobody uses because the codegen isnt good with it compared to using the jit equivalent of comptime ifs to pick the right vector size directly) the amount of times i and others have mixed up Vector3/4 with Vector<T> in particular is damning of the naming

@Vector(T, N, .array) will have identical in memory representation to [T]N to allow for this usage.

This reminds me of https://github.com/ziglang/zig/issues/23327, so I'll just throw this out here:
Wouldn't it be simpler to just add the operators and swizziling to arrays instead of adding a new argument to the already quite verbose vector declaration? I think this could also work better with https://github.com/ziglang/zig/issues/7305

> `@Vector(T, N, .array)` will have identical in memory representation to `[T]N` to allow for this usage. This reminds me of https://github.com/ziglang/zig/issues/23327, so I'll just throw this out here: Wouldn't it be simpler to just add the operators and swizziling to arrays instead of adding a new argument to the already quite verbose vector declaration? I think this could also work better with https://github.com/ziglang/zig/issues/7305
Contributor
Copy link

This reminds me of https://github.com/ziglang/zig/issues/23327, so I'll just throw this out here: Wouldn't it be simpler to just add the operators and swizziling to arrays instead of adding a new argument to the already quite verbose vector declaration?

I'm not a big Odin user but I think this is how Odin does it: specific types for SIMD, and arrays for more typical vectors
https://odin-lang.org/docs/overview/#array-programming
https://pkg.odin-lang.org/core/simd/

> This reminds me of https://github.com/ziglang/zig/issues/23327, so I'll just throw this out here: Wouldn't it be simpler to just add the operators and swizziling to arrays instead of adding a new argument to the already quite verbose vector declaration? I'm not a big Odin user but I think this is how Odin does it: specific types for SIMD, and arrays for more typical vectors https://odin-lang.org/docs/overview/#array-programming https://pkg.odin-lang.org/core/simd/

@IntegratedQuantum wrote in #35376 (comment):

@Vector(T, N, .array) will have identical in memory representation to [T]N to allow for this usage.

This reminds me of https://github.com/ziglang/zig/issues/23327, so I'll just throw this out here: Wouldn't it be simpler to just add the operators and swizziling to arrays instead of adding a new argument to the already quite verbose vector declaration? I think this could also work better with https://github.com/ziglang/zig/issues/7305

There's a couple of reasons I'd argue adding operators to <=4 length arrays isn't a good idea. We'd be extending non-obvious operator behavior to these arrays, even when these are not meant to represent a vector as in graphics/games. In general, Zig aims to be "non surprising", and that seems surprising to me.

The verbose vector declaration would be aliased by user or library code virtually in all cases, so I don't see that as a big issue.


@MasonRemaley: great proposal, really good work.

I still think there might be a point in including vec2 vec3 vec4 as types just for the sake of having a common standard on how to call these (just picking the GLM names seems to be a good choice), so that you read any zig code and you can tell what the representation of vec3 or dvec2 is as clearly as you can with a u32. Leaving it to libraries potentially can lead to fragmentation of standards.

Since we expect that everyone will alias these anyways, why not provide a default one for common cases, leaving parametric @Vector to handle the rest?

@IntegratedQuantum wrote in https://codeberg.org/ziglang/zig/issues/35376#issuecomment-15711083: > > `@Vector(T, N, .array)` will have identical in memory representation to `[T]N` to allow for this usage. > > This reminds me of https://github.com/ziglang/zig/issues/23327, so I'll just throw this out here: Wouldn't it be simpler to just add the operators and swizziling to arrays instead of adding a new argument to the already quite verbose vector declaration? I think this could also work better with https://github.com/ziglang/zig/issues/7305 There's a couple of reasons I'd argue adding operators to <=4 length arrays isn't a good idea. We'd be extending non-obvious operator behavior to these arrays, even when these are not meant to represent a vector as in graphics/games. In general, Zig aims to be "non surprising", and that seems surprising to me. The verbose vector declaration would be aliased by user or library code virtually in all cases, so I don't see that as a big issue. --- @MasonRemaley: great proposal, really good work. I still think there might be a point in including `vec2` `vec3` `vec4` as types just for the sake of having a common standard on how to call these (just picking the GLM names seems to be a good choice), so that you read any zig code and you can tell what the representation of `vec3` or `dvec2` is as clearly as you can with a `u32`. Leaving it to libraries potentially can lead to fragmentation of standards. Since we expect that everyone will alias these anyways, why not provide a default one for common cases, leaving parametric `@Vector` to handle the rest?
Author
Owner
Copy link

@claudiofreda wrote in #35376 (comment):

There's a couple of reasons I'd argue adding operators to <=4 length arrays isn't a good idea. We'd be extending non-obvious operator behavior to of these arrays, even when these are not meant to represent a vector as in graphics/games. In general, Zig aims to be "non surprising", and that seems surprising to me.

The verbose vector declaration would be aliased by user or library code virtually in all cases, so I don't see that as a big issue.

Additionally, arrays may contain elements that don't support arithmetic or equality.

@claudiofreda wrote in #35376 (comment):

@MasonRemaley: great proposal, really good work.

I still think there might be a point in including vec2 vec3 vec4 as types just for the sake of having a common standard on how to call these (just picking the GLM names seems to be a good choice), so that you read any zig code and you can tell what the representation of vec3 or dvec2 is as clearly as you can with a u32. Leaving it to libraries potentially can lead to fragmentation of standards.

Since we expect that everyone will alias these anyways, why not provide a default one for common cases, leaving parametric @Vector to handle the rest?

Thanks!

I think there are two potential benefits to providing built in names for these:

  1. Encouraging consistent naming
  2. Easier to read error messages

Given that the type system doesn't care if the names are consistent and any reasonable name for a vector 3 should be easy to understand, I'm not sure I'd want to introduce a new keyword for point 1 alone. We can always encourage a specific naming convention by using it in the langref.

A lower commitment solution would be to alias them in std, but this doesn't address issue 2. My inclination is to continue implementing the proposal as is without built in aliases, and see if my opinion is shifted one way or the other as I port my game and libraries to use these new types.

@claudiofreda wrote in https://codeberg.org/ziglang/zig/issues/35376#issuecomment-16336394: > There's a couple of reasons I'd argue adding operators to <=4 length arrays isn't a good idea. We'd be extending non-obvious operator behavior to of these arrays, even when these are not meant to represent a vector as in graphics/games. In general, Zig aims to be "non surprising", and that seems surprising to me. > > The verbose vector declaration would be aliased by user or library code virtually in all cases, so I don't see that as a big issue. Additionally, arrays may contain elements that don't support arithmetic or equality. @claudiofreda wrote in https://codeberg.org/ziglang/zig/issues/35376#issuecomment-16336394: > @MasonRemaley: great proposal, really good work. > > I still think there might be a point in including `vec2` `vec3` `vec4` as types just for the sake of having a common standard on how to call these (just picking the GLM names seems to be a good choice), so that you read any zig code and you can tell what the representation of `vec3` or `dvec2` is as clearly as you can with a `u32`. Leaving it to libraries potentially can lead to fragmentation of standards. > > Since we expect that everyone will alias these anyways, why not provide a default one for common cases, leaving parametric `@Vector` to handle the rest? Thanks! I think there are two potential benefits to providing built in names for these: 1. Encouraging consistent naming 2. Easier to read error messages Given that the type system doesn't care if the names are consistent and any reasonable name for a vector 3 should be easy to understand, I'm not sure I'd want to introduce a new keyword for point 1 alone. We can always encourage a specific naming convention by using it in the langref. A lower commitment solution would be to alias them in std, but this doesn't address issue 2. My inclination is to continue implementing the proposal as is without built in aliases, and see if my opinion is shifted one way or the other as I port my game and libraries to use these new types.

I agree that your proposal makes sense regardless of the suggestion to add keywords, which could be discussed again as we start writing code based on this.

I agree that your proposal makes sense regardless of the suggestion to add keywords, which could be discussed again as we start writing code based on this.

Somewhat related: #35960

Somewhat related: #35960
Sign in to join this conversation.
No Branch/Tag specified
master
elfv2
spork8
restricted
0.16.x
panic-rewrite
parking-futex-lockfree
windows-Io-cleanup
Io-watch
ParseCommandLineOptions
windows-async-files
poll-ring
debug-file-leaks-differently
debug-file-leaks
ProcessPrng
elfv2-dyn
jobserver
threadtheft
io-threaded-no-queue
0.15.x
Io.net
comptime-allocator
restricted-function-pointers
cli
wasm-linker-writer
wrangle-writer-buffering
sha1-stream
async-await-demo
fixes
0.14.x
ast-node-methods
macos-debug-info
make-vs-configure
fuzz-macos
sans-aro
ArrayList-reserve
incr-bug
llvm-ir-nosanitize-metadata
ci-tarballs
ci-scripts
threadpool
0.12.x
new-pkg-hash
json-diagnostics
more-doctests
rework-comptime-mutation
0.11.x
ci-perf-comment
stage2-async
0.10.x
autofix
0.9.x
aro
hcs
0.8.x
0.7.x
0.16.0
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.12.1
0.13.0
0.12.0
0.11.0
0.10.1
0.10.0
0.9.1
0.9.0
0.8.1
0.8.0
0.7.1
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.1
0.1.0
Labels
Clear labels
abi/f32
abi/ilp32
abi/sf
accepted
This proposal is planned.
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
The web application for interactive documentation and generation of its assets.
backend/c
The C backend outputs C source code.
backend/llvm
The LLVM backend outputs an LLVM bitcode module.
backend/self-hosted
The self-hosted backends produce machine code directly.
binutils
Zig's included binary utilities: zig ar, zig dlltool, zig lib, zig ranlib, zig objcopy, and zig rc.
breaking
Implementing this issue could cause existing code to no longer compile or have different behavior.
build system
The Zig build system - zig build, std.Build, the build runner, and package management.
debug info
An issue related to debug information (e.g. DWARF) produced by the Zig compiler.
docs
An issue with documentation, e.g. the language reference or standard library doc comments.
error message
This issue points out an error message that is unhelpful and should be improved.
frontend
Tokenization, parsing, AstGen, ZonGen, Sema, Legalize, and Liveness.
fuzzing
An issue related to Zig's integrated fuzz testing.
incremental
Reuse of internal compiler state for faster compilation.
lib/c
This issue relates to Zig's libc implementation and/or vendored libcs.
lib/compiler-rt
This issue relates to Zig's compiler-rt library.
lib/cxx
This issue relates to Zig's vendored libc++ and/or libc++abi.
lib/std
This issue relates to Zig's standard library.
lib/tsan
This issue relates to Zig's vendored libtsan.
lib/ubsan-rt
This issue relates to Zig's ubsan-rt library.
lib/unwind
This issue relates to Zig's vendored libunwind.
linking
Zig's integrated object file and incremental linker.
miscompilation
The compiler reports success but produces semantically incorrect code.
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
This issue suggests language modifications. If it also has the "accepted" label then it is planned.
release notes
This issue or pull request should be mentioned in the release notes.
testing
This issue is related to testing the compiler, standard library, or other parts of Zig.
zig cc
Zig as a drop-in C-family compiler.
zig fmt
The Zig source code formatter.
zig reduce
The Zig source code reduction tool.
bounty
https://ziglang.org/news/announcing-donor-bounties
bug
Observed behavior contradicts documented or intended behavior.
contributor-friendly
This issue is limited in scope and/or knowledge of project internals.
downstream
An issue with a third-party project that uses this project.
enhancement
Solving this issue will likely involve adding new logic or components to the codebase.
infra
An issue related to project infrastructure, e.g. continuous integration.
optimization
A task to improve performance and/or resource usage.
question
No questions on the issue tracker; use a community space instead.
regression
Something that used to work in a previous version stopped working
upstream
An issue with a third-party project that this project uses.
use case
Describes a real use case that is difficult or impossible, but does not propose a solution.
No labels
abi/f32
abi/ilp32
abi/sf
accepted
arch/21k
arch/6502
arch/aarch64
arch/alpha
arch/amdgcn
arch/arc
arch/arc32
arch/arc64
arch/arm
arch/avr
arch/bfin
arch/bpf
arch/colossus
arch/cris
arch/csky
arch/dlx
arch/epiphany
arch/fr30
arch/frv
arch/hexagon
arch/hppa
arch/hppa64
arch/ia64
arch/kalimba
arch/kvx
arch/lanai
arch/lm32
arch/loongarch32
arch/loongarch64
arch/m32r
arch/m68k
arch/m88k
arch/mcore
arch/microblaze
arch/mips
arch/mips64
arch/mmix
arch/moxie
arch/mrisc32
arch/msp430
arch/nds32
arch/ns32k
arch/nvptx
arch/or1k
arch/powerpc
arch/powerpc64
arch/propeller
arch/riscv32
arch/riscv64
arch/rl78
arch/rx
arch/s390x
arch/sh
arch/sparc
arch/sparc64
arch/spirv
arch/spu
arch/tricore
arch/v850
arch/vax
arch/vc4
arch/ve
arch/wasm
arch/x86
arch/x86_64
arch/xcore
arch/xtensa
autodoc
backend/c
backend/llvm
backend/self-hosted
binutils
breaking
build system
debug info
docs
error message
frontend
fuzzing
incremental
lib/c
lib/compiler-rt
lib/cxx
lib/std
lib/tsan
lib/ubsan-rt
lib/unwind
linking
miscompilation
os/android
os/contiki
os/dragonfly
os/driverkit
os/emscripten
os/freebsd
os/fuchsia
os/haiku
os/hermit
os/hurd
os/illumos
os/ios
os/linux
os/maccatalyst
os/macos
os/managarm
os/netbsd
os/ohos
os/openbsd
os/plan9
os/redox
os/rtems
os/serenity
os/tvos
os/uefi
os/visionos
os/wasi
os/watchos
os/windows
proposal
release notes
testing
zig cc
zig fmt
zig reduce
bounty
bug
contributor-friendly
downstream
enhancement
infra
optimization
question
regression
upstream
use case
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
11 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ziglang/zig#35376
Reference in a new issue
ziglang/zig
No description provided.
Delete branch "%!s()"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?