Skip to content

Commit 05e49de

Browse files
Make Force Loop Bounding Optional
Co-authored-by: rudderbucky <anandkwork7@gmail.com>
1 parent 1a346ec commit 05e49de

File tree

23 files changed

+155
-66
lines changed

23 files changed

+155
-66
lines changed

CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ By @cwfitzgerald in [#6619](https://github.com/gfx-rs/wgpu/pull/6619).
5353
A regression intoduced in 23.0.0 caused lifetimes of render and compute passes to be incorrectly enforced. While this is not
5454
a soundness issue, the intent is to move an error from runtime to compile time. This issue has been fixed and restored to the 22.0.0 behavior.
5555

56+
### `Device::create_shader_module_unchecked` Now Has Configuration Options
57+
58+
This allows you to customize which exact checks are omitted so that you can get the correct balance of performance and safety for your use case. Calling the function is still unsafe, but now can be used to skip certain checks only on certain builds.
59+
60+
This also allows users to disable the workarounds in the `msl-out` backend to prevent the compiler from optimizing infinite loops. This can have a big impact on performance, but is not recommended for untrusted shaders.
61+
62+
```diff
63+
let desc: ShaderModuleDescriptor = include_wgsl!(...)
64+
- let module = unsafe { device.create_shader_module_unchecked(desc) };
65+
+ let module = unsafe { device.create_shader_module_unchecked(desc, wgpu::ShaderRuntimeChecks::unchecked()) };
66+
```
67+
68+
By @cwfitzgerald and @rudderbucky in [#6662](https://github.com/gfx-rs/wgpu/pull/6662).
69+
5670
### The `diagnostic(…);` directive is now supported in WGSL
5771

5872
Naga now parses `diagnostic(…);` directives according to the WGSL spec. This allows users to control certain lints, similar to Rust's `allow`, `warn`, and `deny` attributes. For example, in standard WGSL (but, notably, not Naga yet—see <https://github.com/gfx-rs/wgpu/issues/4369>) this snippet would emit a uniformity error:

deno_webgpu/shader.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub fn op_webgpu_create_shader_module(
4242

4343
let descriptor = wgpu_core::pipeline::ShaderModuleDescriptor {
4444
label: Some(label),
45-
shader_bound_checks: wgpu_types::ShaderBoundChecks::default(),
45+
runtime_checks: wgpu_types::ShaderRuntimeChecks::default(),
4646
};
4747

4848
gfx_put!(instance.device_create_shader_module(

naga/src/back/msl/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,9 @@ pub struct Options {
211211
pub bounds_check_policies: index::BoundsCheckPolicies,
212212
/// Should workgroup variables be zero initialized (by polyfilling)?
213213
pub zero_initialize_workgroup_memory: bool,
214+
/// If set, loops will have code injected into them, forcing the compiler
215+
/// to think the number of iterations is bounded.
216+
pub force_loop_bounding: bool,
214217
}
215218

216219
impl Default for Options {
@@ -223,6 +226,7 @@ impl Default for Options {
223226
fake_missing_bindings: true,
224227
bounds_check_policies: index::BoundsCheckPolicies::default(),
225228
zero_initialize_workgroup_memory: true,
229+
force_loop_bounding: true,
226230
}
227231
}
228232
}

naga/src/back/msl/writer.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,8 @@ struct ExpressionContext<'a> {
600600
/// accesses. These may need to be cached in temporary variables. See
601601
/// `index::find_checked_indexes` for details.
602602
guarded_indices: HandleSet<crate::Expression>,
603+
/// See [`emit_force_bounded_loop_macro`] for details.
604+
force_loop_bounding: bool,
603605
}
604606

605607
impl<'a> ExpressionContext<'a> {
@@ -3066,13 +3068,15 @@ impl<W: Write> Writer<W> {
30663068
writeln!(self.out, "{level}while(true) {{",)?;
30673069
}
30683070
self.put_block(level.next(), body, context)?;
3069-
self.emit_force_bounded_loop_macro()?;
3070-
writeln!(
3071-
self.out,
3072-
"{}{}",
3073-
level.next(),
3074-
self.force_bounded_loop_macro_name
3075-
)?;
3071+
if context.expression.force_loop_bounding {
3072+
self.emit_force_bounded_loop_macro()?;
3073+
writeln!(
3074+
self.out,
3075+
"{}{}",
3076+
level.next(),
3077+
self.force_bounded_loop_macro_name
3078+
)?;
3079+
}
30763080
writeln!(self.out, "{level}}}")?;
30773081
}
30783082
crate::Statement::Break => {
@@ -4880,6 +4884,7 @@ template <typename A>
48804884
module,
48814885
mod_info,
48824886
pipeline_options,
4887+
force_loop_bounding: options.force_loop_bounding,
48834888
},
48844889
result_struct: None,
48854890
};
@@ -5780,6 +5785,7 @@ template <typename A>
57805785
module,
57815786
mod_info,
57825787
pipeline_options,
5788+
force_loop_bounding: options.force_loop_bounding,
57835789
},
57845790
result_struct: Some(&stage_out_name),
57855791
};

