Hi.
I'm writing a macro so that I can be sure that my code will be unrolled and hence, I can have it defined inside a const
fn.
I'd like to build a macro that generates the following (notice we're already inside another macro):
let (r0, carry) = mac(0, val[0], $r2.0[0], 0);
let (r1, carry) = mac(0, val[0], $r2.0[1], carry);
let (r2, carry) = mac(0, val[0], $r2.0[2], carry);
let (r3, r4) = mac(0, val[0], $r2.0[3], carry);
let (r1, carry) = mac(r1, val[1], $r2.0[0], 0);
let (r2, carry) = mac(r2, val[1], $r2.0[1], carry);
let (r3, carry) = mac(r3, val[1], $r2.0[2], carry);
let (r4, r5) = mac(r4, val[1], $r2.0[3], carry);
let (r2, carry) = mac(r2, val[2], $r2.0[0], 0);
let (r3, carry) = mac(r3, val[2], $r2.0[1], carry);
let (r4, carry) = mac(r4, val[2], $r2.0[2], carry);
let (r5, r6) = mac(r5, val[2], $r2.0[3], carry);
let (r3, carry) = mac(r3, val[3], $r2.0[0], 0);
let (r4, carry) = mac(r4, val[3], $r2.0[1], carry);
let (r5, carry) = mac(r5, val[3], $r2.0[2], carry);
let (r6, r7) = mac(r6, val[3], $r2.0[3], carry);
Where the number of r
s and blocks varies depending on a top-level macro attribute called $num_limbs
.
What I did?
#[macro_use]
use paste::paste;
#[macro_export]
macro_rules! mac_round {
($num_limbs:expr, $idx:expr, $val:expr, $r2:expr, $carry:expr) => {
for (i, j) in ($idx..($num_limbs + $idx - 2)).into_iter().enumerate() {
paste::paste! { let (crate::r!(j), $carry) = mac(crate::r!(j), $val[$idx], $r2.0[i], $carry); }
}
paste::paste! { let (crate::r!($num_limbs + $idx -1), crate::r!($num_limbs + $idx)) = mac(crate::r!($num_limbs + $idx -1), $val[$idx], $r2.0[$limb_len + $idx - 1], $carry); }
};
}
#[macro_export]
macro_rules! mac0 {
( $num_limbs:expr, $val:expr, $r2:expr, $carry:expr) => {
paste::paste! { let (crate::r!(i), carry) = mac(0, $val[0], $r2.0[0], 0); }
for i in (1..$num_limbs).into_iter() {
paste::paste! { let (crate::r!(i), carry) = mac(0, $val[0], $r2.0[i], $carry); }
}
};
}
#[macro_export]
macro_rules! mac {
($num_limbs:expr, $val:expr, $r2:expr) => {
// for idx in 0..2 * num_limbs {
// paste::paste! { let r!(idx) = 0u64;}
// }
paste::paste! { let carry = 0u64; };
crate::mac0!($num_limbs, $val, $r2, carry);
for i in (1..$num_limbs).into_iter() {
crate::mac_round!($num_limbs, i, $val, $r2, carry)
}
};
}
#[macro_export]
macro_rules! r {
($idx: expr) => {
paste::paste! {[<r $idx>]}
};
}
So far, I was expecting my for loops to be executed at compile time resulting on the pasting of the code X times.
But cargo-expand
shows:
let carry = 0u64;
let (ri, carry) = mac(0, val[0], R2.0[0], 0);
for i in (1..4).into_iter() {
let (ri, carry) = mac(0, val[0], R2.0[i], carry);
}
for i in (1..4).into_iter() {
for (i, j) in (i..(4 + i - 2)).into_iter().enumerate() {
let (rj, carry) = mac(rj, val[i], R2.0[i], carry);
}
}
The question then is immediate. Is there any way (I can port the loops to be iterators) on which I can run this for loop on any other way (recursive macro that decreases/increases a number or whatever). So that I can print rust code at compile time based on a constant that is provided also at compile time to a macro?