0

I am currently working on optimizing the rust jpeg decoder crate using SIMD. In order to avoid long repetitions in the code, I would like to write a macro that generates the following matrix transposition code :

s = [
        i32x8::new(s[0].extract(0),s[1].extract(0),s[2].extract(0),s[3].extract(0),s[4].extract(0),s[5].extract(0),s[6].extract(0),s[7].extract(0), ),
        i32x8::new(s[0].extract(1),s[1].extract(1),s[2].extract(1),s[3].extract(1),s[4].extract(1),s[5].extract(1),s[6].extract(1),s[7].extract(1), ),
        i32x8::new(s[0].extract(2),s[1].extract(2),s[2].extract(2),s[3].extract(2),s[4].extract(2),s[5].extract(2),s[6].extract(2),s[7].extract(2), ),
        i32x8::new(s[0].extract(3),s[1].extract(3),s[2].extract(3),s[3].extract(3),s[4].extract(3),s[5].extract(3),s[6].extract(3),s[7].extract(3), ),
        i32x8::new(s[0].extract(4),s[1].extract(4),s[2].extract(4),s[3].extract(4),s[4].extract(4),s[5].extract(4),s[6].extract(4),s[7].extract(4), ),
        i32x8::new(s[0].extract(5),s[1].extract(5),s[2].extract(5),s[3].extract(5),s[4].extract(5),s[5].extract(5),s[6].extract(5),s[7].extract(5), ),
        i32x8::new(s[0].extract(6),s[1].extract(6),s[2].extract(6),s[3].extract(6),s[4].extract(6),s[5].extract(6),s[6].extract(6),s[7].extract(6), ),
        i32x8::new(s[0].extract(7),s[1].extract(7),s[2].extract(7),s[3].extract(7),s[4].extract(7),s[5].extract(7),s[6].extract(7),s[7].extract(7), ),
];

The macro should be able to generate the code for different matrix sizes (4 or 8).

I have tried several different approaches, but I never manage to get the macro to repeat n times an n-item pattern.

The most logical to me would be:

macro_rules! square {
    (($($x:tt),*), ($($y:tt),*)) => {
        [
            $([ 
                $( s[$x].extract($y) ),* 
            ]),*
        ]
    };
    ($($x:expr),*) => { square!( ($($x),*) , ($($x),*) ) };
}

but it fails with

error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
lovasoa
  • 6,419
  • 1
  • 35
  • 45

1 Answers1

3

You can do it, but you will need to handle the outer repetition through recursion:

macro_rules! square {
    (@row [$($acc:expr),*] [$($before:expr),*] $current:expr $(, $after:expr)*) => {
        square!(@row
            [ $($acc,)*
              stringify!($(s[$current].extract ($before),)*
                         s[$current].extract ($current)
                         $(, s[$current].extract ($after))*) ]
            [ $($before,)* $current ]
            $($after),*)
    };
    (@row [$($acc:tt)*] [$($before:expr),*]) => { vec![ $($acc)* ] };
    ($($r:expr),*) => {
        square!(@row [] [] $($r),*)
    };
}

Playground

I've called stringify! so that the code would compile on the playground without the dependencies. You will need to replace them to fit your needs (probably just remove the stringify! invocation and replace s by an identifier you pass to the macro).

The idea of accumulating the values in $acc and outputting them all at once at the end of the recursion is called the push-down accumulation pattern. If you are not familiar with this concept, it is described in detail in the little book of Rust macros.

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • Great, thank you ! The key element that I missed was keeping the history of the elements that were already seen (the $before) while recursing. – lovasoa Apr 16 '20 at 23:00