Macro repetition based on constant value

Hi Rustaceans !

I'm wondering if its possible to create a macro that generate code based on a constant value.

I have this code :

const NB64BLOC: usize =5 // 
...
        for i in 0..NB64BLOC {
            let (v, carry) = BigInt::overflowing_add(self.bits[i], _rhs.bits[i], c);
            r.bits[i] = v;
            c = carry;
        } 

and I wanted to generate :

        let (v, carry) = BigInt::overflowing_add(self.bits[0], _rhs.bits[0], c);
        r.bits[0] = v;
        c = carry;

        let (v, carry) = BigInt::overflowing_add(self.bits[1], _rhs.bits[1], c);
        r.bits[1] = v;
        c = carry;

        let (v, carry) = BigInt::overflowing_add(self.bits[2], _rhs.bits[2], c);
        r.bits[2] = v;
        c = carry;

        let (v, carry) = BigInt::overflowing_add(self.bits[3], _rhs.bits[3], c);
        r.bits[3] = v;
        c = carry;

        let (v, carry) = BigInt::overflowing_add(self.bits[4], _rhs.bits[4], c);
        r.bits[4] = v;
        c = carry;

I know that it's basically loop unrolling and that the compiler should detect such a case. Nevertheless, I wanted to know if it is possible, to use this on more complicated operation.

Kind regards

No, this is not possible. Macros are purely syntactical, they cannot know anything about the value or type of constants (or anything else, really).

Ok, thanks for the answer !

It is possible to do this sort of thing in the type system using the typenum crate, but it adds a lot of complexity for usually little benefit:

use typenum as tn;

trait Trait {
    fn add(c:bool, l:&[u64], r:&[u64], res:&mut[u64])->bool;
}

impl<T1,T2> Trait for tn::UInt<T1,T2>
where Self: tn::Unsigned + core::ops::Sub<tn::B1>, tn::Sub1<Self>: Trait {
    #[inline(always)]
    fn add(carry:bool, left:&[u64], right:&[u64], result:&mut [u64])->bool {
        let carry = <tn::Sub1<Self> as Trait>::add(carry, left, right, result);
        let idx : usize= <Self as tn::Unsigned>::USIZE-1;
        let (r,rcarry) = right[idx].overflowing_add(carry as u64);
        let (res, lcarry) = left[idx].overflowing_add(r);
        result[idx]=res;
        return lcarry | rcarry;
    }
}

impl Trait for tn::UTerm {
    #[inline(always)]
    fn add(c:bool, _l:&[u64], _r:&[u64], _res:&mut[u64])->bool { c }
}

fn main() {
    let l:[u64; 2] = [ 42, 73 ];
    let r:[u64; 2] = [ 57, 26 ];
    let mut out: [u64;2] = [0,0];
    <tn::U2 as Trait>::add(true, &l, &r, &mut out);
    println!("{:?}", &out);
}

(Playground)

macro_rules! helper {(
    0 $(+ 1 $(+ $_1:tt)*)?
) => (
    $(
        helper! { 0 $(+ $_1)* }
    )?
    {
        const I: usize = 0 $(+ 1 $(+ $_1)*)?;
        let (v, carry) = BigInt::overflowing_add(self.bits[I], _rhs.bits[I], c);
        r.bits[I] = v;
        c = carry;
    }
)}

And then use helper! { 0 + 1 + 1 + 1 + 1 + 1 }.

If you want to parametrize NB64BLOC as its own definition, you will need to use the "macro callback" pattern:

macro_rules! NB64BLOC {(
    => $emit:ident !
) => ($emit! {
    0 + 1 + 1 + 1 + 1 + 1
})}

so that NB64BLOC!(=> helper!); Just Works.

  • Playground

  • It emits:

    {
        const I: usize = 0;
        let (v, carry) =
            BigInt::overflowing_add(self.bits[I], _rhs.bits[I], c);
        r.bits[I] = v;
        c = carry;
    }
    {
        const I: usize = 0 + 1;
        let (v, carry) =
            BigInt::overflowing_add(self.bits[I], _rhs.bits[I], c);
        r.bits[I] = v;
        c = carry;
    }
    {
        const I: usize = 0 + 1 + 1;
        let (v, carry) =
            BigInt::overflowing_add(self.bits[I], _rhs.bits[I], c);
        r.bits[I] = v;
        c = carry;
    }
    {
        const I: usize = 0 + 1 + 1 + 1;
        let (v, carry) =
            BigInt::overflowing_add(self.bits[I], _rhs.bits[I], c);
        r.bits[I] = v;
        c = carry;
    }
    {
        const I: usize = 0 + 1 + 1 + 1 + 1;
        let (v, carry) =
            BigInt::overflowing_add(self.bits[I], _rhs.bits[I], c);
        r.bits[I] = v;
        c = carry;
    }
    {
        const I: usize = 0 + 1 + 1 + 1 + 1 + 1;
        let (v, carry) =
            BigInt::overflowing_add(self.bits[I], _rhs.bits[I], c);
        r.bits[I] = v;
        c = carry;
    };
    

The other option is to use a procedural macro:

use ::proc_macro::TokenStream;
use ::proc_macro2::{TokenStream as TokenStream2};
use ::quote::quote;
use ::syn::*;

#[proc_macro] pub
fn helper (input: TokenStream) -> TokenStream
{
    let n: usize =
        match ::syn::parse::<LitInt>(input)
                .and_then(|lit| lit.base10_parse())
        {
            | Ok(it) => it,
            | Err(err) => return err.to_compile_error().into(),
        }
    ;
    let mut ret = TokenStream2::new();
    let mut emit = |tokens: TokenStream2| ret.extend(tokens);
    for i in 0 .. n {
        emit(quote! {
            let (v, carry) = BigInt::overflowing_add(self.bits[#i], _rhs.bits[#i], c);
            r.bits[#i] = v;
            c = carry;
        });
    }
    ret.into()
}

so that helper!(5) Just Works (but again, if you want that to work with a factored out NB64BLOC, you'll need to use a macro callback, and expand to $emit! { 5 }, so that NB64BLOC!(=> helper) works.

1 Like

Many thanks @Yandros, @2e71828 for your complementary answers. As mentioned, theses solutions involves a lot of additional complexity (I'm still a Rust beginner) and / or external crates. I'm gonna have a look on how conditional compilation work on Rust, and see if it's possible to define new attribute e.g. BIT256, BIT512 ... and provide the unrolled implementation for each. This leads to longer code, but rather simple solution :slight_smile:

Thanks again for your time and suggestions, it's really appreciated.