naga/tests/in/interface.param.ron

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
),
2929
msl_pipeline: (
3030
allow_and_force_point_size: true,
31-
vertex_pulling_transform: false,
32-
vertex_buffer_mappings: [],
31+
vertex_pulling_transform: false,
32+
vertex_buffer_mappings: [],
3333
),
3434
)

wgpu-core/src/device/resource.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1549,7 +1549,7 @@ impl Device {
15491549
});
15501550
let hal_desc = hal::ShaderModuleDescriptor {
15511551
label: desc.label.to_hal(self.instance_flags),
1552-
runtime_checks: desc.shader_bound_checks.runtime_checks(),
1552+
runtime_checks: desc.runtime_checks,
15531553
};
15541554
let raw = match unsafe { self.raw().create_shader_module(&hal_desc, hal_shader) } {
15551555
Ok(raw) => raw,
@@ -1589,7 +1589,7 @@ impl Device {
15891589
self.require_features(wgt::Features::SPIRV_SHADER_PASSTHROUGH)?;
15901590
let hal_desc = hal::ShaderModuleDescriptor {
15911591
label: desc.label.to_hal(self.instance_flags),
1592-
runtime_checks: desc.shader_bound_checks.runtime_checks(),
1592+
runtime_checks: desc.runtime_checks,
15931593
};
15941594
let hal_shader = hal::ShaderInput::SpirV(source);
15951595
let raw = match unsafe { self.raw().create_shader_module(&hal_desc, hal_shader) } {

wgpu-core/src/indirect_validation.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ impl IndirectValidation {
123123
});
124124
let hal_desc = hal::ShaderModuleDescriptor {
125125
label: None,
126-
runtime_checks: false,
126+
runtime_checks: unsafe { wgt::ShaderRuntimeChecks::unchecked() },
127127
};
128128
let module =
129129
unsafe { device.create_shader_module(&hal_desc, hal_shader) }.map_err(|error| {

wgpu-core/src/pipeline.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub enum ShaderModuleSource<'a> {
4141
pub struct ShaderModuleDescriptor<'a> {
4242
pub label: Label<'a>,
4343
#[cfg_attr(feature = "serde", serde(default))]
44-
pub shader_bound_checks: wgt::ShaderBoundChecks,
44+
pub runtime_checks: wgt::ShaderRuntimeChecks,
4545
}
4646

4747
#[derive(Debug)]

wgpu-hal/examples/halmark/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ impl<A: hal::Api> Example<A> {
177177
};
178178
let shader_desc = hal::ShaderModuleDescriptor {
179179
label: None,
180-
runtime_checks: false,
180+
runtime_checks: wgt::ShaderRuntimeChecks::checked(),
181181
};
182182
let shader = unsafe {
183183
device

wgpu-hal/examples/ray-traced-triangle/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ impl<A: hal::Api> Example<A> {
371371
};
372372
let shader_desc = hal::ShaderModuleDescriptor {
373373
label: None,
374-
runtime_checks: false,
374+
runtime_checks: wgt::ShaderRuntimeChecks::checked(),
375375
};
376376
let shader_module = unsafe {
377377
device

wgpu-hal/src/dx12/device.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -273,12 +273,12 @@ impl super::Device {
273273

274274
let needs_temp_options = stage.zero_initialize_workgroup_memory
275275
!= layout.naga_options.zero_initialize_workgroup_memory
276-
|| stage.module.runtime_checks != layout.naga_options.restrict_indexing;
276+
|| stage.module.runtime_checks.bounds_checks() != layout.naga_options.restrict_indexing;
277277
let mut temp_options;
278278
let naga_options = if needs_temp_options {
279279
temp_options = layout.naga_options.clone();
280280
temp_options.zero_initialize_workgroup_memory = stage.zero_initialize_workgroup_memory;
281-
temp_options.restrict_indexing = stage.module.runtime_checks;
281+
temp_options.restrict_indexing = stage.module.runtime_checks.bounds_checks();
282282
&temp_options
283283
} else {
284284
&layout.naga_options

wgpu-hal/src/dx12/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ impl crate::DynPipelineLayout for PipelineLayout {}
967967
pub struct ShaderModule {
968968
naga: crate::NagaShader,
969969
raw_name: Option<ffi::CString>,
970-
runtime_checks: bool,
970+
runtime_checks: wgt::ShaderRuntimeChecks,
971971
}
972972

973973
impl crate::DynShaderModule for ShaderModule {}

wgpu-hal/src/lib.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -2103,6 +2103,10 @@ pub enum ShaderInput<'a> {
21032103
pub struct ShaderModuleDescriptor<'a> {
21042104
pub label: Label<'a>,
21052105

2106+
/// # Safety
2107+
///
2108+
/// ## `runtime_checks.bounds_checks()`:
2109+
///
21062110
/// Enforce bounds checks in shaders, even if the underlying driver doesn't
21072111
/// support doing so natively.
21082112
///
@@ -2122,7 +2126,15 @@ pub struct ShaderModuleDescriptor<'a> {
21222126
/// making such deferred initialization visible to the application.
21232127
///
21242128
/// [ar]: struct.BufferBinding.html#accessible-region
2125-
pub runtime_checks: bool,
2129+
///
2130+
/// ## `runtime_checks.force_loop_bounding()`:
2131+
///
2132+
/// If false, the caller MUST ensure that all passed shaders do not contain any infinite loops.
2133+
///
2134+
/// If it does, backend compilers MAY treat such a loop as unreachable code and draw
2135+
/// conclusions about other safety-critical code paths. This option SHOULD NOT be disabled
2136+
/// when running untrusted code.
2137+
pub runtime_checks: wgt::ShaderRuntimeChecks,
21262138
}
21272139

21282140
#[derive(Debug, Clone)]

wgpu-hal/src/metal/device.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ impl super::Device {
117117

118118
let ep_resources = &layout.per_stage_map[naga_stage];
119119

120-
let bounds_check_policy = if stage.module.runtime_checks {
120+
let bounds_check_policy = if stage.module.bounds_checks.bounds_checks() {
121121
naga::proc::BoundsCheckPolicy::Restrict
122122
} else {
123123
naga::proc::BoundsCheckPolicy::Unchecked
@@ -151,6 +151,7 @@ impl super::Device {
151151
binding_array: naga::proc::BoundsCheckPolicy::Unchecked,
152152
},
153153
zero_initialize_workgroup_memory: stage.zero_initialize_workgroup_memory,
154+
force_loop_bounding: stage.module.bounds_checks.force_loop_bounding(),
154155
};
155156

156157
let pipeline_options = naga::back::msl::PipelineOptions {
@@ -888,7 +889,7 @@ impl crate::Device for super::Device {
888889
match shader {
889890
crate::ShaderInput::Naga(naga) => Ok(super::ShaderModule {
890891
naga,
891-
runtime_checks: desc.runtime_checks,
892+
bounds_checks: desc.runtime_checks,
892893
}),
893894
crate::ShaderInput::SpirV(_) => {
894895
panic!("SPIRV_SHADER_PASSTHROUGH is not enabled for this backend")

wgpu-hal/src/metal/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ unsafe impl Sync for BindGroup {}
726726
#[derive(Debug)]
727727
pub struct ShaderModule {
728728
naga: crate::NagaShader,
729-
runtime_checks: bool,
729+
bounds_checks: wgt::ShaderRuntimeChecks,
730730
}
731731

732732
impl crate::DynShaderModule for ShaderModule {}

wgpu-hal/src/vulkan/device.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -897,14 +897,14 @@ impl super::Device {
897897
entry_point: stage.entry_point.to_string(),
898898
shader_stage: naga_stage,
899899
};
900-
let needs_temp_options = !runtime_checks
900+
let needs_temp_options = !runtime_checks.bounds_checks()
901901
|| !binding_map.is_empty()
902902
|| naga_shader.debug_source.is_some()
903903
|| !stage.zero_initialize_workgroup_memory;
904904
let mut temp_options;
905905
let options = if needs_temp_options {
906906
temp_options = self.naga_options.clone();
907-
if !runtime_checks {
907+
if !runtime_checks.bounds_checks() {
908908
temp_options.bounds_check_policies = naga::proc::BoundsCheckPolicies {
909909
index: naga::proc::BoundsCheckPolicy::Unchecked,
910910
buffer: naga::proc::BoundsCheckPolicy::Unchecked,
@@ -1813,7 +1813,7 @@ impl crate::Device for super::Device {
18131813
file_name: d.file_name.as_ref().as_ref(),
18141814
language: naga::back::spv::SourceLanguage::WGSL,
18151815
});
1816-
if !desc.runtime_checks {
1816+
if !desc.runtime_checks.bounds_checks() {
18171817
naga_options.bounds_check_policies = naga::proc::BoundsCheckPolicies {
18181818
index: naga::proc::BoundsCheckPolicy::Unchecked,
18191819
buffer: naga::proc::BoundsCheckPolicy::Unchecked,

wgpu-hal/src/vulkan/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -974,7 +974,7 @@ pub enum ShaderModule {
974974
Raw(vk::ShaderModule),
975975
Intermediate {
976976
naga_shader: crate::NagaShader,
977-
runtime_checks: bool,
977+
runtime_checks: wgt::ShaderRuntimeChecks,
978978
},
979979
}
980980

wgpu-types/src/lib.rs

+65-16
Original file line numberDiff line numberDiff line change
@@ -7441,22 +7441,47 @@ impl DispatchIndirectArgs {
74417441
}
74427442

74437443
/// Describes how shader bound checks should be performed.
7444-
#[derive(Clone, Debug)]
7444+
#[derive(Copy, Clone, Debug)]
74457445
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
7446-
pub struct ShaderBoundChecks {
7447-
runtime_checks: bool,
7446+
pub struct ShaderRuntimeChecks {
7447+
bounds_checks: bool,
7448+
force_loop_bounding: bool,
74487449
}
74497450

7450-
impl ShaderBoundChecks {
7451-
/// Creates a new configuration where the shader is bound checked.
7451+
impl ShaderRuntimeChecks {
7452+
/// Creates a new configuration where the shader is fully checked.
7453+
#[must_use]
7454+
pub fn checked() -> Self {
7455+
unsafe { Self::all(true) }
7456+
}
7457+
7458+
/// Creates a new configuration where none of the checks are performed.
7459+
///
7460+
/// # Safety
7461+
///
7462+
/// See the documentation for the `set_*` methods for the safety requirements
7463+
/// of each sub-configuration.
7464+
#[must_use]
7465+
pub unsafe fn unchecked() -> Self {
7466+
unsafe { Self::all(false) }
7467+
}
7468+
7469+
/// Creates a new configuration where all checks are enabled or disabled. To safely
7470+
/// create a configuration with all checks enabled, use [`ShaderRuntimeChecks::checked`].
7471+
///
7472+
/// # Safety
7473+
///
7474+
/// See the documentation for the `set_*` methods for the safety requirements
7475+
/// of each sub-configuration.
74527476
#[must_use]
7453-
pub fn new() -> Self {
7454-
ShaderBoundChecks {
7455-
runtime_checks: true,
7477+
pub unsafe fn all(all_checks: bool) -> Self {
7478+
Self {
7479+
bounds_checks: all_checks,
7480+
force_loop_bounding: all_checks,
74567481
}
74577482
}
74587483

7459-
/// Creates a new configuration where the shader isn't bound checked.
7484+
/// Sets the enablement of runtime bounds checks.
74607485
///
74617486
/// # Safety
74627487
///
@@ -7469,22 +7494,46 @@ impl ShaderBoundChecks {
74697494
/// configuration with ill-behaved shaders could expose uninitialized GPU
74707495
/// memory contents to the application.
74717496
#[must_use]
7472-
pub unsafe fn unchecked() -> Self {
7473-
ShaderBoundChecks {
7474-
runtime_checks: false,
7497+
pub unsafe fn set_bounds_checks(self, runtime_checks: bool) -> Self {
7498+
Self {
7499+
bounds_checks: runtime_checks,
7500+
..self
7501+
}
7502+
}
7503+
7504+
/// Sets the enablement of loop bounding.
7505+
///
7506+
/// # Safety
7507+
///
7508+
/// If false, the caller MUST ensure that all passed shaders do not contain any infinite loops.
7509+
///
7510+
/// If it does, backend compilers MAY treat such a loop as unreachable code and draw
7511+
/// conclusions about other safety-critical code paths. This option SHOULD NOT be disabled
7512+
/// when running untrusted code.
7513+
#[must_use]
7514+
pub unsafe fn set_force_loop_bounding(self, force_loop_bounding: bool) -> Self {
7515+
Self {
7516+
force_loop_bounding,
7517+
..self
74757518
}
74767519
}
74777520

74787521
/// Query whether runtime bound checks are enabled in this configuration
74797522
#[must_use]
7480-
pub fn runtime_checks(&self) -> bool {
7481-
self.runtime_checks
7523+
pub fn bounds_checks(&self) -> bool {
7524+
self.bounds_checks
7525+
}
7526+
7527+
/// Query whether loop bounding is enabled in this configuration
7528+
#[must_use]
7529+
pub fn force_loop_bounding(&self) -> bool {
7530+
self.force_loop_bounding
74827531
}
74837532
}
74847533

7485-
impl Default for ShaderBoundChecks {
7534+
impl Default for ShaderRuntimeChecks {
74867535
fn default() -> Self {
7487-
Self::new()
7536+
Self::checked()
74887537
}
74897538
}
74907539

0 commit comments

Comments
 (0)