Macro depending on constant value?

Hi,

I have this macro rule:

macro_rules! is_between {
    ($value:expr, $a:expr, $b:expr) => {
        $value >= $a && $value <= $b
    };
}

It's used in code like this:

pub fn to_phys_addr(mut virt_addr: u32) -> u32 {
    if is_between!(virt_addr, 0x80000000, 0x9FFFFFFF) {
        virt_addr -= 0x80000000;
    }

    if is_between!(virt_addr, 0xA0000000, 0xBFFFFFFF) {
        virt_addr -= 0xA0000000;
    }

    if is_between!(virt_addr, 0x00000000, 0x03EFFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x03F00000, 0x03FFFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04000000, 0x040FFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04100000, 0x041FFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04200000, 0x042FFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04300000, 0x043FFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04400000, 0x044FFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04500000, 0x045FFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04600000, 0x046FFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04700000, 0x047FFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04800000, 0x048FFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x04900000, 0x04FFFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x05000000, 0x05FFFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x06000000, 0x07FFFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x08000000, 0x0FFFFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x10000000, 0x1FBFFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x1FC00000, 0x1FC007BF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x1FC007C0, 0x1FC007FF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x1FC00800, 0x1FCFFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x1FD00000, 0x7FFFFFFF) {
        return virt_addr;
    } else if is_between!(virt_addr, 0x80000000, 0xFFFFFFFF) {
        return virt_addr;
    } else {
        panic!("Invalid virtual address {:x}", virt_addr);
    }
}

This bring to two (valid) warnings in compiler:

warning: comparison is useless due to type limits
   --> src/util_macro.rs:49:9
    |
49  |         $value >= $a && $value <= $b
    |         ^^^^^^
    | 
   ::: src/device.rs:118:12
    |
118 |         if is_between!(virt_addr, 0x00000000, 0x03EFFFFF) {
    |            ---------------------------------------------- in this macro invocation
    |
    = note: `#[warn(unused_comparisons)]` on by default
    = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

warning: comparison is useless due to type limits
   --> src/util_macro.rs:49:25
    |
49  |         $value >= $a && $value <= $b
    |                         ^^^^^^
    | 
   ::: src/device.rs:178:19
    |
178 |         } else if is_between!(virt_addr, 0x80000000, 0xFFFFFFFF) {
    |                   ---------------------------------------------- in this macro invocation
    |
    = note: this warning originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)

I wanted to write macro so if $a constant is min, or $b constant is max, it follow a different path:

macro_rules! is_between {
    ($value:expr, $a:expr, $b:expr) => {
        $value >= $a && $value <= $b
    };
    ($value:expr, $a:expr:is_type_min(), $b:expr) => {
        $value <= $b
    };
    ($value:expr, $a:expr, $b:expr:is_type_max()) => {
        $value >= $a
    };
}
  1. How can I do that? Does it make sense?
  2. How would you write the to_phys_addr() function?

I know I can replace the two conditions with single compare, but I wanted to keep the code explicit.

A big thanks in advance! :slight_smile:

Personally, I’d just disable that warning within this macro:

macro_rules! is_between {
    ($value:expr, $a:expr, $b:expr) => {
        {
            let (val, min, max) = ($value, $a, $b);
            #[allow(unused_comparisons)]
            val >= min && val <= max
        }
    };
}

Edit: Alternatively, this doesn’t really need to be a macro at all; it can be a regular function. In fact, it already exists as RangeInclusive::contains(), so you could write your code like this instead:


    if (0x80000000 ..= 0x9FFFFFFF).contains(&virt_addr) {
        virt_addr -= 0x80000000;
    }
    /* etc. */
3 Likes

Thanks, the assembly code looks inefficient with the range solution. It seems that RangeInclusive::contains() compare every value. It also seems longer to compile.

Release mode seems to optimize though.

RangeInclusive::contains defers to the default implementation of RangeBounds::contains:

    fn contains<U>(&self, item: &U) -> bool
    where
        T: PartialOrd<U>,
        U: ?Sized + PartialOrd<T>,
    {
        (match self.start_bound() {
            Included(ref start) => *start <= item,
            Excluded(ref start) => *start < item,
            Unbounded => true,
        }) && (match self.end_bound() {
            Included(ref end) => item <= *end,
            Excluded(ref end) => item < *end,
            Unbounded => true,
        })
    }

It’s a little more general, but otherwise the same approach as your macro uses.

1 Like

You can also use ranges in patterns, so your big if/else could be written like this:

return match virt_addr {
    0x00000000 ..= 0x03EFFFFF => virt_addr,
    0x03F00000 ..= 0x03FFFFFF => virt_addr,
    0x04000000 ..= 0x040FFFFF => virt_addr,
    /* ... */
    _ => { panic!("Invalid virtual address {:x}", virt_addr); }
}
5 Likes

This solution is very elegant!

That's because you're in the Debug mode. Try again with the Release mode. It produces pretty compact assembly.

That's what I'm saying two lines later. :slight_smile:

Rust is unusual, in that --release is often orders of magnitude faster than --debug, due to the heavy reliance on the compiler "undoing" all the abstractions into efficient machine code.