Skip to content

Commit 203ddc6

Browse files
authored
Merge branch 'Rust-GPU:main' into main
2 parents a072716 + 08f98a7 commit 203ddc6

34 files changed

+446
-53
lines changed

.github/workflows/ci.yaml

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ concurrency:
1313
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
1414
cancel-in-progress: true
1515

16+
permissions: {}
17+
1618
jobs:
1719
test:
1820
name: Test
1921
strategy:
2022
fail-fast: false
2123
matrix:
22-
os: [ ubuntu-24.04, windows-2022, macOS-latest ]
24+
os: [ ubuntu-latest, windows-latest, macOS-latest ]
2325
runs-on: ${{ matrix.os }}
2426
env:
2527
RUSTUP_UNPACK_RAM: "26214400"
@@ -79,7 +81,7 @@ jobs:
7981
name: Android
8082
strategy:
8183
matrix:
82-
os: [ ubuntu-24.04 ]
84+
os: [ ubuntu-latest ]
8385
target: [ aarch64-linux-android ]
8486
runs-on: ${{ matrix.os }}
8587
env:
@@ -127,7 +129,7 @@ jobs:
127129
strategy:
128130
fail-fast: false
129131
matrix:
130-
os: [ ubuntu-24.04, windows-2022, macOS-latest ]
132+
os: [ ubuntu-latest, windows-latest, macOS-latest ]
131133
runs-on: ${{ matrix.os }}
132134
steps:
133135
- uses: actions/checkout@v4
@@ -150,7 +152,7 @@ jobs:
150152
strategy:
151153
fail-fast: false
152154
matrix:
153-
os: [ ubuntu-24.04, windows-2022, macOS-latest ]
155+
os: [ ubuntu-latest, windows-latest, macOS-latest ]
154156
runs-on: ${{ matrix.os }}
155157
steps:
156158
- uses: actions/checkout@v4
@@ -161,16 +163,8 @@ jobs:
161163
install_runtime: true
162164
cache: true
163165
stripdown: true
164-
165-
# FIXME(eddyb) consider using lavapipe instead, or even trying both.
166-
install_swiftshader: true
167-
# install_lavapipe: true
168-
- if: ${{ runner.os == 'Windows' }}
169-
name: Windows - Use SwiftShader as Vulkan driver
170-
# FIXME(eddyb) ideally `jakoch/install-vulkan-sdk-action` should do this.
171-
run: |
172-
echo "C:/Swiftshader/" >> "$GITHUB_PATH"
173-
echo "VK_DRIVER_FILES=C:/Swiftshader/vk_swiftshader_icd.json" >> "$GITHUB_ENV"
166+
install_lavapipe: true
167+
github_token: ${{ secrets.GITHUB_TOKEN }}
174168
- if: ${{ runner.os == 'Linux' }}
175169
name: Linux - Install native dependencies
176170
run: sudo apt install libwayland-cursor0 libxkbcommon-dev libwayland-dev
@@ -195,7 +189,7 @@ jobs:
195189
# This allows us to have a single job we can branch protect on, rather than needing
196190
# to update the branch protection rules when the test matrix changes
197191
test_success:
198-
runs-on: ubuntu-24.04
192+
runs-on: ubuntu-latest
199193
needs: [test, compiletest, difftest, android, lint, cargo-deny]
200194
# Hack for buggy GitHub Actions behavior with skipped checks: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks
201195
if: ${{ always() }}
@@ -212,7 +206,7 @@ jobs:
212206
213207
lint:
214208
name: Lint
215-
runs-on: ubuntu-24.04
209+
runs-on: ubuntu-latest
216210
steps:
217211
# Note that we are explicitly NOT checking out submodules, to validate
218212
# that we haven't accidentally enabled spirv-tools native compilation
@@ -254,7 +248,7 @@ jobs:
254248
run: .github/workflows/lint.sh
255249

256250
cargo-deny:
257-
runs-on: ubuntu-24.04
251+
runs-on: ubuntu-latest
258252
steps:
259253
- uses: actions/checkout@v4
260254
- uses: EmbarkStudios/cargo-deny-action@v2

