Skip to content

Commit e5f20f2

Browse files
authored
Skip codegen of unreachable int to decimal carrier arms (#10735)
The scaled-down integer to decimal cast selects its carrier as `max(carrier(S), T)`. The source type `S` and the target type `T` fix the carrier, but `match_each_decimal_value_type!` compiled the kernel for all six decimal widths in each of the 48 `(S, T)` pairs. An `if const` condition now removes the five arms that cannot match before code generation. The arm that runs is the same as before. `primitive_decimal_carrier_type` becomes a `const fn` so that the condition can use it. | | before | after | |---|---|---| | `vortex-array` release `__text` (macOS arm64) | 14,012,988 B | 13,697,852 B (-315 KB) | | `vortex-array` LLVM IR lines | 5,405,422 | 5,200,767 | Signed-off-by: Robert Kruszewski <github@robertk.io>
1 parent afa4bc8 commit e5f20f2

1 file changed

Lines changed: 71 additions & 10 deletions

File tree

  • vortex-array/src/arrays/primitive/compute

‎vortex-array/src/arrays/primitive/compute/cast.rs‎

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -312,17 +312,40 @@ where
312312
// signed carrier that can represent both the source and target.
313313
let carrier_type = primitive_decimal_carrier_type(array.ptype()).max(T::DECIMAL_TYPE);
314314
match_each_decimal_value_type!(carrier_type, |W| {
315-
let scale_factor = decimal_scale_factor::<W>(decimal_dtype.scale())?;
316-
cast_integer_values_to_decimal_buffer(values, decimal_dtype, valid_values, |value| {
317-
let value = <W as BigCast>::from(value)?;
318-
let quotient = value / scale_factor;
319-
let value = (quotient * scale_factor == value).then_some(quotient)?;
320-
let value = <T as BigCast>::from(value)?;
321-
decimal_value_fits_precision(value, decimal_dtype).then_some(value)
322-
})
315+
// `S` and `T` fix the carrier, so only one arm can match. The const condition lets the
316+
// compiler skip codegen of the kernel for the other arms.
317+
if const { is_scaled_down_carrier::<S, T, W>() } {
318+
let scale_factor = decimal_scale_factor::<W>(decimal_dtype.scale())?;
319+
cast_integer_values_to_decimal_buffer(values, decimal_dtype, valid_values, |value| {
320+
let value = <W as BigCast>::from(value)?;
321+
let quotient = value / scale_factor;
322+
let value = (quotient * scale_factor == value).then_some(quotient)?;
323+
let value = <T as BigCast>::from(value)?;
324+
decimal_value_fits_precision(value, decimal_dtype).then_some(value)
325+
})
326+
} else {
327+
unreachable!(
328+
"{carrier_type} is not the scaled-down carrier for {}",
329+
S::PTYPE
330+
)
331+
}
323332
})
324333
}
325334

335+
/// Whether `W` is the carrier that a scaled-down cast from `S` to `T` selects.
336+
const fn is_scaled_down_carrier<S, T, W>() -> bool
337+
where
338+
S: NativePType,
339+
T: NativeDecimalType,
340+
W: NativeDecimalType,
341+
{
342+
let carrier = primitive_decimal_carrier_type(S::PTYPE) as u8;
343+
let target = T::DECIMAL_TYPE as u8;
344+
let selected = if carrier > target { carrier } else { target };
345+
346+
W::DECIMAL_TYPE as u8 == selected
347+
}
348+
326349
fn cast_integer_values_to_decimal_buffer<S, T>(
327350
values: &[S],
328351
decimal_dtype: DecimalDType,
@@ -352,15 +375,19 @@ fn signed_primitive_decimal_type(ptype: PType) -> Option<DecimalType> {
352375
}
353376

354377
/// The smallest signed decimal physical type that represents every `ptype` value.
355-
fn primitive_decimal_carrier_type(ptype: PType) -> DecimalType {
378+
#[expect(
379+
clippy::panic,
380+
reason = "`unreachable!` with a message is not available in a const fn"
381+
)]
382+
const fn primitive_decimal_carrier_type(ptype: PType) -> DecimalType {
356383
match ptype {
357384
PType::I8 => DecimalType::I8,
358385
PType::U8 | PType::I16 => DecimalType::I16,
359386
PType::U16 | PType::I32 => DecimalType::I32,
360387
PType::U32 | PType::I64 => DecimalType::I64,
361388
PType::U64 => DecimalType::I128,
362389
PType::F16 | PType::F32 | PType::F64 => {
363-
unreachable!("floating primitives are rejected before selecting a decimal carrier")
390+
panic!("floating primitives are rejected before selecting a decimal carrier")
364391
}
365392
}
366393
}
@@ -687,6 +714,7 @@ mod test {
687714
use crate::dtype::PType;
688715
use crate::dtype::i256;
689716
use crate::expr::stats::Stat;
717+
use crate::match_each_decimal_value_type;
690718
use crate::validity::Validity;
691719

692720
static SESSION: LazyLock<VortexSession> = LazyLock::new(VortexSession::empty);
@@ -1013,6 +1041,39 @@ mod test {
10131041
Ok(())
10141042
}
10151043

1044+
#[rstest]
1045+
fn cast_integer_to_negative_scale_decimal_every_width(
1046+
#[values(
1047+
PType::U8, PType::U16, PType::U32, PType::U64, PType::I8, PType::I16, PType::I32,
1048+
PType::I64
1049+
)]
1050+
ptype: PType,
1051+
#[values(2, 4, 9, 18, 38, 76)] precision: u8,
1052+
) -> VortexResult<()> {
1053+
let mut ctx = SESSION.create_execution_ctx();
1054+
let decimal_dtype = DecimalDType::new(precision, -1);
1055+
let casted = PrimitiveArray::from_iter([100u8, 30])
1056+
.into_array()
1057+
.cast(DType::Primitive(ptype, Nullability::NonNullable))?
1058+
.cast(DType::Decimal(decimal_dtype, Nullability::NonNullable))?
1059+
.execute::<DecimalArray>(&mut ctx)?;
1060+
1061+
assert_eq!(
1062+
casted.values_type(),
1063+
DecimalType::smallest_decimal_value_type(&decimal_dtype)
1064+
);
1065+
match_each_decimal_value_type!(casted.values_type(), |T| {
1066+
let expected = [10i8, 3].map(<T as crate::dtype::BigCast>::from);
1067+
let actual = casted
1068+
.buffer::<T>()
1069+
.iter()
1070+
.map(|&value| Some(value))
1071+
.collect::<Vec<_>>();
1072+
assert_eq!(actual, expected);
1073+
});
1074+
Ok(())
1075+
}
1076+
10161077
#[test]
10171078
fn cast_zero_to_large_negative_scale_decimal() -> VortexResult<()> {
10181079
let mut ctx = SESSION.create_execution_ctx();

0 commit comments

Comments
 (0)