diff --git a/naga/src/common/diagnostic_display.rs b/naga/src/common/diagnostic_display.rs index a706ed706..994bebee2 100644 --- a/naga/src/common/diagnostic_display.rs +++ b/naga/src/common/diagnostic_display.rs @@ -1,6 +1,6 @@ //! Displaying Naga IR terms in diagnostic output. -use crate::proc::GlobalCtx; +use crate::proc::{GlobalCtx, Rule}; use crate::{Handle, Scalar, Type, TypeInner}; #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] @@ -83,6 +83,23 @@ impl fmt::Display for DiagnosticDisplay<(&TypeInner, GlobalCtx<'_>)> { } } +impl fmt::Display for DiagnosticDisplay<(&str, &Rule, GlobalCtx<'_>)> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (name, rule, ref ctx) = self.0; + + #[cfg(any(feature = "wgsl-in", feature = "wgsl-out"))] + ctx.write_type_rule(name, rule, f)?; + + #[cfg(not(any(feature = "wgsl-in", feature = "wgsl-out")))] + { + let _ = ctx; + write!(f, "{name}({:?}) -> {:?}", rule.arguments, rule.conclusion)?; + } + + Ok(()) + } +} + impl fmt::Display for DiagnosticDisplay { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let scalar = self.0; diff --git a/naga/src/common/wgsl/types.rs b/naga/src/common/wgsl/types.rs index 786229b08..c0a2675d7 100644 --- a/naga/src/common/wgsl/types.rs +++ b/naga/src/common/wgsl/types.rs @@ -133,6 +133,37 @@ pub trait TypeContext { } } + fn write_type_conclusion( + &self, + conclusion: &crate::proc::Conclusion, + out: &mut W, + ) -> core::fmt::Result { + use crate::proc::Conclusion as Co; + + match *conclusion { + Co::Value(ref inner) => self.write_type_inner(inner, out), + Co::Predeclared(ref predeclared) => out.write_str(&predeclared.struct_name()), + } + } + + fn write_type_rule( + &self, + name: &str, + rule: &crate::proc::Rule, + out: &mut W, + ) -> core::fmt::Result { + write!(out, "fn {name}(")?; + for (i, arg) in rule.arguments.iter().enumerate() { + if i > 0 { + out.write_str(", ")?; + } + self.write_type_resolution(arg, out)? + } + out.write_str(") -> ")?; + self.write_type_conclusion(&rule.conclusion, out)?; + Ok(()) + } + fn type_to_string(&self, handle: Handle) -> String { let mut buf = String::new(); self.write_type(handle, &mut buf).unwrap(); @@ -150,6 +181,12 @@ pub trait TypeContext { self.write_type_resolution(resolution, &mut buf).unwrap(); buf } + + fn type_rule_to_string(&self, name: &str, rule: &crate::proc::Rule) -> String { + let mut buf = String::new(); + self.write_type_rule(name, rule, &mut buf).unwrap(); + buf + } } fn try_write_type_inner(ctx: &C, inner: &TypeInner, out: &mut W) -> Result<(), WriteTypeError> diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index 4a1f3a30a..b7c55a52b 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -271,6 +271,75 @@ pub(crate) enum Error<'a> { expected: Range, found: u32, }, + /// No overload of this function accepts this many arguments. + TooManyArguments { + /// The name of the function being called. + function: String, + + /// The function name in the call expression. + call_span: Span, + + /// The first argument that is unacceptable. + arg_span: Span, + + /// Maximum number of arguments accepted by any overload of + /// this function. + max_arguments: u32, + }, + /// A value passed to a builtin function has a type that is not + /// accepted by any overload of the function. + WrongArgumentType { + /// The name of the function being called. + function: String, + + /// The function name in the call expression. + call_span: Span, + + /// The first argument whose type is unacceptable. + arg_span: Span, + + /// The index of the first argument whose type is unacceptable. + arg_index: u32, + + /// That argument's actual type. + arg_ty: String, + + /// The set of argument types that would have been accepted for + /// this argument, given the prior arguments. + allowed: Vec, + }, + /// A value passed to a builtin function has a type that is not + /// accepted, given the earlier arguments' types. + InconsistentArgumentType { + /// The name of the function being called. + function: String, + + /// The function name in the call expression. + call_span: Span, + + /// The first unacceptable argument. + arg_span: Span, + + /// The index of the first unacceptable argument. + arg_index: u32, + + /// The actual type of the first unacceptable argument. + arg_ty: String, + + /// The prior argument whose type made the `arg_span` argument + /// unacceptable. + inconsistent_span: Span, + + /// The index of the `inconsistent_span` argument. + inconsistent_index: u32, + + /// The type of the `inconsistent_span` argument. + inconsistent_ty: String, + + /// The types that would have been accepted instead of the + /// first unacceptable argument. + allowed: Vec, + }, FunctionReturnsVoid(Span), FunctionMustUseUnused(Span), FunctionMustUseReturnsVoid(Span, Span), @@ -402,7 +471,8 @@ impl<'a> Error<'a> { "workgroup size separator (`,`) or a closing parenthesis".to_string() } ExpectedToken::GlobalItem => concat!( - "global item (`struct`, `const`, `var`, `alias`, `fn`, `diagnostic`, `enable`, `requires`, `;`) ", + "global item (`struct`, `const`, `var`, `alias`, ", + "`fn`, `diagnostic`, `enable`, `requires`, `;`) ", "or the end of the file" ) .to_string(), @@ -831,6 +901,74 @@ impl<'a> Error<'a> { labels: vec![(span, "wrong number of arguments".into())], notes: vec![], }, + Error::TooManyArguments { + ref function, + call_span, + arg_span, + max_arguments, + } => ParseError { + message: format!("too many arguments passed to `{function}`"), + labels: vec![ + (call_span, "".into()), + (arg_span, format!("unexpected argument #{}", max_arguments + 1).into()) + ], + notes: vec![ + format!("The `{function}` function accepts at most {max_arguments} argument(s)") + ], + }, + Error::WrongArgumentType { + ref function, + call_span, + arg_span, + arg_index, + ref arg_ty, + ref allowed, + } => { + let message = format!( + "wrong type passed as argument #{} to `{function}`", + arg_index + 1, + ); + let labels = vec![ + (call_span, "".into()), + (arg_span, format!("argument #{} has type `{arg_ty}`", arg_index + 1).into()) + ]; + + let mut notes = vec![]; + notes.push(format!("`{function}` accepts the following types for argument #{}:", arg_index + 1)); + notes.extend(allowed.iter().map(|ty| format!("allowed type: {ty}"))); + + ParseError { message, labels, notes } + }, + Error::InconsistentArgumentType { + ref function, + call_span, + arg_span, + arg_index, + ref arg_ty, + inconsistent_span, + inconsistent_index, + ref inconsistent_ty, + ref allowed + } => { + let message = format!( + "inconsistent type passed as argument #{} to `{function}`", + arg_index + 1, + ); + let labels = vec![ + (call_span, "".into()), + (arg_span, format!("argument #{} has type {arg_ty}", arg_index + 1).into()), + (inconsistent_span, format!( + "this argument has type {inconsistent_ty}, which constrains subsequent arguments" + ).into()), + ]; + let mut notes = vec![ + format!("Because argument #{} has type {inconsistent_ty}, only the following types", inconsistent_index + 1), + format!("(or types that automatically convert to them) are accepted for argument #{}:", arg_index + 1), + ]; + notes.extend(allowed.iter().map(|ty| format!("allowed type: {ty}"))); + + ParseError { message, labels, notes } + } Error::FunctionReturnsVoid(span) => ParseError { message: "function does not return any value".to_string(), labels: vec![(span, "".into())], diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index d821c2592..af9573503 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -6,7 +6,7 @@ use alloc::{ }; use core::num::NonZeroU32; -use crate::common::wgsl::TypeContext; +use crate::common::wgsl::{TryToWgsl, TypeContext}; use crate::front::wgsl::error::{Error, ExpectedToken, InvalidAssignmentType}; use crate::front::wgsl::index::Index; use crate::front::wgsl::parse::number::Number; @@ -491,6 +491,19 @@ impl<'source, 'temp, 'out> ExpressionContext<'source, 'temp, 'out> { } } + /// Return a wrapper around `value` suitable for formatting. + /// + /// Return a wrapper around `value` that implements + /// [`core::fmt::Display`] in a form suitable for use in + /// diagnostic messages. + fn as_diagnostic_display( + &self, + value: T, + ) -> crate::common::DiagnosticDisplay<(T, crate::proc::GlobalCtx)> { + let ctx = self.module.to_ctx(); + crate::common::DiagnosticDisplay((value, ctx)) + } + fn append_expression( &mut self, expr: crate::Expression, @@ -2439,52 +2452,204 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { crate::Expression::Derivative { axis, ctrl, expr } } else if let Some(fun) = conv::map_standard_fun(function.name) { - let expected = fun.argument_count() as _; - let mut args = ctx.prepare_args(arguments, expected, span); + use crate::proc::OverloadSet as _; - let arg = self.expression(args.next()?, ctx)?; - let arg1 = args - .next() - .map(|x| self.expression(x, ctx)) - .ok() - .transpose()?; - let arg2 = args - .next() - .map(|x| self.expression(x, ctx)) - .ok() - .transpose()?; - let arg3 = args - .next() - .map(|x| self.expression(x, ctx)) - .ok() - .transpose()?; + let fun_overloads = fun.overloads(); + let mut remaining_overloads = fun_overloads.clone(); + let min_arguments = remaining_overloads.min_arguments(); + let max_arguments = remaining_overloads.max_arguments(); + if arguments.len() < min_arguments { + return Err(Box::new(Error::WrongArgumentCount { + span, + expected: min_arguments as u32..max_arguments as u32, + found: arguments.len() as u32, + })); + } + if arguments.len() > max_arguments { + return Err(Box::new(Error::TooManyArguments { + function: fun.to_wgsl_for_diagnostics(), + call_span: span, + arg_span: ctx.ast_expressions.get_span(arguments[max_arguments]), + max_arguments: max_arguments as _, + })); + } - args.finish()?; + log::debug!( + "Initial overloads: {:#?}", + remaining_overloads.for_debug(&ctx.module.types) + ); + let mut unconverted_arguments = Vec::with_capacity(arguments.len()); + for (arg_index, &arg) in arguments.iter().enumerate() { + let lowered = self.expression_for_abstract(arg, ctx)?; + let ty = resolve_inner!(ctx, lowered); + log::debug!( + "Supplying argument {arg_index} of type {}", + crate::common::DiagnosticDisplay((ty, ctx.module.to_ctx())) + ); + let next_remaining_overloads = + remaining_overloads.arg(arg_index, ty, &ctx.module.types); - if fun == crate::MathFunction::Modf || fun == crate::MathFunction::Frexp { - if let Some((size, scalar)) = match *resolve_inner!(ctx, arg) { - crate::TypeInner::Scalar(scalar) => Some((None, scalar)), - crate::TypeInner::Vector { size, scalar, .. } => { - Some((Some(size), scalar)) + // If any argument is not a constant expression, then no overloads + // that accept abstract values should be considered. + // (`OverloadSet::concrete_only` is supposed to help impose this + // restriction.) However, no `MathFunction` accepts a mix of + // abstract and concrete arguments, so we don't need to worry + // about that here. + + log::debug!( + "Remaining overloads: {:#?}", + next_remaining_overloads.for_debug(&ctx.module.types) + ); + + // If the set of remaining overloads is empty, then this argument's type + // was unacceptable. Diagnose the problem and produce an error message. + if next_remaining_overloads.is_empty() { + let function = fun.to_wgsl_for_diagnostics(); + let call_span = span; + let arg_span = ctx.ast_expressions.get_span(arg); + let arg_ty = ctx.as_diagnostic_display(ty).to_string(); + + // Is this type *ever* permitted for the arg_index'th argument? + // For example, `bool` is never permitted for `max`. + let only_this_argument = + fun_overloads.arg(arg_index, ty, &ctx.module.types); + if only_this_argument.is_empty() { + // No overload of `fun` accepts this type as the + // arg_index'th argument. Determine the set of types that + // would ever be allowed there. + let allowed: Vec = fun_overloads + .allowed_args(arg_index, &ctx.module.to_ctx()) + .iter() + .map(|ty| ctx.type_resolution_to_string(ty)) + .collect(); + + if allowed.is_empty() { + // No overload of `fun` accepts any argument at this + // index, so it's a simple case of excess arguments. + // However, since each `MathFunction`'s overloads all + // have the same arity, we should have detected this + // earlier. + unreachable!("expected all overloads to have the same arity"); + } + + // Some overloads of `fun` do accept this many arguments, + // but none accept one of this type. + return Err(Box::new(Error::WrongArgumentType { + function, + call_span, + arg_span, + arg_index: arg_index as u32, + arg_ty, + allowed, + })); } - _ => None, - } { - ctx.module.generate_predeclared_type( - if fun == crate::MathFunction::Modf { - crate::PredeclaredType::ModfResult { size, scalar } - } else { - crate::PredeclaredType::FrexpResult { size, scalar } - }, - ); + + // This argument's type is accepted by some overloads---just + // not those overloads that remain, given the prior arguments. + // For example, `max` accepts `f32` as its second argument - + // but not if the first was `i32`. + + // Build a list of the types that would have been accepted here, + // given the prior arguments. + let allowed: Vec = remaining_overloads + .allowed_args(arg_index, &ctx.module.to_ctx()) + .iter() + .map(|ty| ctx.type_resolution_to_string(ty)) + .collect(); + + // Re-run the argument list to determine which prior argument + // made this one unacceptable. + let mut remaining_overloads = fun_overloads; + for (prior_index, &prior_expr) in + unconverted_arguments.iter().enumerate() + { + let prior_ty = + ctx.typifier()[prior_expr].inner_with(&ctx.module.types); + remaining_overloads = remaining_overloads.arg( + prior_index, + prior_ty, + &ctx.module.types, + ); + if remaining_overloads + .arg(arg_index, ty, &ctx.module.types) + .is_empty() + { + // This is the argument that killed our dreams. + let inconsistent_span = + ctx.ast_expressions.get_span(arguments[prior_index]); + let inconsistent_ty = + ctx.as_diagnostic_display(prior_ty).to_string(); + + if allowed.is_empty() { + // Some overloads did accept `ty` at `arg_index`, but + // given the arguments up through `prior_expr`, we see + // no types acceptable at `arg_index`. This means that some + // overloads expect fewer arguments than others. However, + // each `MathFunction`'s overloads have the same arity, so this + // should be impossible. + unreachable!( + "expected all overloads to have the same arity" + ); + } + + // Report `arg`'s type as inconsistent with `prior_expr`'s + return Err(Box::new(Error::InconsistentArgumentType { + function, + call_span, + arg_span, + arg_index: arg_index as u32, + arg_ty, + inconsistent_span, + inconsistent_index: prior_index as u32, + inconsistent_ty, + allowed, + })); + } + } + unreachable!("Failed to eliminate argument type when re-tried"); } + remaining_overloads = next_remaining_overloads; + unconverted_arguments.push(lowered); + } + + // Select the most preferred type rule for this call, + // given the argument types supplied above. + let rule = remaining_overloads.most_preferred(); + + let mut converted_arguments = Vec::with_capacity(arguments.len()); + for (i, (&ast, unconverted)) in + arguments.iter().zip(unconverted_arguments).enumerate() + { + let goal_inner = rule.arguments[i].inner_with(&ctx.module.types); + let converted = match goal_inner.scalar_for_conversions(&ctx.module.types) { + Some(goal_scalar) => { + let arg_span = ctx.ast_expressions.get_span(ast); + ctx.try_automatic_conversion_for_leaf_scalar( + unconverted, + goal_scalar, + arg_span, + )? + } + // No conversion is necessary. + None => unconverted, + }; + + converted_arguments.push(converted); + } + + // If this function returns a predeclared type, register it + // in `Module::special_types`. The typifier will expect to + // be able to find it there. + if let crate::proc::Conclusion::Predeclared(predeclared) = rule.conclusion { + ctx.module.generate_predeclared_type(predeclared); } crate::Expression::Math { fun, - arg, - arg1, - arg2, - arg3, + arg: converted_arguments[0], + arg1: converted_arguments.get(1).cloned(), + arg2: converted_arguments.get(2).cloned(), + arg3: converted_arguments.get(3).cloned(), } } else if let Some(fun) = Texture::map(function.name) { self.texture_sample_helper(fun, arguments, span, ctx)? diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index 49089fda0..4ca4da554 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -7,6 +7,7 @@ mod emitter; pub mod index; mod layouter; mod namer; +mod overloads; mod terminator; mod type_methods; mod typifier; @@ -18,6 +19,7 @@ pub use emitter::Emitter; pub use index::{BoundsCheckPolicies, BoundsCheckPolicy, IndexableLength, IndexableLengthError}; pub use layouter::{Alignment, LayoutError, LayoutErrorInner, Layouter, TypeLayout}; pub use namer::{EntryPointIndex, NameKey, Namer}; +pub use overloads::{Conclusion, MissingSpecialType, OverloadSet, Rule}; pub use terminator::ensure_block_returns; use thiserror::Error; pub use type_methods::min_max_float_representable_by; diff --git a/naga/src/proc/overloads/any_overload_set.rs b/naga/src/proc/overloads/any_overload_set.rs new file mode 100644 index 000000000..f3228bb07 --- /dev/null +++ b/naga/src/proc/overloads/any_overload_set.rs @@ -0,0 +1,120 @@ +//! Dynamically dispatched [`OverloadSet`]s. + +use crate::common::DiagnosticDebug; +use crate::ir; +use crate::proc::overloads::{list, regular, OverloadSet, Rule}; +use crate::proc::{GlobalCtx, TypeResolution}; + +use alloc::vec::Vec; +use core::fmt; + +macro_rules! define_any_overload_set { + { $( $module:ident :: $name:ident, )* } => { + /// An [`OverloadSet`] that dynamically dispatches to concrete implementations. + #[derive(Clone)] + pub(in crate::proc::overloads) enum AnyOverloadSet { + $( + $name ( $module :: $name ), + )* + } + + $( + impl From<$module::$name> for AnyOverloadSet { + fn from(concrete: $module::$name) -> Self { + AnyOverloadSet::$name(concrete) + } + } + )* + + impl OverloadSet for AnyOverloadSet { + fn is_empty(&self) -> bool { + match *self { + $( + AnyOverloadSet::$name(ref x) => x.is_empty(), + )* + } + } + + fn min_arguments(&self) -> usize { + match *self { + $( + AnyOverloadSet::$name(ref x) => x.min_arguments(), + )* + } + } + + fn max_arguments(&self) -> usize { + match *self { + $( + AnyOverloadSet::$name(ref x) => x.max_arguments(), + )* + } + } + + fn arg( + &self, + i: usize, + ty: &ir::TypeInner, + types: &crate::UniqueArena, + ) -> Self { + match *self { + $( + AnyOverloadSet::$name(ref x) => AnyOverloadSet::$name(x.arg(i, ty, types)), + )* + } + } + + fn concrete_only(self, types: &crate::UniqueArena) -> Self { + match self { + $( + AnyOverloadSet::$name(x) => AnyOverloadSet::$name(x.concrete_only(types)), + )* + } + } + + fn most_preferred(&self) -> Rule { + match *self { + $( + AnyOverloadSet::$name(ref x) => x.most_preferred(), + )* + } + } + + fn overload_list(&self, gctx: &GlobalCtx<'_>) -> Vec { + match *self { + $( + AnyOverloadSet::$name(ref x) => x.overload_list(gctx), + )* + } + } + + fn allowed_args(&self, i: usize, gctx: &GlobalCtx<'_>) -> Vec { + match *self { + $( + AnyOverloadSet::$name(ref x) => x.allowed_args(i, gctx), + )* + } + } + + fn for_debug(&self, types: &crate::UniqueArena) -> impl fmt::Debug { + DiagnosticDebug((self, types)) + } + } + + impl fmt::Debug for DiagnosticDebug<(&AnyOverloadSet, &crate::UniqueArena)> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let (set, types) = self.0; + match *set { + $( + AnyOverloadSet::$name(ref x) => DiagnosticDebug((x, types)).fmt(f), + )* + } + } + } + } +} + +define_any_overload_set! { + list::List, + regular::Regular, +} diff --git a/naga/src/proc/overloads/constructor_set.rs b/naga/src/proc/overloads/constructor_set.rs new file mode 100644 index 000000000..eeb831d39 --- /dev/null +++ b/naga/src/proc/overloads/constructor_set.rs @@ -0,0 +1,174 @@ +//! A set of type constructors, represented as a bitset. + +use crate::ir; +use crate::proc::overloads::one_bits_iter::OneBitsIter; + +bitflags::bitflags! { + /// A set of type constructors. + #[derive(Copy, Clone, Debug, PartialEq)] + pub(crate) struct ConstructorSet: u16 { + const SCALAR = 1 << 0; + const VEC2 = 1 << 1; + const VEC3 = 1 << 2; + const VEC4 = 1 << 3; + const MAT2X2 = 1 << 4; + const MAT2X3 = 1 << 5; + const MAT2X4 = 1 << 6; + const MAT3X2 = 1 << 7; + const MAT3X3 = 1 << 8; + const MAT3X4 = 1 << 9; + const MAT4X2 = 1 << 10; + const MAT4X3 = 1 << 11; + const MAT4X4 = 1 << 12; + + const VECN = Self::VEC2.bits() + | Self::VEC3.bits() + | Self::VEC4.bits(); + } +} + +impl ConstructorSet { + /// Return the single-member set containing `inner`'s constructor. + pub const fn singleton(inner: &ir::TypeInner) -> ConstructorSet { + use ir::TypeInner as Ti; + use ir::VectorSize as Vs; + match *inner { + Ti::Scalar(_) => Self::SCALAR, + Ti::Vector { size, scalar: _ } => match size { + Vs::Bi => Self::VEC2, + Vs::Tri => Self::VEC3, + Vs::Quad => Self::VEC4, + }, + Ti::Matrix { + columns, + rows, + scalar: _, + } => match (columns, rows) { + (Vs::Bi, Vs::Bi) => Self::MAT2X2, + (Vs::Bi, Vs::Tri) => Self::MAT2X3, + (Vs::Bi, Vs::Quad) => Self::MAT2X4, + (Vs::Tri, Vs::Bi) => Self::MAT3X2, + (Vs::Tri, Vs::Tri) => Self::MAT3X3, + (Vs::Tri, Vs::Quad) => Self::MAT3X4, + (Vs::Quad, Vs::Bi) => Self::MAT4X2, + (Vs::Quad, Vs::Tri) => Self::MAT4X3, + (Vs::Quad, Vs::Quad) => Self::MAT4X4, + }, + _ => Self::empty(), + } + } + + pub const fn is_singleton(self) -> bool { + self.bits().is_power_of_two() + } + + /// Return an iterator over this set's members. + /// + /// Members are produced as singleton, in order from most general to least. + pub fn members(self) -> impl Iterator { + OneBitsIter::new(self.bits() as u64).map(|bit| Self::from_bits(bit as u16).unwrap()) + } + + /// Return the size of the sole element of `self`. + /// + /// # Panics + /// + /// Panic if `self` is not a singleton. + pub fn size(self) -> ConstructorSize { + use ir::VectorSize as Vs; + use ConstructorSize as Cs; + + match self { + ConstructorSet::SCALAR => Cs::Scalar, + ConstructorSet::VEC2 => Cs::Vector(Vs::Bi), + ConstructorSet::VEC3 => Cs::Vector(Vs::Tri), + ConstructorSet::VEC4 => Cs::Vector(Vs::Quad), + ConstructorSet::MAT2X2 => Cs::Matrix { + columns: Vs::Bi, + rows: Vs::Bi, + }, + ConstructorSet::MAT2X3 => Cs::Matrix { + columns: Vs::Bi, + rows: Vs::Tri, + }, + ConstructorSet::MAT2X4 => Cs::Matrix { + columns: Vs::Bi, + rows: Vs::Quad, + }, + ConstructorSet::MAT3X2 => Cs::Matrix { + columns: Vs::Tri, + rows: Vs::Bi, + }, + ConstructorSet::MAT3X3 => Cs::Matrix { + columns: Vs::Tri, + rows: Vs::Tri, + }, + ConstructorSet::MAT3X4 => Cs::Matrix { + columns: Vs::Tri, + rows: Vs::Quad, + }, + ConstructorSet::MAT4X2 => Cs::Matrix { + columns: Vs::Quad, + rows: Vs::Bi, + }, + ConstructorSet::MAT4X3 => Cs::Matrix { + columns: Vs::Quad, + rows: Vs::Tri, + }, + ConstructorSet::MAT4X4 => Cs::Matrix { + columns: Vs::Quad, + rows: Vs::Quad, + }, + _ => unreachable!("ConstructorSet was not a singleton"), + } + } +} + +/// The sizes a member of [`ConstructorSet`] might have. +#[derive(Clone, Copy)] +pub enum ConstructorSize { + /// The constructor is [`SCALAR`]. + /// + /// [`SCALAR`]: ConstructorSet::SCALAR + Scalar, + + /// The constructor is `VECN` for some `N`. + Vector(ir::VectorSize), + + /// The constructor is `MATCXR` for some `C` and `R`. + Matrix { + columns: ir::VectorSize, + rows: ir::VectorSize, + }, +} + +impl ConstructorSize { + /// Construct a [`TypeInner`] for a type with this size and the given `scalar`. + /// + /// [`TypeInner`]: ir::TypeInner + pub const fn to_inner(self, scalar: ir::Scalar) -> ir::TypeInner { + match self { + Self::Scalar => ir::TypeInner::Scalar(scalar), + Self::Vector(size) => ir::TypeInner::Vector { size, scalar }, + Self::Matrix { columns, rows } => ir::TypeInner::Matrix { + columns, + rows, + scalar, + }, + } + } +} + +macro_rules! constructor_set { + ( $( $constr:ident )|* ) => { + { + use $crate::proc::overloads::constructor_set::ConstructorSet; + ConstructorSet::empty() + $( + .union(ConstructorSet::$constr) + )* + } + } +} + +pub(in crate::proc::overloads) use constructor_set; diff --git a/naga/src/proc/overloads/list.rs b/naga/src/proc/overloads/list.rs new file mode 100644 index 000000000..62a8a83cc --- /dev/null +++ b/naga/src/proc/overloads/list.rs @@ -0,0 +1,182 @@ +//! An [`OverloadSet`] represented as a vector of rules. +//! +//! [`OverloadSet`]: crate::proc::overloads::OverloadSet + +use crate::common::{DiagnosticDebug, ForDebug, ForDebugWithTypes}; +use crate::ir; +use crate::proc::overloads::one_bits_iter::OneBitsIter; +use crate::proc::overloads::Rule; +use crate::proc::{GlobalCtx, TypeResolution}; + +use alloc::rc::Rc; +use alloc::vec::Vec; +use core::fmt; + +/// A simple list of overloads. +/// +/// Note that this type is not quite as general as it looks, in that +/// the implementation of `most_preferred` doesn't work for arbitrary +/// lists of overloads. See the documentation for [`List::rules`] for +/// details. +#[derive(Clone)] +pub(in crate::proc::overloads) struct List { + /// A bitmask of which elements of `rules` are included in the set. + members: u64, + + /// A list of type rules that are members of the set. + /// + /// These must be listed in order such that every rule in the list + /// is always more preferred than all subsequent rules in the + /// list. If there is no such arrangement of rules, then you + /// cannot use `List` to represent the overload set. + rules: Rc>, +} + +impl List { + pub(in crate::proc::overloads) fn from_rules(rules: Vec) -> List { + List { + members: len_to_full_mask(rules.len()), + rules: Rc::new(rules), + } + } + + fn members(&self) -> impl Iterator { + OneBitsIter::new(self.members).map(|mask| { + let index = mask.trailing_zeros() as usize; + (mask, &self.rules[index]) + }) + } + + fn filter(&self, mut pred: F) -> List + where + F: FnMut(&Rule) -> bool, + { + let mut filtered_members = 0; + for (mask, rule) in self.members() { + if pred(rule) { + filtered_members |= mask; + } + } + + List { + members: filtered_members, + rules: self.rules.clone(), + } + } +} + +impl crate::proc::overloads::OverloadSet for List { + fn is_empty(&self) -> bool { + self.members == 0 + } + + fn min_arguments(&self) -> usize { + self.members() + .fold(None, |best, (_, rule)| { + // This is different from `max_arguments` because + // `