crates/rustc_codegen_spirv/src/codegen_cx/entry.rs

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::builder_spirv::{SpirvFunctionCursor, SpirvValue, SpirvValueExt};
99
use crate::spirv_type::SpirvType;
1010
use rspirv::dr::Operand;
1111
use rspirv::spirv::{
12-
Capability, Decoration, Dim, ExecutionModel, FunctionControl, StorageClass, Word,
12+
BuiltIn, Capability, Decoration, Dim, ExecutionModel, FunctionControl, StorageClass, Word,
1313
};
1414
use rustc_abi::FieldsShape;
1515
use rustc_codegen_ssa::traits::{BaseTypeCodegenMethods, BuilderMethods, MiscCodegenMethods as _};
@@ -722,8 +722,9 @@ impl<'tcx> CodegenCx<'tcx> {
722722
Ok(StorageClass::Input | StorageClass::Output | StorageClass::UniformConstant)
723723
);
724724
let mut assign_location = |var_id: Result<Word, &str>, explicit: Option<u32>| {
725+
let storage_class = storage_class.unwrap();
725726
let location = decoration_locations
726-
.entry(storage_class.unwrap())
727+
.entry(storage_class)
727728
.or_insert_with(|| 0);
728729
if let Some(explicit) = explicit {
729730
*location = explicit;
@@ -733,7 +734,46 @@ impl<'tcx> CodegenCx<'tcx> {
733734
Decoration::Location,
734735
std::iter::once(Operand::LiteralBit32(*location)),
735736
);
736-
let spirv_type = self.lookup_type(value_spirv_type);
737+
let mut spirv_type = self.lookup_type(value_spirv_type);
738+
739+
// These shader types and storage classes skip the outer array or pointer of the declaration when computing
740+
// the location layout, see bug at https://github.com/Rust-GPU/rust-gpu/issues/500.
741+
//
742+
// The match statment follows the rules at:
743+
// https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#interfaces-iointerfaces-matching
744+
#[allow(clippy::match_same_arms)]
745+
let can_skip_outer_array =
746+
match (execution_model, storage_class, attrs.per_primitive_ext) {
747+
// > if the input is declared in a tessellation control or geometry shader...
748+
(
749+
ExecutionModel::TessellationControl | ExecutionModel::Geometry,
750+
StorageClass::Input,
751+
_,
752+
) => true,
753+
// > if the maintenance4 feature is enabled, they are declared as OpTypeVector variables, and the
754+
// > output has a Component Count value higher than that of the input but the same Component Type
755+
// Irrelevant: This allows a vertex shader to output a Vec4 and a fragment shader to accept a vector
756+
// type with fewer components, like Vec3, Vec2 (or f32?). Which has no influence on locations.
757+
// > if the output is declared in a mesh shader...
758+
(ExecutionModel::MeshEXT | ExecutionModel::MeshNV, StorageClass::Output, _) => {
759+
true
760+
}
761+
// > if the input is decorated with PerVertexKHR, and is declared in a fragment shader...
762+
(ExecutionModel::Fragment, StorageClass::Input, Some(_)) => true,
763+
// > if in any other case...
764+
(_, _, _) => false,
765+
};
766+
if can_skip_outer_array {
767+
spirv_type = match spirv_type {
768+
SpirvType::Array { element, .. }
769+
| SpirvType::RuntimeArray { element, .. }
770+
| SpirvType::Pointer {
771+
pointee: element, ..
772+
} => self.lookup_type(element),
773+
e => e,
774+
};
775+
}
776+
737777
if let Some(location_size) = spirv_type.location_size(self) {
738778
*location += location_size;
739779
} else {
@@ -916,6 +956,11 @@ impl<'tcx> CodegenCx<'tcx> {
916956
);
917957
}
918958

959+
// Check builtin-specific type requirements.
960+
if let Some(builtin) = attrs.builtin {
961+
self.check_builtin_type(hir_param.ty_span, value_layout.ty, builtin);
962+
}
963+
919964
if let Ok(storage_class) = storage_class {
920965
self.check_for_bad_types(
921966
execution_model,
@@ -1083,4 +1128,15 @@ impl<'tcx> CodegenCx<'tcx> {
10831128
}
10841129
}
10851130
}
1131+
1132+
/// Check that builtin variables have the correct type.
1133+
fn check_builtin_type(&self, span: Span, rust_ty: Ty<'tcx>, builtin: Spanned<BuiltIn>) {
1134+
// LocalInvocationIndex must be a u32.
1135+
if builtin.value == BuiltIn::LocalInvocationIndex && rust_ty != self.tcx.types.u32 {
1136+
self.tcx.dcx().span_err(
1137+
span,
1138+
format!("`#[spirv(local_invocation_index)]` must be a `u32`, not `{rust_ty}`"),
1139+
);
1140+
}
1141+
}
10861142
}

crates/rustc_codegen_spirv/src/linker/dce.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,15 @@ fn instruction_is_pure(inst: &Instruction) -> bool {
282282
| PtrEqual
283283
| PtrNotEqual
284284
| PtrDiff => true,
285-
Variable => inst.operands.first() == Some(&Operand::StorageClass(StorageClass::Function)),
285+
// Variables with Function or Private storage class are pure and can be DCE'd if unused.
286+
// Other storage classes (Input, Output, Uniform, etc.) are part of the shader interface
287+
// and must be kept.
288+
Variable => matches!(
289+
inst.operands.first(),
290+
Some(&Operand::StorageClass(
291+
StorageClass::Function | StorageClass::Private
292+
))
293+
),
286294
_ => false,
287295
}
288296
}

