use rucc_ir::{Builder, Def, Flags, Func, Inst, IntPred, Opcode, Type, Value};
use crate::loops::LoopId;
use crate::scev::{Assumption, Count, Plain, Reading, Scev};
pub(crate) const NOT_COUNTED: &str =
"loop left alone, how many times it runs is not settled before it starts";
pub(crate) const COUNT_ON_TWO_VALUES: &str = "loop left alone, how many times it runs is a distance between two values and only one of \
them is built here";
pub(crate) const COUNT_IS_WIDENED: &str = "loop left alone, how many times it runs is built on a value that has already been widened \
once";
pub(crate) const RESTS_ON_NO_WRAP: &str =
"loop left alone, how many times it runs is known only if its counter does not wrap";
#[derive(Clone, Copy, Debug)]
pub(crate) enum Around {
Number(i128),
Computed(Plain, Reading),
}
pub(crate) fn counted(scev: &mut Scev<'_>, id: LoopId) -> Result<Around, &'static str> {
let bound = scev.bound(id).ok_or(NOT_COUNTED)?;
if let Some(Count::Exact(exact)) = bound.under_undefined_overflow() {
return i128::try_from(exact).map(Around::Number).map_err(|_| NOT_COUNTED);
}
let reading = bound.reading();
let (Count::Symbolic(count), assumptions) = bound.parts() else {
return Err(NOT_COUNTED);
};
for rests_on in assumptions {
match rests_on {
Assumption::StrictOverflow | Assumption::Approaching => {}
Assumption::NoWrap(_) => return Err(RESTS_ON_NO_WRAP),
}
}
let count = count.plain().ok_or(COUNT_ON_TWO_VALUES)?;
if count.read.is_some() {
return Err(COUNT_IS_WIDENED);
}
Ok(Around::Computed(count, reading))
}
pub(crate) fn covered(
build: &mut Builder<'_>,
made: &mut Vec<Value>,
count: Plain,
step: i128,
reach: i128,
reading: Reading,
flags: Flags,
) -> Value {
let word = Type::int(64);
let value = count.value.expect("a count that is an expression is built on a value");
let mut wide = value;
if build.func()[value].ty.bits() < 64 {
let widen = match reading {
Reading::Signed => Opcode::SExt,
Reading::Unsigned => Opcode::ZExt,
};
wide = build.unary(widen, value, word);
made.push(wide);
}
if count.scale != 1 {
let scale = build.iconst(word, count.scale);
made.push(scale);
wide = build.binary(Opcode::Mul, wide, scale, flags);
made.push(wide);
}
if count.offset != 0 {
let offset = build.iconst(word, count.offset);
made.push(offset);
wide = build.binary(Opcode::Add, wide, offset, flags);
made.push(wide);
}
let zero = build.iconst(word, 0);
made.push(zero);
let entered = build.icmp(IntPred::Sgt, wide, zero);
made.push(entered);
let mut span = build.select(entered, wide, zero);
made.push(span);
if step != 1 {
let by = build.iconst(word, step);
made.push(by);
span = build.binary(Opcode::Mul, span, by, flags);
made.push(span);
}
if reach != 0 {
let last = build.iconst(word, reach);
made.push(last);
span = build.binary(Opcode::Add, span, last, flags);
made.push(span);
}
span
}
pub(crate) fn inst_of(func: &Func, value: Value) -> Inst {
let Def::Result { inst, .. } = func[value].def else {
unreachable!("the builder was just asked for an instruction that produces this")
};
inst
}