Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 86 additions & 2 deletions compiler/noirc_evaluator/src/ssa/ir/dfg/simplify/cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ pub(super) fn simplify_cast(
}

if let Value::Instruction { instruction, .. } = &dfg[value]
&& let Instruction::Cast(original_value, _) = &dfg[*instruction]
&& let Instruction::Cast(original_value, intermediate_typ) = &dfg[*instruction]
{
let original_value = *original_value;
let original_typ = dfg.type_of_value(original_value).unwrap_numeric();
// Constant folding can later observe truncation or extension at the intermediate type.
// Collapse the chain only when folding the two casts is equivalent to folding one.
if !cast_chain_is_equivalent(original_typ, *intermediate_typ, dst_typ) {
return None;
}
return match simplify_cast(original_value, dst_typ, dfg) {
None => SimplifiedToInstruction(Instruction::Cast(original_value, dst_typ)),
simpler => simpler,
Expand Down Expand Up @@ -119,9 +125,53 @@ pub(super) fn simplify_cast(
}
}

fn cast_chain_is_equivalent(
original_typ: NumericType,
intermediate_typ: NumericType,
dst_typ: NumericType,
) -> bool {
let integer = |typ| match typ {
NumericType::Signed { bit_size } => (bit_size, true),
NumericType::Unsigned { bit_size } => (bit_size, false),
NumericType::NativeField => (FieldElement::max_num_bits(), false),
};
let (original_bit_size, original_is_signed) = integer(original_typ);
let (intermediate_bit_size, intermediate_is_signed) = integer(intermediate_typ);
let (dst_bit_size, dst_is_signed) = integer(dst_typ);

if dst_bit_size <= intermediate_bit_size {
if dst_bit_size <= original_bit_size {
// Both paths keep the same low destination bits.
return true;
}

// The intermediate and direct casts both widen the original value. Their extension bits
// agree unless a signed original crosses exactly one signed destination boundary.
return !original_is_signed || intermediate_is_signed == dst_is_signed;
}

if intermediate_bit_size < original_bit_size {
// The intermediate cast discards bits which the wider destination would otherwise retain.
return false;
}

if intermediate_bit_size == original_bit_size {
// Retyping equal-width bits only matters when the destination sign-extends them.
return !dst_is_signed || intermediate_is_signed == original_is_signed;
}

// Both casts widen. An unsigned original always zero-extends. For a signed original, the
// intermediate and destination signedness must agree so that extension happens at both
// boundaries or neither one.
!original_is_signed || intermediate_is_signed == dst_is_signed
}

#[cfg(test)]
mod tests {
use crate::{assert_ssa_snapshot, ssa::ssa_gen::Ssa};
use crate::{
assert_ssa_snapshot,
ssa::{interpreter::value::Value, opt::CONSTANT_FOLDING_MAX_ITER, ssa_gen::Ssa},
};

#[test]
fn unsigned_u8_to_i8_safe() {
Expand Down Expand Up @@ -298,6 +348,40 @@ mod tests {
");
}

#[test]
fn preserves_observable_cast_boundaries() {
let src = "
acir(inline) fn main f0 {
b0(v0: i8, v1: u8, v2: i16):
constrain v0 == i8 -1
constrain v1 == u8 255
constrain v2 == i16 256
v3 = cast v0 as u8
v4 = cast v3 as i16
v5 = cast v1 as i8
v6 = cast v5 as i16
v7 = truncate v2 to 8 bits, max_bit_size: 16
v8 = cast v7 as u8
v9 = cast v8 as u16
v10 = truncate v2 to 8 bits, max_bit_size: 16
v11 = cast v10 as i8
v12 = cast v11 as i16
v13 = cast v0 as u16
v14 = cast v13 as i16
return v4, v6, v9, v12, v14
}
";

let ssa = Ssa::from_str_simplifying(src).unwrap();
let ssa = ssa.fold_constants_using_constraints(CONSTANT_FOLDING_MAX_ITER);
let result = ssa.interpret(vec![Value::i8(-1), Value::u8(255), Value::i16(256)]).unwrap();

assert_eq!(
result,
vec![Value::i16(255), Value::i16(-1), Value::u16(0), Value::i16(0), Value::i16(255),]
);
}

#[test]
fn simplifies_cast_from_field_4_to_i8() {
let src = "
Expand Down
27 changes: 14 additions & 13 deletions compiler/noirc_evaluator/src/ssa/opt/expand_signed_math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -515,19 +515,20 @@ mod tests {
v27 = cast v26 as u8
v28 = mod v20, v27
v29 = cast v10 as u1
v30 = cast v28 as Field
v31 = sub Field 128, v30
v32 = cast v10 as Field
v33 = mul v31, v32
v34 = mul v33, Field 2
v35 = add v30, v34
v37 = eq v30, Field 0
v38 = not v37
v39 = cast v38 as Field
v40 = mul v35, v39
v41 = cast v40 as u8
v42 = cast v40 as i8
return v42
v30 = cast v29 as u8
v31 = cast v28 as Field
v32 = sub Field 128, v31
v33 = cast v29 as Field
v34 = mul v32, v33
v35 = mul v34, Field 2
v36 = add v31, v35
v38 = eq v31, Field 0
v39 = not v38
v40 = cast v39 as Field
v41 = mul v36, v40
v42 = cast v41 as u8
v43 = cast v41 as i8
return v43
}
"#);
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/noirc_evaluator/src/ssa/opt/flatten_cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2327,7 +2327,7 @@ mod tests {
v12 = cast v11 as u8
v13 = load v6 -> u8
v14 = not v5
v15 = cast v4 as u8
v15 = cast v5 as u8
v16 = cast v14 as u8
v17 = unchecked_mul v15, v12
v18 = unchecked_mul v16, v13
Expand All @@ -2336,7 +2336,7 @@ mod tests {
enable_side_effects v14
v20 = load v6 -> u8
v21 = cast v14 as u8
v22 = cast v4 as u8
v22 = cast v5 as u8
v23 = unchecked_mul v22, v20
store v23 at v6
enable_side_effects u1 1
Expand Down