crates/rustc_codegen_spirv/src/linker/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,16 @@ pub fn link(
415415
inline::inline(sess, &mut output)?;
416416
}
417417

418+
// Fold OpLoad from Private/Function variables with constant initializers.
419+
// This must run after inlining (which may expose such patterns) and before DCE
420+
// (so that the now-unused variables can be removed).
421+
// This is critical for pointer-to-pointer patterns like `&&123` which would
422+
// otherwise generate invalid SPIR-V in Logical addressing mode.
423+
{
424+
let _timer = sess.timer("link_fold_load_from_constant_variable");
425+
peephole_opts::fold_load_from_constant_variable(&mut output);
426+
}
427+
418428
{
419429
let _timer = sess.timer("link_dce-after-inlining");
420430
dce::dce(&mut output);

crates/rustc_codegen_spirv/src/linker/peephole_opts.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,3 +606,77 @@ pub fn bool_fusion(
606606
}
607607
super::apply_rewrite_rules(&rewrite_rules, &mut function.blocks);
608608
}
609+
610+
/// Fold `OpLoad` from Private/Function storage class variables that have constant initializers.
611+
///
612+
/// This optimization handles patterns like:
613+
/// ```text
614+
/// %ptr = OpVariable %_ptr_Private_T Private %initializer
615+
/// %val = OpLoad %T %ptr
616+
/// ```
617+
/// After this optimization, uses of `%val` are replaced with `%initializer`.
618+
///
619+
/// This is particularly important for pointer-to-pointer constants (e.g., `&&123`)
620+
/// where the outer pointer variable has a known initializer (the inner pointer).
621+
/// Without this optimization, such patterns generate invalid SPIR-V in Logical
622+
/// addressing mode (pointer-to-pointer with Private storage class is not allowed).
623+
pub fn fold_load_from_constant_variable(module: &mut Module) {
624+
use rspirv::spirv::StorageClass;
625+
626+
// Build a map of variable ID -> initializer ID for Private/Function variables
627+
// that have constant initializers.
628+
let var_initializers: FxHashMap<Word, Word> = module
629+
.types_global_values
630+
.iter()
631+
.filter_map(|inst| {
632+
if inst.class.opcode != Op::Variable {
633+
return None;
634+
}
635+
// Check storage class - only Private and Function can be folded
636+
let storage_class = inst.operands.first()?.unwrap_storage_class();
637+
if !matches!(
638+
storage_class,
639+
StorageClass::Private | StorageClass::Function
640+
) {
641+
return None;
642+
}
643+
// Check for initializer (second operand after storage class)
644+
let initializer = inst.operands.get(1)?.id_ref_any()?;
645+
Some((inst.result_id?, initializer))
646+
})
647+
.collect();
648+
649+
if var_initializers.is_empty() {
650+
return;
651+
}
652+
653+
// Rewrite OpLoad instructions that load from variables with known initializers
654+
let mut rewrite_rules: FxHashMap<Word, Word> = FxHashMap::default();
655+
656+
for func in &mut module.functions {
657+
for block in &mut func.blocks {
658+
for inst in &mut block.instructions {
659+
if inst.class.opcode != Op::Load {
660+
continue;
661+
}
662+
// OpLoad has pointer as first operand
663+
let ptr_id = inst.operands[0].unwrap_id_ref();
664+
if let Some(&initializer) = var_initializers.get(&ptr_id) {
665+
// This load can be replaced with the initializer value
666+
if let Some(result_id) = inst.result_id {
667+
rewrite_rules.insert(result_id, initializer);
668+
// Turn this instruction into a Nop
669+
*inst = Instruction::new(Op::Nop, None, None, Vec::new());
670+
}
671+
}
672+
}
673+
}
674+
}
675+
676+
if !rewrite_rules.is_empty() {
677+
// Apply rewrite rules to all functions
678+
for func in &mut module.functions {
679+
super::apply_rewrite_rules(&rewrite_rules, &mut func.blocks);
680+
}
681+
}
682+
}

