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. */

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.

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); }
}

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.