Skip to main content

for_each_layout

Macro for_each_layout 

Source
macro_rules! for_each_layout {
    (all, $callback:ident $(, $extra:tt)*) => { ... };
    (fixed, $callback:ident $(, $extra:tt)*) => { ... };
    (ordered, $callback:ident $(, $extra:tt)*) => { ... };
    (integer, $callback:ident $(, $extra:tt)*) => { ... };
    (signed, $callback:ident $(, $extra:tt)*) => { ... };
    (unsigned, $callback:ident $(, $extra:tt)*) => { ... };
    (exact, $callback:ident $(, $extra:tt)*) => { ... };
    (narrow, $callback:ident $(, $extra:tt)*) => { ... };
    (float, $callback:ident $(, $extra:tt)*) => { ... };
}
Expand description

Calls $callback with one group of physical layouts.

Each entry is (variant, element type, zero), where the variant is the Data variant, the element type is what one value of it is, and the zero is the value that fills a slot whose row is null. A caller that does not need all three ignores the ones it does not need.

The callback is a macro the caller has already defined, almost always a macro_rules inside the function that needs it, so that its body can refer to the function’s own locals. It is passed a comma separated list of parenthesised triples and should match $(($variant:ident, $native:ty, $zero:expr)),+ $(,)?.

Anything after the callback name is passed through ahead of the list, one token tree each, for the callers that generate a loop per layout inside another loop per layout and need to hand the inner one what the outer one bound. A caller taking one of those matches it first, as in ($values:expr, $(($variant:ident, $native:ty, $zero:expr)),+ $(,)?).

use rudb_vector::{Data, for_each_layout};

fn widest(data: &Data) -> Option<i128> {
    macro_rules! biggest {
        ($(($variant:ident, $native:ty, $zero:expr)),+ $(,)?) => {
            match data {
                $(Data::$variant(values) => {
                    values.iter().copied().map(i128::from).max().or(Some($zero))
                })+
                _ => None,
            }
        };
    }
    for_each_layout!(narrow, biggest)
}

assert_eq!(widest(&Data::Int32(vec![3, 9, 4].into())), Some(9));
assert_eq!(widest(&Data::Float64(vec![3.0].into())), None);

§The groups

  • all, every layout that holds values. Data::Empty is not in it, because it holds none and has nothing for a loop to read.
  • fixed, every layout whose run is a Buffer of a Copy element. These are the ones where a null slot can be filled with a zero and a gather is a copy of fixed width slots rather than a copy of bytes.
  • ordered, the layouts whose SQL order is the derived order of the element type. A float is not in it, because NaN orders where SQL says rather than where the hardware says, and a string is not in it, because its order is over the bytes a view points at.
  • integer, the ten integer widths.
  • signed and unsigned, the five of each that integer is made of. Several kernels want one half and not the other, negation being the clearest, since negating an unsigned value is an overflow at every row but zero and a loop for it would be a loop that exists to fail.
  • exact, the integers whose every value fits in an i128, so a kernel can widen the lot into one accumulator type. UInt128 is the one that does not.
  • narrow, the integers narrower than 128 bits. Two things follow from that and both of them are used. The total of a whole vector of them still fits in an i128, which is what lets the only overflow check in a sum be the one at the vector boundary rather than one per row, and a run of i128 narrows into any of them, which is the shape the cast path works in.
  • float, the two IEEE widths.