Bug 1919567 - build(webgpu): update WGPU to 3fda684eb9e69c78b16312a3e927e3ea82e853d1 r=webgpu-reviewers,supply-chain-reviewers,jimb

Differential Revision: https://phabricator.services.mozilla.com/D222852
This commit is contained in:
Erich Gubler 2024-09-19 20:54:47 +00:00
parent 74b9b73c79
commit f470dfbfb8
20 changed files with 509 additions and 99 deletions

View File

@ -25,9 +25,9 @@ git = "https://github.com/franziskuskiefer/cose-rust"
rev = "43c22248d136c8b38fe42ea709d08da6355cf04b"
replace-with = "vendored-sources"
[source."git+https://github.com/gfx-rs/wgpu?rev=c8beade1877251c494036fc3661b04ec6aad63a9"]
[source."git+https://github.com/gfx-rs/wgpu?rev=3fda684eb9e69c78b16312a3e927e3ea82e853d1"]
git = "https://github.com/gfx-rs/wgpu"
rev = "c8beade1877251c494036fc3661b04ec6aad63a9"
rev = "3fda684eb9e69c78b16312a3e927e3ea82e853d1"
replace-with = "vendored-sources"
[source."git+https://github.com/hsivonen/any_all_workaround?rev=7fb1b7034c9f172aade21ee1c8554e8d8a48af80"]

8
Cargo.lock generated
View File

