Skip to content

Impossible to implement VisitOperator::simd_visitor generically #2181

Open
@nagisa

Description

@nagisa

I have a snippet of code similar to this in my code base, after I made changes to upgrade to a more recent version of wasm-tools:

pub(crate) struct JoinVisitor<L, R>(pub(crate) L, pub(crate) R);

macro_rules! join_visit {
    ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => {
        $(fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output {
            (self.0.$visit($($($arg.clone()),*)?), self.1.$visit($($($arg),*)?))
        })*
    }
}

impl<'a, L, R> wasmparser::VisitOperator<'a> for JoinVisitor<L, R>
where
    L: wasmparser::VisitOperator<'a>,
    R: wasmparser::VisitOperator<'a>,
{
    type Output = (L::Output, R::Output);
    wasmparser::for_each_visit_operator!(join_visit);
}

impl<'a, L, R> wasmparser::VisitSimdOperator<'a> for JoinVisitor<L, R>
where
    L: wasmparser::VisitSimdOperator<'a>,
    R: wasmparser::VisitSimdOperator<'a>,
{
    wasmparser::for_each_visit_simd_operator!(join_visit);
}

But currently this seems to still fail to visit SIMD opcodes if I'm calling JoinVisitor::visit_operator. From the looks of it that's because there is a new function I need to implement VisitOperator::simd_visitor.

However I cannot implement this method in a generic-preserving way. If I do the naive thing

impl<'a, L, R> VisitOperator<'a> for JoinVisitor<L, R>
where
    L: VisitOperator<'a> + VisitSimdOperator<'a>,
    R: VisitOperator<'a> + VisitSimdOperator<'a>,
{
    fn simd_visitor(&mut self) -> Option<...> { Some(self) }
}

then I will have this code build, but the JoinVisitor: VisitOperator will now require an implementation of VisitSimdOperator. As far as I can tell there is no way to construct this such a way that simd_visitor returns Some only if L + R: VisitSimdOperator. An obvious

    fn simd_visitor(
        &mut self,
    ) -> Option<&mut dyn wasmparser::VisitSimdOperator<'a, Output = Self::Output>>
    where
        L: wasmparser::VisitSimdOperator<'a>,
        R: wasmparser::VisitSimdOperator<'a>,
    {
        Some(self)
    }

won't work because this method has stricter requirements than the trait being impld, nor will this:

    fn simd_visitor(
        &mut self,
    ) -> Option<&mut dyn wasmparser::VisitSimdOperator<'a, Output = Self::Output>> {
        Some(JoinVisitor(self.0.simd_visitor()?, self.1.simd_visitor()?))
    }

due to the signature requiring &mut dyn VisitSimdOperator to be returned.

Besides this particular snag, simd_visitor is also a plain footgun in that implementing a VisitSimdOperator is not all that's required on each of the implementors to support visiting SIMD operators, and the failure mode is a runtime error.


In the world where we know we'll only have VisitOperator and VisitSimdOperator this could be addressed by adding visit_operator to VisitSimdOperator as well (which would delegate to its supertrait's visit_operator if it isn't a SIMD opcode,) but that won't work out nicely if future delivers another Visit*Operator that's unrelated to VisitSimdOperator.

The only other way to address this that I can see is to add a new associated type to VisitOperator along the lines of type SimdVisitor that simd_visitor would return. Defaulting to Infallible or something.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions