diff --git a/CHANGELOG.md b/CHANGELOG.md index e347053f4b..39a386359a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,12 @@ Bottom level categories: ## Unreleased +### Changes + +#### naga + +- Naga now enforces the `@must_use` attribute on WGSL built-in functions, when applicable. You can waive the error with a phony assignment, e.g., `_ = subgroupElect()`. By @andyleiserson in [#8713](https://github.com/gfx-rs/wgpu/pull/8713). + ## v28.0.0 (2025-12-17) ### Major Changes diff --git a/cts_runner/test.lst b/cts_runner/test.lst index 62afa3008c..0c4c954d41 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -183,7 +183,25 @@ webgpu:shader,validation,expression,binary,short_circuiting_and_or:array_overrid webgpu:shader,validation,expression,binary,short_circuiting_and_or:invalid_types:* webgpu:shader,validation,expression,binary,short_circuiting_and_or:scalar_vector:op="%26%26";lhs="bool";rhs="bool" webgpu:shader,validation,expression,call,builtin,all:arguments:test="ptr_deref" -webgpu:shader,validation,expression,call,builtin,max:values:* +webgpu:shader,validation,expression,call,builtin,arrayLength:* +webgpu:shader,validation,expression,call,builtin,barriers:* +webgpu:shader,validation,expression,call,builtin,cos:* +webgpu:shader,validation,expression,call,builtin,floor:* +webgpu:shader,validation,expression,call,builtin,fract:* +webgpu:shader,validation,expression,call,builtin,max:* +webgpu:shader,validation,expression,call,builtin,min:* +webgpu:shader,validation,expression,call,builtin,radians:* +webgpu:shader,validation,expression,call,builtin,sign:* +webgpu:shader,validation,expression,call,builtin,sin:* +webgpu:shader,validation,expression,call,builtin,step:* +webgpu:shader,validation,expression,call,builtin,tan:* +webgpu:shader,validation,expression,call,builtin,tanh:* +webgpu:shader,validation,expression,call,builtin,textureNumLayers:* +webgpu:shader,validation,expression,call,builtin,textureNumLevels:* +webgpu:shader,validation,expression,call,builtin,textureNumSamples:* +webgpu:shader,validation,expression,call,builtin,textureStore:* +webgpu:shader,validation,expression,call,builtin,trunc:* +webgpu:shader,validation,expression,call,builtin,workgroupUniformLoad:* webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="break" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="break_if" webgpu:shader,validation,statement,statement_behavior:invalid_statements:body="continue" diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index 77c20b0c78..ff6bfefe16 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -1195,6 +1195,23 @@ enum AbstractRule { Allow, } +enum MaybeHandle { + Value(T), + Handle(Handle), +} + +impl From for MaybeHandle { + fn from(value: T) -> Self { + MaybeHandle::Value(value) + } +} + +impl From> for MaybeHandle { + fn from(handle: Handle) -> Self { + MaybeHandle::Handle(handle) + } +} + pub struct Lowerer<'source, 'temp> { index: &'temp Index<'source>, } @@ -2021,8 +2038,8 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { stmt.span, function, arguments, - &mut ctx.as_expression(block, &mut emitter), true, + &mut ctx.as_expression(block, &mut emitter), )?; block.extend(emitter.finish(&ctx.function.expressions)); return Ok(()); @@ -2349,7 +2366,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { ref arguments, } => { let handle = self - .call(span, function, arguments, ctx, false)? + .call(span, function, arguments, false, ctx)? .ok_or(Error::FunctionReturnsVoid(function.span))?; return Ok(Typed::Plain(handle)); } @@ -2667,6 +2684,742 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { } } + fn call_builtin( + &mut self, + function: &ast::Ident<'source>, + arguments: &[Handle>], + is_statement: bool, + ctx: &mut ExpressionContext<'source, '_, '_>, + ) -> Result<'source, Option>> { + const MUST_USE_YES: bool = true; + const MUST_USE_NO: bool = false; + + // We report all diagnostics associated with builtins with a span for + // just the function identifier, unlike for other kinds of calls, where + // we sometimes report with the span for the entire call expression. + let span = function.span; + + let (result, must_use) = if let Some(fun) = conv::map_relational_fun(function.name) { + let mut args = ctx.prepare_args(arguments, 1, span); + let argument = self.expression(args.next()?, ctx)?; + args.finish()?; + + // Check for no-op all(bool) and any(bool): + let argument_unmodified = matches!( + fun, + ir::RelationalFunction::All | ir::RelationalFunction::Any + ) && { + matches!( + resolve_inner!(ctx, argument), + &ir::TypeInner::Scalar(ir::Scalar { + kind: ir::ScalarKind::Bool, + .. + }) + ) + }; + + let result = if argument_unmodified { + Some(argument.into()) + } else { + Some(ir::Expression::Relational { fun, argument }.into()) + }; + (result, MUST_USE_YES) + } else if let Some((axis, ctrl)) = conv::map_derivative(function.name) { + let mut args = ctx.prepare_args(arguments, 1, span); + let expr = self.expression(args.next()?, ctx)?; + args.finish()?; + + ( + Some(ir::Expression::Derivative { axis, ctrl, expr }.into()), + MUST_USE_YES, + ) + } else if let Some(fun) = conv::map_standard_fun(function.name) { + ( + Some(self.math_function_helper(span, fun, arguments, ctx)?.into()), + MUST_USE_YES, + ) + } else if let Some(fun) = Texture::map(function.name) { + ( + Some( + self.texture_sample_helper(fun, arguments, span, ctx)? + .into(), + ), + MUST_USE_YES, + ) + } else if let Some((op, cop)) = conv::map_subgroup_operation(function.name) { + ( + Some( + self.subgroup_operation_helper(span, op, cop, arguments, ctx)? + .into(), + ), + MUST_USE_YES, + ) + } else if let Some(mode) = SubgroupGather::map(function.name) { + ( + Some( + self.subgroup_gather_helper(span, mode, arguments, ctx)? + .into(), + ), + MUST_USE_YES, + ) + } else if let Some(fun) = ir::AtomicFunction::map(function.name) { + let result = self + .atomic_helper(span, fun, arguments, is_statement, ctx)? + .map(Into::into); + (result, MUST_USE_NO) + } else { + match function.name { + "select" => { + let mut args = ctx.prepare_args(arguments, 3, span); + + let reject_orig = args.next()?; + let accept_orig = args.next()?; + let mut values = [ + self.expression_for_abstract(reject_orig, ctx)?, + self.expression_for_abstract(accept_orig, ctx)?, + ]; + let condition = self.expression(args.next()?, ctx)?; + + args.finish()?; + + let diagnostic_details = + |ctx: &ExpressionContext<'_, '_, '_>, + ty_res: &proc::TypeResolution, + orig_expr| { + ( + ctx.ast_expressions.get_span(orig_expr), + format!("`{}`", ctx.as_diagnostic_display(ty_res)), + ) + }; + for (&value, orig_value) in values.iter().zip([reject_orig, accept_orig]) { + let value_ty_res = resolve!(ctx, value); + if value_ty_res + .inner_with(&ctx.module.types) + .vector_size_and_scalar() + .is_none() + { + let (arg_span, arg_type) = + diagnostic_details(ctx, value_ty_res, orig_value); + return Err(Box::new(Error::SelectUnexpectedArgumentType { + arg_span, + arg_type, + })); + } + } + let mut consensus_scalar = ctx + .automatic_conversion_consensus(&values) + .map_err(|_idx| { + let [reject, accept] = values; + let [(reject_span, reject_type), (accept_span, accept_type)] = + [(reject_orig, reject), (accept_orig, accept)].map( + |(orig_expr, expr)| { + let ty_res = &ctx.typifier()[expr]; + diagnostic_details(ctx, ty_res, orig_expr) + }, + ); + Error::SelectRejectAndAcceptHaveNoCommonType { + reject_span, + reject_type, + accept_span, + accept_type, + } + })?; + if !ctx.is_const(condition) { + consensus_scalar = consensus_scalar.concretize(); + } + + ctx.convert_slice_to_common_leaf_scalar(&mut values, consensus_scalar)?; + + let [reject, accept] = values; + + ( + Some( + ir::Expression::Select { + reject, + accept, + condition, + } + .into(), + ), + MUST_USE_YES, + ) + } + "arrayLength" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let expr = self.expression(args.next()?, ctx)?; + args.finish()?; + + (Some(ir::Expression::ArrayLength(expr).into()), MUST_USE_YES) + } + "atomicLoad" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let (pointer, _scalar) = self.atomic_pointer(args.next()?, ctx)?; + args.finish()?; + + (Some(ir::Expression::Load { pointer }.into()), MUST_USE_NO) + } + "atomicStore" => { + let mut args = ctx.prepare_args(arguments, 2, span); + let (pointer, scalar) = self.atomic_pointer(args.next()?, ctx)?; + let value = self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; + args.finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.emitter.start(&rctx.function.expressions); + rctx.block + .push(ir::Statement::Store { pointer, value }, span); + (None, MUST_USE_NO) + } + "atomicCompareExchangeWeak" => { + let mut args = ctx.prepare_args(arguments, 3, span); + + let (pointer, scalar) = self.atomic_pointer(args.next()?, ctx)?; + + let compare = self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; + + let value = args.next()?; + let value_span = ctx.ast_expressions.get_span(value); + let value = self.expression_with_leaf_scalar(value, scalar, ctx)?; + + args.finish()?; + + let expression = match *resolve_inner!(ctx, value) { + ir::TypeInner::Scalar(scalar) => ir::Expression::AtomicResult { + ty: ctx.module.generate_predeclared_type( + ir::PredeclaredType::AtomicCompareExchangeWeakResult(scalar), + ), + comparison: true, + }, + _ => return Err(Box::new(Error::InvalidAtomicOperandType(value_span))), + }; + + let result = ctx.interrupt_emitter(expression, span)?; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block.push( + ir::Statement::Atomic { + pointer, + fun: ir::AtomicFunction::Exchange { + compare: Some(compare), + }, + value, + result: Some(result), + }, + span, + ); + (Some(result.into()), MUST_USE_NO) + } + "textureAtomicMin" | "textureAtomicMax" | "textureAtomicAdd" + | "textureAtomicAnd" | "textureAtomicOr" | "textureAtomicXor" => { + let mut args = ctx.prepare_args(arguments, 3, span); + + let image = args.next()?; + let image_span = ctx.ast_expressions.get_span(image); + let image = self.expression(image, ctx)?; + + let coordinate = self.expression(args.next()?, ctx)?; + + let (_, arrayed) = ctx.image_data(image, image_span)?; + let array_index = arrayed + .then(|| { + args.min_args += 1; + self.expression(args.next()?, ctx) + }) + .transpose()?; + + let value = self.expression(args.next()?, ctx)?; + + args.finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.emitter.start(&rctx.function.expressions); + let stmt = ir::Statement::ImageAtomic { + image, + coordinate, + array_index, + fun: match function.name { + "textureAtomicMin" => ir::AtomicFunction::Min, + "textureAtomicMax" => ir::AtomicFunction::Max, + "textureAtomicAdd" => ir::AtomicFunction::Add, + "textureAtomicAnd" => ir::AtomicFunction::And, + "textureAtomicOr" => ir::AtomicFunction::InclusiveOr, + "textureAtomicXor" => ir::AtomicFunction::ExclusiveOr, + _ => unreachable!(), + }, + value, + }; + rctx.block.push(stmt, span); + (None, MUST_USE_NO) + } + "storageBarrier" => { + ctx.prepare_args(arguments, 0, span).finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(ir::Statement::ControlBarrier(ir::Barrier::STORAGE), span); + (None, MUST_USE_NO) + } + "workgroupBarrier" => { + ctx.prepare_args(arguments, 0, span).finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(ir::Statement::ControlBarrier(ir::Barrier::WORK_GROUP), span); + (None, MUST_USE_NO) + } + "subgroupBarrier" => { + ctx.prepare_args(arguments, 0, span).finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(ir::Statement::ControlBarrier(ir::Barrier::SUB_GROUP), span); + (None, MUST_USE_NO) + } + "textureBarrier" => { + ctx.prepare_args(arguments, 0, span).finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(ir::Statement::ControlBarrier(ir::Barrier::TEXTURE), span); + (None, MUST_USE_NO) + } + "workgroupUniformLoad" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let expr = args.next()?; + args.finish()?; + + let pointer = self.expression(expr, ctx)?; + let result_ty = match *resolve_inner!(ctx, pointer) { + ir::TypeInner::Pointer { + base, + space: ir::AddressSpace::WorkGroup, + } => base, + ref other => { + log::error!("Type {other:?} passed to workgroupUniformLoad"); + let span = ctx.ast_expressions.get_span(expr); + return Err(Box::new(Error::InvalidWorkGroupUniformLoad(span))); + } + }; + let result = ctx.interrupt_emitter( + ir::Expression::WorkGroupUniformLoadResult { ty: result_ty }, + span, + )?; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block.push( + ir::Statement::WorkGroupUniformLoad { pointer, result }, + span, + ); + + (Some(result.into()), MUST_USE_YES) + } + "textureStore" => { + let mut args = ctx.prepare_args(arguments, 3, span); + + let image = args.next()?; + let image_span = ctx.ast_expressions.get_span(image); + let image = self.expression(image, ctx)?; + + let coordinate = self.expression(args.next()?, ctx)?; + + let (class, arrayed) = ctx.image_data(image, image_span)?; + let array_index = arrayed + .then(|| { + args.min_args += 1; + self.expression(args.next()?, ctx) + }) + .transpose()?; + let scalar = if let ir::ImageClass::Storage { format, .. } = class { + format.into() + } else { + return Err(Box::new(Error::NotStorageTexture(image_span))); + }; + + let value = self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; + + args.finish()?; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.emitter.start(&rctx.function.expressions); + let stmt = ir::Statement::ImageStore { + image, + coordinate, + array_index, + value, + }; + rctx.block.push(stmt, span); + (None, MUST_USE_NO) + } + "textureLoad" => { + let mut args = ctx.prepare_args(arguments, 2, span); + + let image = args.next()?; + let image_span = ctx.ast_expressions.get_span(image); + let image = self.expression(image, ctx)?; + + let coordinate = self.expression(args.next()?, ctx)?; + + let (class, arrayed) = ctx.image_data(image, image_span)?; + let array_index = arrayed + .then(|| { + args.min_args += 1; + self.expression(args.next()?, ctx) + }) + .transpose()?; + + let level = class + .is_mipmapped() + .then(|| { + args.min_args += 1; + self.expression(args.next()?, ctx) + }) + .transpose()?; + + let sample = class + .is_multisampled() + .then(|| self.expression(args.next()?, ctx)) + .transpose()?; + + args.finish()?; + + ( + Some( + ir::Expression::ImageLoad { + image, + coordinate, + array_index, + level, + sample, + } + .into(), + ), + MUST_USE_YES, + ) + } + "textureDimensions" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let image = self.expression(args.next()?, ctx)?; + let level = args + .next() + .map(|arg| self.expression(arg, ctx)) + .ok() + .transpose()?; + args.finish()?; + + ( + Some( + ir::Expression::ImageQuery { + image, + query: ir::ImageQuery::Size { level }, + } + .into(), + ), + MUST_USE_YES, + ) + } + "textureNumLevels" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let image = self.expression(args.next()?, ctx)?; + args.finish()?; + + ( + Some( + ir::Expression::ImageQuery { + image, + query: ir::ImageQuery::NumLevels, + } + .into(), + ), + MUST_USE_YES, + ) + } + "textureNumLayers" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let image = self.expression(args.next()?, ctx)?; + args.finish()?; + + ( + Some( + ir::Expression::ImageQuery { + image, + query: ir::ImageQuery::NumLayers, + } + .into(), + ), + MUST_USE_YES, + ) + } + "textureNumSamples" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let image = self.expression(args.next()?, ctx)?; + args.finish()?; + + ( + Some( + ir::Expression::ImageQuery { + image, + query: ir::ImageQuery::NumSamples, + } + .into(), + ), + MUST_USE_YES, + ) + } + "rayQueryInitialize" => { + let mut args = ctx.prepare_args(arguments, 3, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + let acceleration_structure = self.expression(args.next()?, ctx)?; + let descriptor = self.expression(args.next()?, ctx)?; + args.finish()?; + + let _ = ctx.module.generate_ray_desc_type(); + let fun = ir::RayQueryFunction::Initialize { + acceleration_structure, + descriptor, + }; + + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.emitter.start(&rctx.function.expressions); + rctx.block + .push(ir::Statement::RayQuery { query, fun }, span); + (None, MUST_USE_NO) + } + "getCommittedHitVertexPositions" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + args.finish()?; + + let _ = ctx.module.generate_vertex_return_type(); + + ( + Some( + ir::Expression::RayQueryVertexPositions { + query, + committed: true, + } + .into(), + ), + MUST_USE_NO, + ) + } + "getCandidateHitVertexPositions" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + args.finish()?; + + let _ = ctx.module.generate_vertex_return_type(); + + ( + Some( + ir::Expression::RayQueryVertexPositions { + query, + committed: false, + } + .into(), + ), + MUST_USE_NO, + ) + } + "rayQueryProceed" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + args.finish()?; + + let result = + ctx.interrupt_emitter(ir::Expression::RayQueryProceedResult, span)?; + let fun = ir::RayQueryFunction::Proceed { result }; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(ir::Statement::RayQuery { query, fun }, span); + (Some(result.into()), MUST_USE_NO) + } + "rayQueryGenerateIntersection" => { + let mut args = ctx.prepare_args(arguments, 2, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + let hit_t = self.expression(args.next()?, ctx)?; + args.finish()?; + + let fun = ir::RayQueryFunction::GenerateIntersection { hit_t }; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(ir::Statement::RayQuery { query, fun }, span); + (None, MUST_USE_NO) + } + "rayQueryConfirmIntersection" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + args.finish()?; + + let fun = ir::RayQueryFunction::ConfirmIntersection; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(ir::Statement::RayQuery { query, fun }, span); + (None, MUST_USE_NO) + } + "rayQueryTerminate" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + args.finish()?; + + let fun = ir::RayQueryFunction::Terminate; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(ir::Statement::RayQuery { query, fun }, span); + (None, MUST_USE_NO) + } + "rayQueryGetCommittedIntersection" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + args.finish()?; + + let _ = ctx.module.generate_ray_intersection_type(); + ( + Some( + ir::Expression::RayQueryGetIntersection { + query, + committed: true, + } + .into(), + ), + MUST_USE_NO, + ) + } + "rayQueryGetCandidateIntersection" => { + let mut args = ctx.prepare_args(arguments, 1, span); + let query = self.ray_query_pointer(args.next()?, ctx)?; + args.finish()?; + + let _ = ctx.module.generate_ray_intersection_type(); + ( + Some( + ir::Expression::RayQueryGetIntersection { + query, + committed: false, + } + .into(), + ), + MUST_USE_NO, + ) + } + "RayDesc" => { + let ty = ctx.module.generate_ray_desc_type(); + let handle = self.construct( + span, + &ast::ConstructorType::Type(ty), + span, + arguments, + ctx, + )?; + (Some(handle.into()), MUST_USE_NO) + } + "subgroupBallot" => { + let mut args = ctx.prepare_args(arguments, 0, span); + let predicate = if arguments.len() == 1 { + Some(self.expression(args.next()?, ctx)?) + } else { + None + }; + args.finish()?; + + let result = + ctx.interrupt_emitter(ir::Expression::SubgroupBallotResult, span)?; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block + .push(ir::Statement::SubgroupBallot { result, predicate }, span); + (Some(result.into()), MUST_USE_YES) + } + "quadSwapX" => { + let mut args = ctx.prepare_args(arguments, 1, span); + + let argument = self.expression(args.next()?, ctx)?; + args.finish()?; + + let ty = ctx.register_type(argument)?; + + let result = ctx.interrupt_emitter( + crate::Expression::SubgroupOperationResult { ty }, + span, + )?; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block.push( + crate::Statement::SubgroupGather { + mode: crate::GatherMode::QuadSwap(crate::Direction::X), + argument, + result, + }, + span, + ); + (Some(result.into()), MUST_USE_YES) + } + + "quadSwapY" => { + let mut args = ctx.prepare_args(arguments, 1, span); + + let argument = self.expression(args.next()?, ctx)?; + args.finish()?; + + let ty = ctx.register_type(argument)?; + + let result = ctx.interrupt_emitter( + crate::Expression::SubgroupOperationResult { ty }, + span, + )?; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block.push( + crate::Statement::SubgroupGather { + mode: crate::GatherMode::QuadSwap(crate::Direction::Y), + argument, + result, + }, + span, + ); + (Some(result.into()), MUST_USE_YES) + } + + "quadSwapDiagonal" => { + let mut args = ctx.prepare_args(arguments, 1, span); + + let argument = self.expression(args.next()?, ctx)?; + args.finish()?; + + let ty = ctx.register_type(argument)?; + + let result = ctx.interrupt_emitter( + crate::Expression::SubgroupOperationResult { ty }, + span, + )?; + let rctx = ctx.runtime_expression_ctx(span)?; + rctx.block.push( + crate::Statement::SubgroupGather { + mode: crate::GatherMode::QuadSwap(crate::Direction::Diagonal), + argument, + result, + }, + span, + ); + (Some(result.into()), MUST_USE_YES) + } + _ => return Err(Box::new(Error::UnknownIdent(function.span, function.name))), + } + }; + + if must_use && is_statement { + return Err(Box::new(Error::FunctionMustUseUnused(function.span))); + } + + match result { + Some(MaybeHandle::Value(expr)) => Ok(Some(ctx.append_expression(expr, span)?)), + Some(MaybeHandle::Handle(handle)) => Ok(Some(handle)), + None => Ok(None), + } + } + /// Generate Naga IR for call expressions and statements, and type /// constructor expressions. /// @@ -2690,9 +3443,11 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { span: Span, function: &ast::Ident<'source>, arguments: &[Handle>], - ctx: &mut ExpressionContext<'source, '_, '_>, is_statement: bool, + ctx: &mut ExpressionContext<'source, '_, '_>, ) -> Result<'source, Option>> { + // `span` is for the entire call expression, while `function_span` is just + // for the function identifier. let function_span = function.span; match ctx.globals.get(function.name) { Some(&LoweredGlobalDecl::Type(ty)) => { @@ -2774,650 +3529,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { Ok(result) } - None => { - let span = function_span; - let expr = if let Some(fun) = conv::map_relational_fun(function.name) { - let mut args = ctx.prepare_args(arguments, 1, span); - let argument = self.expression(args.next()?, ctx)?; - args.finish()?; - - // Check for no-op all(bool) and any(bool): - let argument_unmodified = matches!( - fun, - ir::RelationalFunction::All | ir::RelationalFunction::Any - ) && { - matches!( - resolve_inner!(ctx, argument), - &ir::TypeInner::Scalar(ir::Scalar { - kind: ir::ScalarKind::Bool, - .. - }) - ) - }; - - if argument_unmodified { - return Ok(Some(argument)); - } else { - ir::Expression::Relational { fun, argument } - } - } else if let Some((axis, ctrl)) = conv::map_derivative(function.name) { - let mut args = ctx.prepare_args(arguments, 1, span); - let expr = self.expression(args.next()?, ctx)?; - args.finish()?; - - ir::Expression::Derivative { axis, ctrl, expr } - } else if let Some(fun) = conv::map_standard_fun(function.name) { - self.math_function_helper(span, fun, arguments, ctx)? - } else if let Some(fun) = Texture::map(function.name) { - self.texture_sample_helper(fun, arguments, span, ctx)? - } else if let Some((op, cop)) = conv::map_subgroup_operation(function.name) { - return Ok(Some( - self.subgroup_operation_helper(span, op, cop, arguments, ctx)?, - )); - } else if let Some(mode) = SubgroupGather::map(function.name) { - return Ok(Some( - self.subgroup_gather_helper(span, mode, arguments, ctx)?, - )); - } else if let Some(fun) = ir::AtomicFunction::map(function.name) { - return self.atomic_helper(span, fun, arguments, is_statement, ctx); - } else { - match function.name { - "select" => { - let mut args = ctx.prepare_args(arguments, 3, span); - - let reject_orig = args.next()?; - let accept_orig = args.next()?; - let mut values = [ - self.expression_for_abstract(reject_orig, ctx)?, - self.expression_for_abstract(accept_orig, ctx)?, - ]; - let condition = self.expression(args.next()?, ctx)?; - - args.finish()?; - - let diagnostic_details = - |ctx: &ExpressionContext<'_, '_, '_>, - ty_res: &proc::TypeResolution, - orig_expr| { - ( - ctx.ast_expressions.get_span(orig_expr), - format!("`{}`", ctx.as_diagnostic_display(ty_res)), - ) - }; - for (&value, orig_value) in - values.iter().zip([reject_orig, accept_orig]) - { - let value_ty_res = resolve!(ctx, value); - if value_ty_res - .inner_with(&ctx.module.types) - .vector_size_and_scalar() - .is_none() - { - let (arg_span, arg_type) = - diagnostic_details(ctx, value_ty_res, orig_value); - return Err(Box::new(Error::SelectUnexpectedArgumentType { - arg_span, - arg_type, - })); - } - } - let mut consensus_scalar = ctx - .automatic_conversion_consensus(&values) - .map_err(|_idx| { - let [reject, accept] = values; - let [(reject_span, reject_type), (accept_span, accept_type)] = - [(reject_orig, reject), (accept_orig, accept)].map( - |(orig_expr, expr)| { - let ty_res = &ctx.typifier()[expr]; - diagnostic_details(ctx, ty_res, orig_expr) - }, - ); - Error::SelectRejectAndAcceptHaveNoCommonType { - reject_span, - reject_type, - accept_span, - accept_type, - } - })?; - if !ctx.is_const(condition) { - consensus_scalar = consensus_scalar.concretize(); - } - - ctx.convert_slice_to_common_leaf_scalar(&mut values, consensus_scalar)?; - - let [reject, accept] = values; - - ir::Expression::Select { - reject, - accept, - condition, - } - } - "arrayLength" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let expr = self.expression(args.next()?, ctx)?; - args.finish()?; - - ir::Expression::ArrayLength(expr) - } - "atomicLoad" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let (pointer, _scalar) = self.atomic_pointer(args.next()?, ctx)?; - args.finish()?; - - ir::Expression::Load { pointer } - } - "atomicStore" => { - let mut args = ctx.prepare_args(arguments, 2, span); - let (pointer, scalar) = self.atomic_pointer(args.next()?, ctx)?; - let value = - self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; - args.finish()?; - - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .extend(rctx.emitter.finish(&rctx.function.expressions)); - rctx.emitter.start(&rctx.function.expressions); - rctx.block - .push(ir::Statement::Store { pointer, value }, span); - return Ok(None); - } - "atomicCompareExchangeWeak" => { - let mut args = ctx.prepare_args(arguments, 3, span); - - let (pointer, scalar) = self.atomic_pointer(args.next()?, ctx)?; - - let compare = - self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; - - let value = args.next()?; - let value_span = ctx.ast_expressions.get_span(value); - let value = self.expression_with_leaf_scalar(value, scalar, ctx)?; - - args.finish()?; - - let expression = match *resolve_inner!(ctx, value) { - ir::TypeInner::Scalar(scalar) => ir::Expression::AtomicResult { - ty: ctx.module.generate_predeclared_type( - ir::PredeclaredType::AtomicCompareExchangeWeakResult( - scalar, - ), - ), - comparison: true, - }, - _ => { - return Err(Box::new(Error::InvalidAtomicOperandType( - value_span, - ))) - } - }; - - let result = ctx.interrupt_emitter(expression, span)?; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block.push( - ir::Statement::Atomic { - pointer, - fun: ir::AtomicFunction::Exchange { - compare: Some(compare), - }, - value, - result: Some(result), - }, - span, - ); - return Ok(Some(result)); - } - "textureAtomicMin" | "textureAtomicMax" | "textureAtomicAdd" - | "textureAtomicAnd" | "textureAtomicOr" | "textureAtomicXor" => { - let mut args = ctx.prepare_args(arguments, 3, span); - - let image = args.next()?; - let image_span = ctx.ast_expressions.get_span(image); - let image = self.expression(image, ctx)?; - - let coordinate = self.expression(args.next()?, ctx)?; - - let (_, arrayed) = ctx.image_data(image, image_span)?; - let array_index = arrayed - .then(|| { - args.min_args += 1; - self.expression(args.next()?, ctx) - }) - .transpose()?; - - let value = self.expression(args.next()?, ctx)?; - - args.finish()?; - - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .extend(rctx.emitter.finish(&rctx.function.expressions)); - rctx.emitter.start(&rctx.function.expressions); - let stmt = ir::Statement::ImageAtomic { - image, - coordinate, - array_index, - fun: match function.name { - "textureAtomicMin" => ir::AtomicFunction::Min, - "textureAtomicMax" => ir::AtomicFunction::Max, - "textureAtomicAdd" => ir::AtomicFunction::Add, - "textureAtomicAnd" => ir::AtomicFunction::And, - "textureAtomicOr" => ir::AtomicFunction::InclusiveOr, - "textureAtomicXor" => ir::AtomicFunction::ExclusiveOr, - _ => unreachable!(), - }, - value, - }; - rctx.block.push(stmt, span); - return Ok(None); - } - "storageBarrier" => { - ctx.prepare_args(arguments, 0, span).finish()?; - - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .push(ir::Statement::ControlBarrier(ir::Barrier::STORAGE), span); - return Ok(None); - } - "workgroupBarrier" => { - ctx.prepare_args(arguments, 0, span).finish()?; - - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .push(ir::Statement::ControlBarrier(ir::Barrier::WORK_GROUP), span); - return Ok(None); - } - "subgroupBarrier" => { - ctx.prepare_args(arguments, 0, span).finish()?; - - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .push(ir::Statement::ControlBarrier(ir::Barrier::SUB_GROUP), span); - return Ok(None); - } - "textureBarrier" => { - ctx.prepare_args(arguments, 0, span).finish()?; - - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .push(ir::Statement::ControlBarrier(ir::Barrier::TEXTURE), span); - return Ok(None); - } - "workgroupUniformLoad" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let expr = args.next()?; - args.finish()?; - - let pointer = self.expression(expr, ctx)?; - let result_ty = match *resolve_inner!(ctx, pointer) { - ir::TypeInner::Pointer { - base, - space: ir::AddressSpace::WorkGroup, - } => base, - ref other => { - log::error!("Type {other:?} passed to workgroupUniformLoad"); - let span = ctx.ast_expressions.get_span(expr); - return Err(Box::new(Error::InvalidWorkGroupUniformLoad(span))); - } - }; - let result = ctx.interrupt_emitter( - ir::Expression::WorkGroupUniformLoadResult { ty: result_ty }, - span, - )?; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block.push( - ir::Statement::WorkGroupUniformLoad { pointer, result }, - span, - ); - - return Ok(Some(result)); - } - "textureStore" => { - let mut args = ctx.prepare_args(arguments, 3, span); - - let image = args.next()?; - let image_span = ctx.ast_expressions.get_span(image); - let image = self.expression(image, ctx)?; - - let coordinate = self.expression(args.next()?, ctx)?; - - let (class, arrayed) = ctx.image_data(image, image_span)?; - let array_index = arrayed - .then(|| { - args.min_args += 1; - self.expression(args.next()?, ctx) - }) - .transpose()?; - let scalar = if let ir::ImageClass::Storage { format, .. } = class { - format.into() - } else { - return Err(Box::new(Error::NotStorageTexture(image_span))); - }; - - let value = - self.expression_with_leaf_scalar(args.next()?, scalar, ctx)?; - - args.finish()?; - - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .extend(rctx.emitter.finish(&rctx.function.expressions)); - rctx.emitter.start(&rctx.function.expressions); - let stmt = ir::Statement::ImageStore { - image, - coordinate, - array_index, - value, - }; - rctx.block.push(stmt, span); - return Ok(None); - } - "textureLoad" => { - let mut args = ctx.prepare_args(arguments, 2, span); - - let image = args.next()?; - let image_span = ctx.ast_expressions.get_span(image); - let image = self.expression(image, ctx)?; - - let coordinate = self.expression(args.next()?, ctx)?; - - let (class, arrayed) = ctx.image_data(image, image_span)?; - let array_index = arrayed - .then(|| { - args.min_args += 1; - self.expression(args.next()?, ctx) - }) - .transpose()?; - - let level = class - .is_mipmapped() - .then(|| { - args.min_args += 1; - self.expression(args.next()?, ctx) - }) - .transpose()?; - - let sample = class - .is_multisampled() - .then(|| self.expression(args.next()?, ctx)) - .transpose()?; - - args.finish()?; - - ir::Expression::ImageLoad { - image, - coordinate, - array_index, - level, - sample, - } - } - "textureDimensions" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let image = self.expression(args.next()?, ctx)?; - let level = args - .next() - .map(|arg| self.expression(arg, ctx)) - .ok() - .transpose()?; - args.finish()?; - - ir::Expression::ImageQuery { - image, - query: ir::ImageQuery::Size { level }, - } - } - "textureNumLevels" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let image = self.expression(args.next()?, ctx)?; - args.finish()?; - - ir::Expression::ImageQuery { - image, - query: ir::ImageQuery::NumLevels, - } - } - "textureNumLayers" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let image = self.expression(args.next()?, ctx)?; - args.finish()?; - - ir::Expression::ImageQuery { - image, - query: ir::ImageQuery::NumLayers, - } - } - "textureNumSamples" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let image = self.expression(args.next()?, ctx)?; - args.finish()?; - - ir::Expression::ImageQuery { - image, - query: ir::ImageQuery::NumSamples, - } - } - "rayQueryInitialize" => { - let mut args = ctx.prepare_args(arguments, 3, span); - let query = self.ray_query_pointer(args.next()?, ctx)?; - let acceleration_structure = self.expression(args.next()?, ctx)?; - let descriptor = self.expression(args.next()?, ctx)?; - args.finish()?; - - let _ = ctx.module.generate_ray_desc_type(); - let fun = ir::RayQueryFunction::Initialize { - acceleration_structure, - descriptor, - }; - - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .extend(rctx.emitter.finish(&rctx.function.expressions)); - rctx.emitter.start(&rctx.function.expressions); - rctx.block - .push(ir::Statement::RayQuery { query, fun }, span); - return Ok(None); - } - "getCommittedHitVertexPositions" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let query = self.ray_query_pointer(args.next()?, ctx)?; - args.finish()?; - - let _ = ctx.module.generate_vertex_return_type(); - - ir::Expression::RayQueryVertexPositions { - query, - committed: true, - } - } - "getCandidateHitVertexPositions" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let query = self.ray_query_pointer(args.next()?, ctx)?; - args.finish()?; - - let _ = ctx.module.generate_vertex_return_type(); - - ir::Expression::RayQueryVertexPositions { - query, - committed: false, - } - } - "rayQueryProceed" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let query = self.ray_query_pointer(args.next()?, ctx)?; - args.finish()?; - - let result = - ctx.interrupt_emitter(ir::Expression::RayQueryProceedResult, span)?; - let fun = ir::RayQueryFunction::Proceed { result }; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .push(ir::Statement::RayQuery { query, fun }, span); - return Ok(Some(result)); - } - "rayQueryGenerateIntersection" => { - let mut args = ctx.prepare_args(arguments, 2, span); - let query = self.ray_query_pointer(args.next()?, ctx)?; - let hit_t = self.expression(args.next()?, ctx)?; - args.finish()?; - - let fun = ir::RayQueryFunction::GenerateIntersection { hit_t }; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .push(ir::Statement::RayQuery { query, fun }, span); - return Ok(None); - } - "rayQueryConfirmIntersection" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let query = self.ray_query_pointer(args.next()?, ctx)?; - args.finish()?; - - let fun = ir::RayQueryFunction::ConfirmIntersection; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .push(ir::Statement::RayQuery { query, fun }, span); - return Ok(None); - } - "rayQueryTerminate" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let query = self.ray_query_pointer(args.next()?, ctx)?; - args.finish()?; - - let fun = ir::RayQueryFunction::Terminate; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .push(ir::Statement::RayQuery { query, fun }, span); - return Ok(None); - } - "rayQueryGetCommittedIntersection" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let query = self.ray_query_pointer(args.next()?, ctx)?; - args.finish()?; - - let _ = ctx.module.generate_ray_intersection_type(); - ir::Expression::RayQueryGetIntersection { - query, - committed: true, - } - } - "rayQueryGetCandidateIntersection" => { - let mut args = ctx.prepare_args(arguments, 1, span); - let query = self.ray_query_pointer(args.next()?, ctx)?; - args.finish()?; - - let _ = ctx.module.generate_ray_intersection_type(); - ir::Expression::RayQueryGetIntersection { - query, - committed: false, - } - } - "RayDesc" => { - let ty = ctx.module.generate_ray_desc_type(); - let handle = self.construct( - span, - &ast::ConstructorType::Type(ty), - function.span, - arguments, - ctx, - )?; - return Ok(Some(handle)); - } - "subgroupBallot" => { - let mut args = ctx.prepare_args(arguments, 0, span); - let predicate = if arguments.len() == 1 { - Some(self.expression(args.next()?, ctx)?) - } else { - None - }; - args.finish()?; - - let result = - ctx.interrupt_emitter(ir::Expression::SubgroupBallotResult, span)?; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block - .push(ir::Statement::SubgroupBallot { result, predicate }, span); - return Ok(Some(result)); - } - "quadSwapX" => { - let mut args = ctx.prepare_args(arguments, 1, span); - - let argument = self.expression(args.next()?, ctx)?; - args.finish()?; - - let ty = ctx.register_type(argument)?; - - let result = ctx.interrupt_emitter( - crate::Expression::SubgroupOperationResult { ty }, - span, - )?; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block.push( - crate::Statement::SubgroupGather { - mode: crate::GatherMode::QuadSwap(crate::Direction::X), - argument, - result, - }, - span, - ); - return Ok(Some(result)); - } - - "quadSwapY" => { - let mut args = ctx.prepare_args(arguments, 1, span); - - let argument = self.expression(args.next()?, ctx)?; - args.finish()?; - - let ty = ctx.register_type(argument)?; - - let result = ctx.interrupt_emitter( - crate::Expression::SubgroupOperationResult { ty }, - span, - )?; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block.push( - crate::Statement::SubgroupGather { - mode: crate::GatherMode::QuadSwap(crate::Direction::Y), - argument, - result, - }, - span, - ); - return Ok(Some(result)); - } - - "quadSwapDiagonal" => { - let mut args = ctx.prepare_args(arguments, 1, span); - - let argument = self.expression(args.next()?, ctx)?; - args.finish()?; - - let ty = ctx.register_type(argument)?; - - let result = ctx.interrupt_emitter( - crate::Expression::SubgroupOperationResult { ty }, - span, - )?; - let rctx = ctx.runtime_expression_ctx(span)?; - rctx.block.push( - crate::Statement::SubgroupGather { - mode: crate::GatherMode::QuadSwap(crate::Direction::Diagonal), - argument, - result, - }, - span, - ); - return Ok(Some(result)); - } - _ => { - return Err(Box::new(Error::UnknownIdent(function.span, function.name))) - } - } - }; - - let expr = ctx.append_expression(expr, span)?; - Ok(Some(expr)) - } + None => self.call_builtin(function, arguments, is_statement, ctx), } } diff --git a/naga/tests/in/wgsl/subgroup-operations.wgsl b/naga/tests/in/wgsl/subgroup-operations.wgsl index 26b3d98e84..fae0197f5f 100644 --- a/naga/tests/in/wgsl/subgroup-operations.wgsl +++ b/naga/tests/in/wgsl/subgroup-operations.wgsl @@ -11,32 +11,32 @@ fn main( ) { subgroupBarrier(); - subgroupBallot((subgroup_invocation_id & 1u) == 1u); - subgroupBallot(); + _ = subgroupBallot((subgroup_invocation_id & 1u) == 1u); + _ = subgroupBallot(); - subgroupAll(subgroup_invocation_id != 0u); - subgroupAny(subgroup_invocation_id == 0u); - subgroupAdd(subgroup_invocation_id); - subgroupMul(subgroup_invocation_id); - subgroupMin(subgroup_invocation_id); - subgroupMax(subgroup_invocation_id); - subgroupAnd(subgroup_invocation_id); - subgroupOr(subgroup_invocation_id); - subgroupXor(subgroup_invocation_id); - subgroupExclusiveAdd(subgroup_invocation_id); - subgroupExclusiveMul(subgroup_invocation_id); - subgroupInclusiveAdd(subgroup_invocation_id); - subgroupInclusiveMul(subgroup_invocation_id); + _ = subgroupAll(subgroup_invocation_id != 0u); + _ = subgroupAny(subgroup_invocation_id == 0u); + _ = subgroupAdd(subgroup_invocation_id); + _ = subgroupMul(subgroup_invocation_id); + _ = subgroupMin(subgroup_invocation_id); + _ = subgroupMax(subgroup_invocation_id); + _ = subgroupAnd(subgroup_invocation_id); + _ = subgroupOr(subgroup_invocation_id); + _ = subgroupXor(subgroup_invocation_id); + _ = subgroupExclusiveAdd(subgroup_invocation_id); + _ = subgroupExclusiveMul(subgroup_invocation_id); + _ = subgroupInclusiveAdd(subgroup_invocation_id); + _ = subgroupInclusiveMul(subgroup_invocation_id); - subgroupBroadcastFirst(subgroup_invocation_id); - subgroupBroadcast(subgroup_invocation_id, 4u); - subgroupShuffle(subgroup_invocation_id, sizes.subgroup_size - 1u - subgroup_invocation_id); - subgroupShuffleDown(subgroup_invocation_id, 1u); - subgroupShuffleUp(subgroup_invocation_id, 1u); - subgroupShuffleXor(subgroup_invocation_id, sizes.subgroup_size - 1u); + _ = subgroupBroadcastFirst(subgroup_invocation_id); + _ = subgroupBroadcast(subgroup_invocation_id, 4u); + _ = subgroupShuffle(subgroup_invocation_id, sizes.subgroup_size - 1u - subgroup_invocation_id); + _ = subgroupShuffleDown(subgroup_invocation_id, 1u); + _ = subgroupShuffleUp(subgroup_invocation_id, 1u); + _ = subgroupShuffleXor(subgroup_invocation_id, sizes.subgroup_size - 1u); - quadBroadcast(subgroup_invocation_id, 4u); - quadSwapX(subgroup_invocation_id); - quadSwapY(subgroup_invocation_id); - quadSwapDiagonal(subgroup_invocation_id); + _ = quadBroadcast(subgroup_invocation_id, 4u); + _ = quadSwapX(subgroup_invocation_id); + _ = quadSwapY(subgroup_invocation_id); + _ = quadSwapDiagonal(subgroup_invocation_id); } diff --git a/naga/tests/naga/wgsl_errors.rs b/naga/tests/naga/wgsl_errors.rs index 9fbc11563f..aca6086ec7 100644 --- a/naga/tests/naga/wgsl_errors.rs +++ b/naga/tests/naga/wgsl_errors.rs @@ -3877,7 +3877,7 @@ fn subgroup_capability() { &format!(" {stage_attr} fn main() {{ - subgroupBallot(); + _ = subgroupBallot(); }} "), Err(naga::valid::ValidationError::EntryPoint { @@ -3902,7 +3902,7 @@ fn subgroup_capability() { " {stage_attr} fn main() {{ - subgroupBallot(); + _ = subgroupBallot(); }} " ), @@ -3919,7 +3919,7 @@ fn subgroup_capability() { " @vertex fn main() -> @builtin(position) vec4 {{ - subgroupBallot(); + _ = subgroupBallot(); return vec4(); }} ": @@ -3931,7 +3931,7 @@ fn subgroup_capability() { " @vertex fn main() -> @builtin(position) vec4 {{ - subgroupBallot(); + _ = subgroupBallot(); return vec4(); }} ", @@ -4028,7 +4028,7 @@ fn subgroup_invalid_broadcast() { check_validation! { r#" fn main(id: u32) { - subgroupBroadcast(123, id); + _ = subgroupBroadcast(123, id); } "#: Err(naga::valid::ValidationError::Function { @@ -4042,7 +4042,7 @@ fn subgroup_invalid_broadcast() { check_validation! { r#" fn main(id: u32) { - quadBroadcast(123, id); + _ = quadBroadcast(123, id); } "#: Err(naga::valid::ValidationError::Function {