tests/compiletests/ui/arch/shared/dce_shared.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ pub fn main(
2121
#[spirv(descriptor_set = 0, binding = 1, storage_buffer)] output: &mut f32,
2222
#[spirv(workgroup)] used_shared: &mut f32,
2323
#[spirv(workgroup)] dce_shared: &mut [i32; 2],
24-
#[spirv(local_invocation_index)] inv_id: UVec3,
24+
#[spirv(local_invocation_index)] inv_id: u32,
2525
) {
2626
unsafe {
27-
let inv_id = inv_id.x as usize;
27+
let inv_id = inv_id as usize;
2828
if inv_id == 0 {
2929
*used_shared = *input;
3030
}

tests/compiletests/ui/arch/shared/dce_shared.stderr

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,15 @@ OpDecorate %4 BuiltIn LocalInvocationIndex
2020
%11 = OpTypePointer Workgroup %9
2121
%12 = OpTypeInt 32 0
2222
%13 = OpConstant %12 2
23-
%14 = OpTypeVector %12 3
24-
%15 = OpTypePointer Input %14
25-
%16 = OpTypeVoid
26-
%17 = OpTypeFunction %16
27-
%18 = OpTypePointer StorageBuffer %9
23+
%14 = OpTypePointer Input %12
24+
%15 = OpTypeVoid
25+
%16 = OpTypeFunction %15
26+
%17 = OpTypePointer StorageBuffer %9
2827
%2 = OpVariable %10 StorageBuffer
29-
%19 = OpConstant %12 0
28+
%18 = OpConstant %12 0
3029
%3 = OpVariable %10 StorageBuffer
31-
%4 = OpVariable %15 Input
32-
%20 = OpTypeBool
30+
%4 = OpVariable %14 Input
31+
%19 = OpTypeBool
3332
%5 = OpVariable %11 Workgroup
34-
%21 = OpConstant %12 264
35-
%22 = OpConstant %12 1
33+
%20 = OpConstant %12 264
34+
%21 = OpConstant %12 1

tests/compiletests/ui/arch/shared/reduction_array.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ pub fn main(
5757
#[spirv(descriptor_set = 0, binding = 0, storage_buffer)] input: &[Value],
5858
#[spirv(descriptor_set = 0, binding = 1, storage_buffer)] output: &mut Value,
5959
#[spirv(workgroup)] shared: &mut [Value; WG_SIZE],
60-
#[spirv(local_invocation_index)] inv_id: UVec3,
60+
#[spirv(local_invocation_index)] inv_id: u32,
6161
) {
6262
unsafe {
63-
let inv_id = inv_id.x as usize;
63+
let inv_id = inv_id as usize;
6464
shared[inv_id] = input[inv_id];
6565
workgroup_memory_barrier_with_group_sync();
6666

tests/compiletests/ui/arch/shared/reduction_big_struct.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ pub fn main(
6565
#[spirv(descriptor_set = 0, binding = 0, storage_buffer)] input: &[Value],
6666
#[spirv(descriptor_set = 0, binding = 1, storage_buffer)] output: &mut Value,
6767
#[spirv(workgroup)] shared: &mut [Value; WG_SIZE],
68-
#[spirv(local_invocation_index)] inv_id: UVec3,
68+
#[spirv(local_invocation_index)] inv_id: u32,
6969
) {
7070
unsafe {
71-
let inv_id = inv_id.x as usize;
71+
let inv_id = inv_id as usize;
7272
shared[inv_id] = input[inv_id];
7373
workgroup_memory_barrier_with_group_sync();
7474

tests/compiletests/ui/arch/shared/reduction_u32.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ pub fn main(
2222
#[spirv(descriptor_set = 0, binding = 0, storage_buffer)] input: &[Value],
2323
#[spirv(descriptor_set = 0, binding = 1, storage_buffer)] output: &mut Value,
2424
#[spirv(workgroup)] shared: &mut [Value; WG_SIZE],
25-
#[spirv(local_invocation_index)] inv_id: UVec3,
25+
#[spirv(local_invocation_index)] inv_id: u32,
2626
) {
2727
unsafe {
28-
let inv_id = inv_id.x as usize;
28+
let inv_id = inv_id as usize;
2929
shared[inv_id] = input[inv_id];
3030
workgroup_memory_barrier_with_group_sync();
3131

0 commit comments

Comments
 (0)