@ -4039,7 +4039,7 @@ checksum = "a2983372caf4480544083767bf2d27defafe32af49ab4df3a0b7fc90793a3664"
[[package]]
name = "naga"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=c8beade1877251c494036fc3661b04ec6aad63a9#c8beade1877251c494036fc3661b04ec6aad63a9"
source = "git+https://github.com/gfx-rs/wgpu?rev=3fda684eb9e69c78b16312a3e927e3ea82e853d1#3fda684eb9e69c78b16312a3e927e3ea82e853d1"
dependencies = [
"arrayvec",
"bit-set",
@ -6809,7 +6809,7 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=c8beade1877251c494036fc3661b04ec6aad63a9#c8beade1877251c494036fc3661b04ec6aad63a9"
source = "git+https://github.com/gfx-rs/wgpu?rev=3fda684eb9e69c78b16312a3e927e3ea82e853d1#3fda684eb9e69c78b16312a3e927e3ea82e853d1"
dependencies = [
"arrayvec",
"bit-vec",
@ -6834,7 +6834,7 @@ dependencies = [
[[package]]
name = "wgpu-hal"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=c8beade1877251c494036fc3661b04ec6aad63a9#c8beade1877251c494036fc3661b04ec6aad63a9"
source = "git+https://github.com/gfx-rs/wgpu?rev=3fda684eb9e69c78b16312a3e927e3ea82e853d1#3fda684eb9e69c78b16312a3e927e3ea82e853d1"
dependencies = [
"android_system_properties",
"arrayvec",
@ -6873,7 +6873,7 @@ dependencies = [
[[package]]
name = "wgpu-types"
version = "22.0.0"
source = "git+https://github.com/gfx-rs/wgpu?rev=c8beade1877251c494036fc3661b04ec6aad63a9#c8beade1877251c494036fc3661b04ec6aad63a9"
source = "git+https://github.com/gfx-rs/wgpu?rev=3fda684eb9e69c78b16312a3e927e3ea82e853d1#3fda684eb9e69c78b16312a3e927e3ea82e853d1"
dependencies = [
"bitflags 2.6.0",
"js-sys",

View File

@ -17,7 +17,7 @@ default = []
[dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "c8beade1877251c494036fc3661b04ec6aad63a9"
rev = "3fda684eb9e69c78b16312a3e927e3ea82e853d1"
# TODO: remove the replay feature on the next update containing https://github.com/gfx-rs/wgpu/pull/5182
features = ["serde", "replay", "trace", "strict_asserts", "wgsl", "api_log_info"]
@ -26,32 +26,32 @@ features = ["serde", "replay", "trace", "strict_asserts", "wgsl", "api_log_info"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "c8beade1877251c494036fc3661b04ec6aad63a9"
rev = "3fda684eb9e69c78b16312a3e927e3ea82e853d1"
features = ["metal"]
# We want the wgpu-core Direct3D backends on Windows.
[target.'cfg(windows)'.dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "c8beade1877251c494036fc3661b04ec6aad63a9"
rev = "3fda684eb9e69c78b16312a3e927e3ea82e853d1"
features = ["dx12"]
# We want the wgpu-core Vulkan backend on Linux and Windows.
[target.'cfg(any(windows, all(unix, not(any(target_os = "macos", target_os = "ios")))))'.dependencies.wgc]
package = "wgpu-core"
git = "https://github.com/gfx-rs/wgpu"
rev = "c8beade1877251c494036fc3661b04ec6aad63a9"
rev = "3fda684eb9e69c78b16312a3e927e3ea82e853d1"
features = ["vulkan"]
[dependencies.wgt]
package = "wgpu-types"
git = "https://github.com/gfx-rs/wgpu"
rev = "c8beade1877251c494036fc3661b04ec6aad63a9"
rev = "3fda684eb9e69c78b16312a3e927e3ea82e853d1"
[dependencies.wgh]
package = "wgpu-hal"
git = "https://github.com/gfx-rs/wgpu"
rev = "c8beade1877251c494036fc3661b04ec6aad63a9"
rev = "3fda684eb9e69c78b16312a3e927e3ea82e853d1"
features = ["oom_panic", "device_lost_panic", "internal_error_panic"]
[target.'cfg(windows)'.dependencies]

View File

@ -20,11 +20,11 @@ origin:
# Human-readable identifier for this version/release
# Generally "version NNN", "tag SSS", "bookmark SSS"
release: commit c8beade1877251c494036fc3661b04ec6aad63a9
release: 3fda684eb9e69c78b16312a3e927e3ea82e853d1 (2024-09-18T15:01:51Z).
# Revision to pull in
# Must be a long or short commit SHA (long preferred)
revision: c8beade1877251c494036fc3661b04ec6aad63a9
revision: 3fda684eb9e69c78b16312a3e927e3ea82e853d1
license: ['MIT', 'Apache-2.0']

View File

@ -3132,12 +3132,12 @@ delta = "0.20.0 -> 22.0.0"
[[audits.naga]]
who = [
"Erich Gubler <erichdongubler@gmail.com>",
"Jim Blandy <jimb@red-bean.com>",
"Teodor Tanasoaia <ttanasoaia@mozilla.com>",
"Erich Gubler <erichdongubler@gmail.com>",
]
criteria = "safe-to-deploy"
delta = "22.0.0 -> 22.0.0@git:c8beade1877251c494036fc3661b04ec6aad63a9"
delta = "22.0.0 -> 22.0.0@git:3fda684eb9e69c78b16312a3e927e3ea82e853d1"
importable = false
[[audits.net2]]
@ -5133,12 +5133,12 @@ delta = "0.20.0 -> 22.0.0"
[[audits.wgpu-core]]
who = [
"Erich Gubler <erichdongubler@gmail.com>",
"Jim Blandy <jimb@red-bean.com>",
"Teodor Tanasoaia <ttanasoaia@mozilla.com>",
"Erich Gubler <erichdongubler@gmail.com>",
]
criteria = "safe-to-deploy"
delta = "22.0.0 -> 22.0.0@git:c8beade1877251c494036fc3661b04ec6aad63a9"
delta = "22.0.0 -> 22.0.0@git:3fda684eb9e69c78b16312a3e927e3ea82e853d1"
importable = false
[[audits.wgpu-hal]]
@ -5206,12 +5206,12 @@ delta = "0.20.0 -> 22.0.0"
[[audits.wgpu-hal]]
who = [
"Erich Gubler <erichdongubler@gmail.com>",
"Jim Blandy <jimb@red-bean.com>",
"Teodor Tanasoaia <ttanasoaia@mozilla.com>",
"Erich Gubler <erichdongubler@gmail.com>",
]
criteria = "safe-to-deploy"
delta = "22.0.0 -> 22.0.0@git:c8beade1877251c494036fc3661b04ec6aad63a9"
delta = "22.0.0 -> 22.0.0@git:3fda684eb9e69c78b16312a3e927e3ea82e853d1"
importable = false
[[audits.wgpu-types]]
@ -5279,12 +5279,12 @@ delta = "0.20.0 -> 22.0.0"
[[audits.wgpu-types]]
who = [
"Erich Gubler <erichdongubler@gmail.com>",
"Jim Blandy <jimb@red-bean.com>",
"Teodor Tanasoaia <ttanasoaia@mozilla.com>",
"Erich Gubler <erichdongubler@gmail.com>",
]
criteria = "safe-to-deploy"
delta = "22.0.0 -> 22.0.0@git:c8beade1877251c494036fc3661b04ec6aad63a9"
delta = "22.0.0 -> 22.0.0@git:3fda684eb9e69c78b16312a3e927e3ea82e853d1"
importable = false
[[audits.whatsys]]

View File

@ -1,23 +1,33 @@
[cts.https.html?q=webgpu:shader,execution,padding:array_of_matCx3:*]
implementation-status:
if os == "win": backlog
if os == "mac": backlog
[:columns=2;use_struct=false]
expected:
if os == "mac": FAIL
[:columns=2;use_struct=true]
expected:
if os == "win": FAIL
if os == "mac": FAIL
[:columns=3;use_struct=false]
expected:
if os == "mac": FAIL
[:columns=3;use_struct=true]
expected:
if os == "win": FAIL
if os == "mac": FAIL
[:columns=4;use_struct=false]
expected:
if os == "mac": FAIL
[:columns=4;use_struct=true]
expected:
if os == "win": FAIL
if os == "mac": FAIL
[cts.https.html?q=webgpu:shader,execution,padding:array_of_struct:*]
@ -35,17 +45,31 @@
[cts.https.html?q=webgpu:shader,execution,padding:matCx3:*]
implementation-status:
if os == "mac": backlog
[:columns=2;use_struct=false]
expected:
if os == "mac": FAIL
[:columns=2;use_struct=true]
expected:
if os == "mac": FAIL
[:columns=3;use_struct=false]
expected:
if os == "mac": FAIL
[:columns=3;use_struct=true]
expected:
if os == "mac": FAIL
[:columns=4;use_struct=false]
expected:
if os == "mac": FAIL
[:columns=4;use_struct=true]
expected:
if os == "mac": FAIL
[cts.https.html?q=webgpu:shader,execution,padding:struct_explicit:*]

File diff suppressed because one or more lines are too long

View File

@ -376,6 +376,11 @@ pub struct Writer<W> {
/// Set of (struct type, struct field index) denoting which fields require
/// padding inserted **before** them (i.e. between fields at index - 1 and index)
struct_member_pads: FastHashSet<(Handle<crate::Type>, u32)>,
/// Name of the loop reachability macro.
///
/// See `emit_loop_reachable_macro` for details.
loop_reachable_macro_name: String,
}
impl crate::Scalar {
@ -665,6 +670,7 @@ impl<W: Write> Writer<W> {
#[cfg(test)]
put_block_stack_pointers: Default::default(),
struct_member_pads: FastHashSet::default(),
loop_reachable_macro_name: String::default(),
}
}
@ -675,6 +681,125 @@ impl<W: Write> Writer<W> {
self.out
}
/// Define a macro to invoke before loops, to defeat MSL infinite loop
/// reasoning.
///
/// If we haven't done so already, emit the definition of a preprocessor
/// macro to be invoked before each loop in the generated MSL, to ensure
/// that the MSL compiler's optimizations do not remove bounds checks.
///
/// Only the first call to this function for a given module actually causes
/// the macro definition to be written. Subsequent loops can simply use the
/// prior macro definition, since macros aren't block-scoped.
///
/// # What is this trying to solve?
///
/// In Metal Shading Language, an infinite loop has undefined behavior.
/// (This rule is inherited from C++14.) This means that, if the MSL
/// compiler determines that a given loop will never exit, it may assume
/// that it is never reached. It may thus assume that any conditions
/// sufficient to cause the loop to be reached must be false. Like many
/// optimizing compilers, MSL uses this kind of analysis to establish limits
/// on the range of values variables involved in those conditions might
/// hold.
///
/// For example, suppose the MSL compiler sees the code:
///
/// ```ignore
/// if (i >= 10) {
/// while (true) { }
/// }
/// ```
///
/// It will recognize that the `while` loop will never terminate, conclude
/// that it must be unreachable, and thus infer that, if this code is
/// reached, then `i < 10` at that point.
///
/// Now suppose that, at some point where `i` has the same value as above,
/// the compiler sees the code:
///
/// ```ignore
/// if (i < 10) {
/// a[i] = 1;
/// }
/// ```
///
/// Because the compiler is confident that `i < 10`, it will make the
/// assignment to `a[i]` unconditional, rewriting this code as, simply:
///
/// ```ignore
/// a[i] = 1;
/// ```
///
/// If that `if` condition was injected by Naga to implement a bounds check,
/// the MSL compiler's optimizations could allow out-of-bounds array
/// accesses to occur.
///
/// Naga cannot feasibly anticipate whether the MSL compiler will determine
/// that a loop is infinite, so an attacker could craft a Naga module
/// containing an infinite loop protected by conditions that cause the Metal
/// compiler to remove bounds checks that Naga injected elsewhere in the
/// function.
///
/// This rewrite could occur even if the conditional assignment appears
/// *before* the `while` loop, as long as `i < 10` by the time the loop is
/// reached. This would allow the attacker to save the results of
/// unauthorized reads somewhere accessible before entering the infinite
/// loop. But even worse, the MSL compiler has been observed to simply
/// delete the infinite loop entirely, so that even code dominated by the
/// loop becomes reachable. This would make the attack even more flexible,
/// since shaders that would appear to never terminate would actually exit
/// nicely, after having stolen data from elsewhere in the GPU address
/// space.
///
/// Ideally, Naga would prevent UB entirely via some means that persuades
/// the MSL compiler that no loop Naga generates is infinite. One approach
/// would be to add inline assembly to each loop that is annotated as
/// potentially branching out of the loop, but which in fact generates no
/// instructions. Unfortunately, inline assembly is not handled correctly by
/// some Metal device drivers. Further experimentation hasn't produced a
/// satisfactory approach.
///
/// Instead, we accept that the MSL compiler may determine that some loops
/// are infinite, and focus instead on preventing the range analysis from
/// being affected. We transform *every* loop into something like this:
///
/// ```ignore
/// if (volatile bool unpredictable = true; unpredictable)
/// while (true) { }
/// ```
///
/// Since the `volatile` qualifier prevents the compiler from assuming that
/// the `if` condition is true, it cannot be sure the infinite loop is
/// reached, and thus it cannot assume the entire structure is unreachable.
/// This prevents the range analysis impact described above.
///
/// Unfortunately, what makes this a kludge, not a hack, is that this
/// solution leaves the GPU executing a pointless conditional branch, at
/// runtime, before each loop. There's no part of the system that has a
/// global enough view to be sure that `unpredictable` is true, and remove
/// it from the code.
///
/// To make our output a bit more legible, we pull the condition out into a
/// preprocessor macro defined at the top of the module.
fn emit_loop_reachable_macro(&mut self) -> BackendResult {
if !self.loop_reachable_macro_name.is_empty() {
return Ok(());
}
self.loop_reachable_macro_name = self.namer.call("LOOP_IS_REACHABLE");
let loop_reachable_volatile_name = self.namer.call("unpredictable_jump_over_loop");
writeln!(
self.out,
"#define {} if (volatile bool {} = true; {})",
self.loop_reachable_macro_name,
loop_reachable_volatile_name,
loop_reachable_volatile_name,
)?;
Ok(())
}
fn put_call_parameters(
&mut self,
parameters: impl Iterator<Item = Handle<crate::Expression>>,
@ -2924,10 +3049,15 @@ impl<W: Write> Writer<W> {
ref continuing,
break_if,
} => {
self.emit_loop_reachable_macro()?;
if !continuing.is_empty() || break_if.is_some() {
let gate_name = self.namer.call("loop_init");
writeln!(self.out, "{level}bool {gate_name} = true;")?;
writeln!(self.out, "{level}while(true) {{")?;
writeln!(
self.out,
"{level}{} while(true) {{",
self.loop_reachable_macro_name,
)?;
let lif = level.next();
let lcontinuing = lif.next();
writeln!(self.out, "{lif}if (!{gate_name}) {{")?;
@ -2942,7 +3072,11 @@ impl<W: Write> Writer<W> {
writeln!(self.out, "{lif}}}")?;
writeln!(self.out, "{lif}{gate_name} = false;")?;
} else {
writeln!(self.out, "{level}while(true) {{")?;
writeln!(
self.out,
"{level}{} while(true) {{",
self.loop_reachable_macro_name,
)?;
}
self.put_block(level.next(), body, context)?;
writeln!(self.out, "{level}}}")?;
@ -3379,6 +3513,7 @@ impl<W: Write> Writer<W> {
&[CLAMPED_LOD_LOAD_PREFIX],
&mut self.names,
);
self.loop_reachable_macro_name.clear();
self.struct_member_pads.clear();
writeln!(

View File

@ -16,7 +16,7 @@ mod selection;
mod subgroup;
mod writer;
pub use spirv::Capability;
pub use spirv::{Capability, SourceLanguage};
use crate::arena::{Handle, HandleVec};
use crate::proc::{BoundsCheckPolicies, TypeResolution};
@ -89,6 +89,7 @@ impl IdGenerator {
pub struct DebugInfo<'a> {
pub source_code: &'a str,
pub file_name: &'a std::path::Path,
pub language: SourceLanguage,
}
/// A SPIR-V block to which we are still adding instructions.

View File

@ -1967,7 +1967,7 @@ impl Writer {
source_file_id,
});
self.debugs.append(&mut Instruction::source_auto_continued(
spirv::SourceLanguage::Unknown,
debug_info.language,
0,
&debug_info_inner,
));

View File

@ -44,12 +44,8 @@ pub enum Error {
MultiMemberStruct,
#[error("encountered unsupported global initializer in an atomic variable")]
GlobalInitUnsupported,
}
impl From<Error> for crate::front::spv::Error {
fn from(source: Error) -> Self {
crate::front::spv::Error::AtomicUpgradeError(source)
}
#[error("expected to find a global variable")]
GlobalVariableMissing,
}
#[derive(Clone, Default)]

View File

@ -159,3 +159,9 @@ impl Error {
String::from_utf8(writer.into_inner()).unwrap()
}
}
impl From<atomic_upgrade::Error> for Error {
fn from(source: atomic_upgrade::Error) -> Self {
crate::front::spv::Error::AtomicUpgradeError(source)
}
}

View File

@ -565,11 +565,15 @@ impl<'a> BlockContext<'a> {
/// Descend into the expression with the given handle, locating a contained
/// global variable.
///
/// If the expression doesn't actually refer to something in a global
/// variable, we can't upgrade its type in a way that Naga validation would
/// pass, so reject the input instead.
///
/// This is used to track atomic upgrades.
fn get_contained_global_variable(
&self,
mut handle: Handle<crate::Expression>,
) -> Option<Handle<crate::GlobalVariable>> {
) -> Result<Handle<crate::GlobalVariable>, Error> {
log::debug!("\t\tlocating global variable in {handle:?}");
loop {
match self.expressions[handle] {
@ -583,14 +587,16 @@ impl<'a> BlockContext<'a> {
}
crate::Expression::GlobalVariable(h) => {
log::debug!("\t\t found {h:?}");
return Some(h);
return Ok(h);
}
_ => {
break;
}
}
}
None
Err(Error::AtomicUpgradeError(
crate::front::atomic_upgrade::Error::GlobalVariableMissing,
))
}
}
@ -1323,6 +1329,109 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
))
}
/// Return the Naga [`Expression`] for `pointer_id`, and its referent [`Type`].
///
/// Return a [`Handle`] for a Naga [`Expression`] that holds the value of
/// the SPIR-V instruction `pointer_id`, along with the [`Type`] to which it
/// is a pointer.
///
/// This may entail spilling `pointer_id`'s value to a temporary:
/// see [`get_expr_handle`]'s documentation.
///
/// [`Expression`]: crate::Expression
/// [`Type`]: crate::Type
/// [`Handle`]: crate::Handle
/// [`get_expr_handle`]: Frontend::get_expr_handle
fn get_exp_and_base_ty_handles(
&self,
pointer_id: spirv::Word,
ctx: &mut BlockContext,
emitter: &mut crate::proc::Emitter,
block: &mut crate::Block,
body_idx: usize,
) -> Result<(Handle<crate::Expression>, Handle<crate::Type>), Error> {
log::trace!("\t\t\tlooking up pointer expr {:?}", pointer_id);
let p_lexp_handle;
let p_lexp_ty_id;
{
let lexp = self.lookup_expression.lookup(pointer_id)?;
p_lexp_handle = self.get_expr_handle(pointer_id, lexp, ctx, emitter, block, body_idx);
p_lexp_ty_id = lexp.type_id;
};
log::trace!("\t\t\tlooking up pointer type {pointer_id:?}");
let p_ty = self.lookup_type.lookup(p_lexp_ty_id)?;
let p_ty_base_id = p_ty.base_id.ok_or(Error::InvalidAccessType(p_lexp_ty_id))?;
log::trace!("\t\t\tlooking up pointer base type {p_ty_base_id:?} of {p_ty:?}");
let p_base_ty = self.lookup_type.lookup(p_ty_base_id)?;
Ok((p_lexp_handle, p_base_ty.handle))
}
#[allow(clippy::too_many_arguments)]
fn parse_atomic_expr_with_value(
&mut self,
inst: Instruction,
emitter: &mut crate::proc::Emitter,
ctx: &mut BlockContext,
block: &mut crate::Block,
block_id: spirv::Word,
body_idx: usize,
atomic_function: crate::AtomicFunction,
) -> Result<(), Error> {
inst.expect(7)?;
let start = self.data_offset;
let result_type_id = self.next()?;
let result_id = self.next()?;
let pointer_id = self.next()?;
let _scope_id = self.next()?;
let _memory_semantics_id = self.next()?;
let value_id = self.next()?;
let span = self.span_from_with_op(start);
let (p_lexp_handle, p_base_ty_handle) =
self.get_exp_and_base_ty_handles(pointer_id, ctx, emitter, block, body_idx)?;
log::trace!("\t\t\tlooking up value expr {value_id:?}");
let v_lexp_handle = self.lookup_expression.lookup(value_id)?.handle;
block.extend(emitter.finish(ctx.expressions));
// Create an expression for our result
let r_lexp_handle = {
let expr = crate::Expression::AtomicResult {
ty: p_base_ty_handle,
comparison: false,
};
let handle = ctx.expressions.append(expr, span);
self.lookup_expression.insert(
result_id,
LookupExpression {
handle,
type_id: result_type_id,
block_id,
},
);
handle
};
emitter.start(ctx.expressions);
// Create a statement for the op itself
let stmt = crate::Statement::Atomic {
pointer: p_lexp_handle,
fun: atomic_function,
value: v_lexp_handle,
result: Some(r_lexp_handle),
};
block.push(stmt, span);
// Store any associated global variables so we can upgrade their types later
self.upgrade_atomics
.insert(ctx.get_contained_global_variable(p_lexp_handle)?);
Ok(())
}
/// Add the next SPIR-V block's contents to `block_ctx`.
///
/// Except for the function's entry block, `block_id` should be the label of
@ -3985,35 +4094,91 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
);
emitter.start(ctx.expressions);
}
Op::AtomicIIncrement => {
Op::AtomicLoad => {
inst.expect(6)?;
let start = self.data_offset;
let span = self.span_from_with_op(start);
let result_type_id = self.next()?;
let result_id = self.next()?;
let pointer_id = self.next()?;
let _scope_id = self.next()?;
let _memory_semantics_id = self.next()?;
let span = self.span_from_with_op(start);
log::trace!("\t\t\tlooking up expr {:?}", pointer_id);
let (p_lexp_handle, p_lexp_ty_id) = {
let lexp = self.lookup_expression.lookup(pointer_id)?;
let handle = get_expr_handle!(pointer_id, &lexp);
(handle, lexp.type_id)
let p_lexp_handle =
get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?);
// Create an expression for our result
let expr = crate::Expression::Load {
pointer: p_lexp_handle,
};
let handle = ctx.expressions.append(expr, span);
self.lookup_expression.insert(
result_id,
LookupExpression {
handle,
type_id: result_type_id,
block_id,
},
);
log::trace!("\t\t\tlooking up type {pointer_id:?}");
let p_ty = self.lookup_type.lookup(p_lexp_ty_id)?;
let p_ty_base_id =
p_ty.base_id.ok_or(Error::InvalidAccessType(p_lexp_ty_id))?;
// Store any associated global variables so we can upgrade their types later
self.upgrade_atomics
.insert(ctx.get_contained_global_variable(p_lexp_handle)?);
}
Op::AtomicStore => {
inst.expect(5)?;
let start = self.data_offset;
let pointer_id = self.next()?;
let _scope_id = self.next()?;
let _memory_semantics_id = self.next()?;
let value_id = self.next()?;
let span = self.span_from_with_op(start);
log::trace!("\t\t\tlooking up base type {p_ty_base_id:?} of {p_ty:?}");
let p_base_ty = self.lookup_type.lookup(p_ty_base_id)?;
log::trace!("\t\t\tlooking up pointer expr {:?}", pointer_id);
let p_lexp_handle =
get_expr_handle!(pointer_id, self.lookup_expression.lookup(pointer_id)?);
log::trace!("\t\t\tlooking up value expr {:?}", pointer_id);
let v_lexp_handle =
get_expr_handle!(value_id, self.lookup_expression.lookup(value_id)?);
block.extend(emitter.finish(ctx.expressions));
// Create a statement for the op itself
let stmt = crate::Statement::Store {
pointer: p_lexp_handle,
value: v_lexp_handle,
};
block.push(stmt, span);
emitter.start(ctx.expressions);
// Store any associated global variables so we can upgrade their types later
self.upgrade_atomics
.insert(ctx.get_contained_global_variable(p_lexp_handle)?);
}
Op::AtomicIIncrement | Op::AtomicIDecrement => {
inst.expect(6)?;
let start = self.data_offset;
let result_type_id = self.next()?;
let result_id = self.next()?;
let pointer_id = self.next()?;
let _scope_id = self.next()?;
let _memory_semantics_id = self.next()?;
let span = self.span_from_with_op(start);
let (p_exp_h, p_base_ty_h) = self.get_exp_and_base_ty_handles(
pointer_id,
ctx,
&mut emitter,
&mut block,
body_idx,
)?;
block.extend(emitter.finish(ctx.expressions));
// Create an expression for our result
let r_lexp_handle = {
let expr = crate::Expression::AtomicResult {
ty: p_base_ty.handle,
ty: p_base_ty_h,
comparison: false,
};
let handle = ctx.expressions.append(expr, span);
@ -4027,22 +4192,26 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
);
handle
};
emitter.start(ctx.expressions);
// Create a literal "1" since WGSL lacks an increment operation
// Create a literal "1" to use as our value
let one_lexp_handle = make_index_literal(
ctx,
1,
&mut block,
&mut emitter,
p_base_ty.handle,
p_lexp_ty_id,
p_base_ty_h,
result_type_id,
span,
)?;
// Create a statement for the op itself
let stmt = crate::Statement::Atomic {
pointer: p_lexp_handle,
fun: crate::AtomicFunction::Add,
pointer: p_exp_h,
fun: match inst.op {
Op::AtomicIIncrement => crate::AtomicFunction::Add,
_ => crate::AtomicFunction::Subtract,
},
value: one_lexp_handle,
result: Some(r_lexp_handle),
};
@ -4050,8 +4219,38 @@ impl<I: Iterator<Item = u32>> Frontend<I> {
// Store any associated global variables so we can upgrade their types later
self.upgrade_atomics
.extend(ctx.get_contained_global_variable(p_lexp_handle));
.insert(ctx.get_contained_global_variable(p_exp_h)?);
}
Op::AtomicExchange
| Op::AtomicIAdd
| Op::AtomicISub
| Op::AtomicSMin
| Op::AtomicUMin
| Op::AtomicSMax
| Op::AtomicUMax
| Op::AtomicAnd
| Op::AtomicOr
| Op::AtomicXor => self.parse_atomic_expr_with_value(
inst,
&mut emitter,
ctx,
&mut block,
block_id,
body_idx,
match inst.op {
Op::AtomicExchange => crate::AtomicFunction::Exchange { compare: None },
Op::AtomicIAdd => crate::AtomicFunction::Add,
Op::AtomicISub => crate::AtomicFunction::Subtract,
Op::AtomicSMin => crate::AtomicFunction::Min,
Op::AtomicUMin => crate::AtomicFunction::Min,
Op::AtomicSMax => crate::AtomicFunction::Max,
Op::AtomicUMax => crate::AtomicFunction::Max,
Op::AtomicAnd => crate::AtomicFunction::And,
Op::AtomicOr => crate::AtomicFunction::InclusiveOr,
_ => crate::AtomicFunction::ExclusiveOr,
},
)?,
_ => {
return Err(Error::UnsupportedInstruction(self.state, inst.op));
}
@ -5709,33 +5908,48 @@ mod test {
];
let _ = super::parse_u8_slice(&bin, &Default::default()).unwrap();
}
}
#[cfg(all(feature = "wgsl-in", wgsl_out))]
#[test]
fn atomic_i_inc() {
#[cfg(all(test, feature = "wgsl-in", wgsl_out))]
mod test_atomic {
fn atomic_test(bytes: &[u8]) {
let _ = env_logger::builder().is_test(true).try_init();
let bytes = include_bytes!("../../../tests/in/spv/atomic_i_increment.spv");
let m = super::parse_u8_slice(bytes, &Default::default()).unwrap();
let mut validator = crate::valid::Validator::new(
let m = crate::front::spv::parse_u8_slice(bytes, &Default::default()).unwrap();
let mut wgsl = String::new();
let mut should_panic = false;
for vflags in [
crate::valid::ValidationFlags::all(),
crate::valid::ValidationFlags::empty(),
Default::default(),
);
let info = match validator.validate(&m) {
Err(e) => {
log::error!("{}", e.emit_to_string(""));
return;
}
Ok(i) => i,
};
let wgsl =
crate::back::wgsl::write_string(&m, &info, crate::back::wgsl::WriterFlags::empty())
.unwrap();
log::info!("atomic_i_increment:\n{wgsl}");
] {
let mut validator = crate::valid::Validator::new(vflags, Default::default());
match validator.validate(&m) {
Err(e) => {
log::error!("SPIR-V validation {}", e.emit_to_string(""));
should_panic = true;
}
Ok(i) => {
wgsl = crate::back::wgsl::write_string(
&m,
&i,
crate::back::wgsl::WriterFlags::empty(),
)
.unwrap();
log::info!("wgsl-out:\n{wgsl}");
break;
}
};
}
if should_panic {
panic!("validation error");
}
let m = match crate::front::wgsl::parse_str(&wgsl) {
Ok(m) => m,
Err(e) => {
log::error!("{}", e.emit_to_string(&wgsl));
log::error!("round trip WGSL validation {}", e.emit_to_string(&wgsl));
panic!("invalid module");
}
};
@ -5746,4 +5960,35 @@ mod test {
panic!("invalid generated wgsl");
}
}
#[test]
fn atomic_i_inc() {
atomic_test(include_bytes!(
"../../../tests/in/spv/atomic_i_increment.spv"
));
}
#[test]
fn atomic_load_and_store() {
atomic_test(include_bytes!(
"../../../tests/in/spv/atomic_load_and_store.spv"
));
}
#[test]
fn atomic_exchange() {
atomic_test(include_bytes!("../../../tests/in/spv/atomic_exchange.spv"));
}
#[test]
fn atomic_i_decrement() {
atomic_test(include_bytes!(
"../../../tests/in/spv/atomic_i_decrement.spv"
));
}
#[test]
fn atomic_i_add_and_sub() {
atomic_test(include_bytes!("../../../tests/in/spv/atomic_i_add_sub.spv"));
}
}

File diff suppressed because one or more lines are too long

View File

@ -84,7 +84,7 @@ path = "../naga"
version = "1.19.0"
[dependencies.parking_lot]
version = ">=0.11, <0.13"
version = "0.12.1"
[dependencies.profiling]
version = "1"

File diff suppressed because one or more lines are too long

View File

@ -71,7 +71,7 @@ version = "0.7"
version = "2.6"
[dependencies.glow]
version = "0.14.0"
version = "0.14.1"
optional = true
[dependencies.log]
@ -85,7 +85,7 @@ path = "../naga"
version = "1.19.0"
[dependencies.parking_lot]
version = ">=0.11, <0.13"
version = "0.12.1"
[dependencies.profiling]
version = "1"

View File

@ -769,6 +769,7 @@ impl super::Device {
temp_options.debug_info = Some(naga::back::spv::DebugInfo {
source_code: &debug.source_code,
file_name: debug.file_name.as_ref().as_ref(),
language: naga::back::spv::SourceLanguage::WGSL,
})
}
if !stage.zero_initialize_workgroup_memory {
@ -1742,6 +1743,7 @@ impl crate::Device for super::Device {
.map(|d| naga::back::spv::DebugInfo {
source_code: d.source_code.as_ref(),
file_name: d.file_name.as_ref().as_ref(),
language: naga::back::spv::SourceLanguage::WGSL,
});
if !desc.runtime_checks {
naga_options.bounds_check_policies = naga::proc::BoundsCheckPolicies {

View File

@ -1 +1 @@
{"files":{"Cargo.toml":"32a781ba9b4fb68f6854c80c3d307bdd720de34bc8fe17aef73cf8f821f18098","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","src/assertions.rs":"d22aa7ddb95d3ae129ff9848fd45913bab41bd354b6f1c1f6c38a86f9d1abbc6","src/counters.rs":"599e9c033c4f8fcc95113bf730191d80f51b3276b3ee8fd03bbc1c27d24bf76d","src/lib.rs":"e4c3a736e22002ea6d5c4e0aceeb1b5cdf269b742fbfa06b88e369b5c90b20c0","src/math.rs":"4d03039736dd6926feb139bc68734cb59df34ede310427bbf059e5c925e0af3b"},"package":null}
{"files":{"Cargo.toml":"32a781ba9b4fb68f6854c80c3d307bdd720de34bc8fe17aef73cf8f821f18098","LICENSE.APACHE":"a6cba85bc92e0cff7a450b1d873c0eaa2e9fc96bf472df0247a26bec77bf3ff9","LICENSE.MIT":"c7fea58d1cfe49634cd92e54fc10a9d871f4b275321a4cd8c09e449122caaeb4","src/assertions.rs":"d22aa7ddb95d3ae129ff9848fd45913bab41bd354b6f1c1f6c38a86f9d1abbc6","src/counters.rs":"599e9c033c4f8fcc95113bf730191d80f51b3276b3ee8fd03bbc1c27d24bf76d","src/lib.rs":"bf21e776108de35061b72b95cd865bc435a3096567b00381555299c009c8c05b","src/math.rs":"4d03039736dd6926feb139bc68734cb59df34ede310427bbf059e5c925e0af3b"},"package":null}

View File

@ -568,7 +568,7 @@ bitflags::bitflags! {
/// may also create uniform arrays of storage textures.
///
/// ex.
/// - `var textures: array<texture_storage_2d<f32, write>, 10>` (WGSL)
/// - `var textures: array<texture_storage_2d<r32float, write>, 10>` (WGSL)
/// - `uniform image2D textures[10]` (GLSL)
///
/// This capability allows them to exist and to be indexed by dynamically uniform
@ -1959,12 +1959,13 @@ impl TextureViewDimension {
/// Alpha blend factor.
///
/// Alpha blending is very complicated: see the OpenGL or Vulkan spec for more information.
///
/// Corresponds to [WebGPU `GPUBlendFactor`](
/// https://gpuweb.github.io/gpuweb/#enumdef-gpublendfactor).
/// Values using S1 requires [`Features::DUAL_SOURCE_BLENDING`] and can only be
/// used with the first render target.
/// https://gpuweb.github.io/gpuweb/#enumdef-gpublendfactor). Values using `Src1`
/// require [`Features::DUAL_SOURCE_BLENDING`] and can only be used with the first
/// render target.
///
/// For further details on how the blend factors are applied, see the analogous
/// functionality in OpenGL: <https://www.khronos.org/opengl/wiki/Blending#Blending_Parameters>.
#[repr(C)]
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -2024,10 +2025,11 @@ impl BlendFactor {
/// Alpha blend operation.
///
/// Alpha blending is very complicated: see the OpenGL or Vulkan spec for more information.
///
/// Corresponds to [WebGPU `GPUBlendOperation`](
/// https://gpuweb.github.io/gpuweb/#enumdef-gpublendoperation).
///
/// For further details on how the blend operations are applied, see
/// the analogous functionality in OpenGL: <https://www.khronos.org/opengl/wiki/Blending#Blend_Equations>.
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Hash, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@ -2102,8 +2104,6 @@ impl Default for BlendComponent {
/// Describe the blend state of a render pipeline,
/// within [`ColorTargetState`].
///
/// See the OpenGL or Vulkan spec for more information.
///
/// Corresponds to [WebGPU `GPUBlendState`](
/// https://gpuweb.github.io/gpuweb/#dictdef-gpublendstate).
#[repr(C)]
@ -6543,7 +6543,7 @@ pub enum StorageTextureAccess {
/// Example WGSL syntax:
/// ```rust,ignore
/// @group(0) @binding(0)
/// var my_storage_image: texture_storage_2d<f32, write>;
/// var my_storage_image: texture_storage_2d<r32float, write>;
/// ```
///
/// Example GLSL syntax:
@ -6560,7 +6560,7 @@ pub enum StorageTextureAccess {
/// Example WGSL syntax:
/// ```rust,ignore
/// @group(0) @binding(0)
/// var my_storage_image: texture_storage_2d<f32, read>;
/// var my_storage_image: texture_storage_2d<r32float, read>;
/// ```
///
/// Example GLSL syntax:
@ -6577,7 +6577,7 @@ pub enum StorageTextureAccess {
/// Example WGSL syntax:
/// ```rust,ignore
/// @group(0) @binding(0)
/// var my_storage_image: texture_storage_2d<f32, read_write>;
/// var my_storage_image: texture_storage_2d<r32float, read_write>;
/// ```
///
/// Example GLSL syntax:
@ -6701,8 +6701,8 @@ pub enum BindingType {
/// Dimension of the texture view that is going to be sampled.
view_dimension: TextureViewDimension,
/// True if the texture has a sample count greater than 1. If this is true,
/// the texture must be read from shaders with `texture1DMS`, `texture2DMS`, or `texture3DMS`,
/// depending on `dimension`.
/// the texture must be declared as `texture_multisampled_2d` or
/// `texture_depth_multisampled_2d` in the shader, and read using `textureLoad`.
multisampled: bool,
},
/// A storage texture.
@ -6710,15 +6710,16 @@ pub enum BindingType {
/// Example WGSL syntax:
/// ```rust,ignore
/// @group(0) @binding(0)
/// var my_storage_image: texture_storage_2d<f32, write>;
/// var my_storage_image: texture_storage_2d<r32float, write>;
/// ```
///
/// Example GLSL syntax:
/// ```cpp,ignore
/// layout(set=0, binding=0, r32f) writeonly uniform image2D myStorageImage;
/// ```
/// Note that the texture format must be specified in the shader as well.
/// A list of valid formats can be found in the specification here: <https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.html#layout-qualifiers>
/// Note that the texture format must be specified in the shader, along with the
/// access mode. For WGSL, the format must be one of the enumerants in the list
/// of [storage texel formats](https://gpuweb.github.io/gpuweb/wgsl/#storage-texel-formats).
///
/// Corresponds to [WebGPU `GPUStorageTextureBindingLayout`](
/// https://gpuweb.github.io/gpuweb/#dictdef-gpustoragetexturebindinglayout).