@@ -9,14 +9,13 @@ |
| 9 | 9 | //! and hi <= upper |
| 10 | 10 | //! - Constant index within [lower, upper] |
| 11 | 11 | //! |
| 12 | | -//! Note: Bounds check INSERTION (the lowerer adding CheckBounds calls |
| 13 | | -//! at array access sites) is deferred. This pass provides the |
| 14 | | -//! elimination framework for when insertion lands. |
| 12 | +//! Bounds checks are inserted during lowering for scalar array |
| 13 | +//! element accesses. This pass removes the checks when the index is |
| 14 | +//! provably safe. |
| 15 | 15 | |
| 16 | | -use std::collections::HashSet; |
| 17 | 16 | use crate::ir::inst::*; |
| 18 | | -use crate::ir::walk::find_natural_loops; |
| 19 | | -use super::loop_utils::{resolve_const_int, loop_defined_values}; |
| 17 | +use crate::ir::walk::{find_natural_loops, predecessors}; |
| 18 | +use super::loop_utils::{find_preheader, resolve_const_int}; |
| 20 | 19 | use super::pass::Pass; |
| 21 | 20 | |
| 22 | 21 | pub struct Bce; |
@@ -35,6 +34,7 @@ impl Pass for Bce { |
| 35 | 34 | |
| 36 | 35 | fn bce_function(func: &mut Function) -> bool { |
| 37 | 36 | let loops = find_natural_loops(func); |
| 37 | + let preds = predecessors(func); |
| 38 | 38 | let mut to_remove: Vec<(BlockId, usize)> = Vec::new(); |
| 39 | 39 | |
| 40 | 40 | for block in &func.blocks { |
@@ -45,7 +45,7 @@ fn bce_function(func: &mut Function) -> bool { |
| 45 | 45 | let lower = args[1]; |
| 46 | 46 | let upper = args[2]; |
| 47 | 47 | |
| 48 | | - if is_provably_safe(func, &loops, index, lower, upper) { |
| 48 | + if is_provably_safe(func, &loops, &preds, index, lower, upper) { |
| 49 | 49 | to_remove.push((block.id, inst_idx)); |
| 50 | 50 | } |
| 51 | 51 | } |
@@ -68,60 +68,214 @@ fn bce_function(func: &mut Function) -> bool { |
| 68 | 68 | fn is_provably_safe( |
| 69 | 69 | func: &Function, |
| 70 | 70 | loops: &[crate::ir::walk::NaturalLoop], |
| 71 | + preds: &std::collections::HashMap<BlockId, Vec<BlockId>>, |
| 71 | 72 | index: ValueId, |
| 72 | 73 | lower: ValueId, |
| 73 | 74 | upper: ValueId, |
| 74 | 75 | ) -> bool { |
| 76 | + let index = strip_int_casts(func, index); |
| 77 | + |
| 75 | 78 | // Case 1: constant index, constant bounds. |
| 76 | 79 | if let (Some(idx), Some(lo), Some(hi)) = ( |
| 77 | | - resolve_const_int(func, index), |
| 78 | | - resolve_const_int(func, lower), |
| 79 | | - resolve_const_int(func, upper), |
| 80 | + resolve_int_scalar(func, index), |
| 81 | + resolve_int_scalar(func, lower), |
| 82 | + resolve_int_scalar(func, upper), |
| 80 | 83 | ) { |
| 81 | 84 | return idx >= lo && idx <= hi; |
| 82 | 85 | } |
| 83 | 86 | |
| 84 | | - // Case 2: index is a loop IV, bounds are constants matching loop bounds. |
| 85 | | - // Check if the index is a block param of a loop header, and the loop's |
| 86 | | - // init/bound encompass [lower, upper]. |
| 87 | + // Case 2: index is a canonical loop IV, and the loop's closed range |
| 88 | + // stays within the checked bounds. |
| 89 | + let Some(lo_const) = resolve_int_scalar(func, lower) else { return false; }; |
| 90 | + let Some(hi_const) = resolve_int_scalar(func, upper) else { return false; }; |
| 87 | 91 | for lp in loops { |
| 88 | | - let hdr = func.block(lp.header); |
| 89 | | - if hdr.params.len() != 1 { continue; } |
| 90 | | - let iv = hdr.params[0].id; |
| 91 | | - if iv != index { continue; } |
| 92 | | - |
| 93 | | - // The IV is in-bounds if the loop's init >= lower and bound <= upper. |
| 94 | | - // Find init (from preheader's branch arg) and bound (from cmp block). |
| 95 | | - // For now, conservative: only eliminate if both lower and upper are |
| 96 | | - // constants and the loop is a standard counted loop. |
| 97 | | - let loop_defs = loop_defined_values(func, lp); |
| 98 | | - |
| 99 | | - // Find bound from comparison. |
| 100 | | - for &bid in &lp.body { |
| 101 | | - let block = func.block(bid); |
| 102 | | - for inst in &block.insts { |
| 103 | | - if let InstKind::ICmp(CmpOp::Le, a, b) = &inst.kind { |
| 104 | | - if *a == iv { |
| 105 | | - // IV <= b → loop upper = b |
| 106 | | - if let (Some(lo_const), Some(hi_const), Some(bound_const)) = ( |
| 107 | | - resolve_const_int(func, lower), |
| 108 | | - resolve_const_int(func, upper), |
| 109 | | - resolve_const_int(func, *b), |
| 110 | | - ) { |
| 111 | | - // If loop runs from some init to bound_const, |
| 112 | | - // and lo_const <= init and bound_const <= hi_const, |
| 113 | | - // the access is safe. |
| 114 | | - if bound_const <= hi_const && lo_const <= 1 { |
| 115 | | - return true; |
| 116 | | - } |
| 117 | | - } |
| 118 | | - } |
| 92 | + let Some((range_lo, range_hi)) = loop_index_range(func, lp, preds, index) else { |
| 93 | + continue; |
| 94 | + }; |
| 95 | + if range_lo >= lo_const && range_hi <= hi_const { |
| 96 | + return true; |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + false |
| 101 | +} |
| 102 | + |
| 103 | +fn loop_index_range( |
| 104 | + func: &Function, |
| 105 | + lp: &crate::ir::walk::NaturalLoop, |
| 106 | + preds: &std::collections::HashMap<BlockId, Vec<BlockId>>, |
| 107 | + index: ValueId, |
| 108 | +) -> Option<(i64, i64)> { |
| 109 | + let header = func.block(lp.header); |
| 110 | + let param_idx = header.params.iter().position(|param| param.id == index)?; |
| 111 | + let init = loop_init_const(func, lp, preds, param_idx)?; |
| 112 | + let (bound, dir) = loop_bound_const(func, lp.header, index)?; |
| 113 | + let step = loop_step_const(func, lp, param_idx, index)?; |
| 114 | + |
| 115 | + match (dir, step.signum()) { |
| 116 | + (LoopDir::Ascending, 1) | (LoopDir::Descending, -1) => { |
| 117 | + Some((init.min(bound), init.max(bound))) |
| 118 | + } |
| 119 | + _ => None, |
| 120 | + } |
| 121 | +} |
| 122 | + |
| 123 | +fn loop_init_const( |
| 124 | + func: &Function, |
| 125 | + lp: &crate::ir::walk::NaturalLoop, |
| 126 | + preds: &std::collections::HashMap<BlockId, Vec<BlockId>>, |
| 127 | + param_idx: usize, |
| 128 | +) -> Option<i64> { |
| 129 | + let preheader = find_preheader(func, lp, preds)?; |
| 130 | + match &func.block(preheader).terminator { |
| 131 | + Some(Terminator::Branch(dest, args)) |
| 132 | + if *dest == lp.header && param_idx < args.len() => |
| 133 | + { |
| 134 | + resolve_int_scalar(func, args[param_idx]) |
| 135 | + } |
| 136 | + _ => None, |
| 137 | + } |
| 138 | +} |
| 139 | + |
| 140 | +#[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 141 | +enum LoopDir { |
| 142 | + Ascending, |
| 143 | + Descending, |
| 144 | +} |
| 145 | + |
| 146 | +fn loop_bound_const( |
| 147 | + func: &Function, |
| 148 | + header: BlockId, |
| 149 | + index: ValueId, |
| 150 | +) -> Option<(i64, LoopDir)> { |
| 151 | + for inst in &func.block(header).insts { |
| 152 | + let InstKind::ICmp(op, lhs, rhs) = &inst.kind else { continue }; |
| 153 | + match op { |
| 154 | + CmpOp::Le => { |
| 155 | + if *lhs == index { |
| 156 | + return resolve_int_scalar(func, *rhs) |
| 157 | + .map(|bound| (bound, LoopDir::Ascending)); |
| 158 | + } |
| 159 | + if *rhs == index { |
| 160 | + return resolve_int_scalar(func, *lhs) |
| 161 | + .map(|bound| (bound, LoopDir::Descending)); |
| 119 | 162 | } |
| 120 | 163 | } |
| 164 | + CmpOp::Ge => { |
| 165 | + if *lhs == index { |
| 166 | + return resolve_int_scalar(func, *rhs) |
| 167 | + .map(|bound| (bound, LoopDir::Descending)); |
| 168 | + } |
| 169 | + if *rhs == index { |
| 170 | + return resolve_int_scalar(func, *lhs) |
| 171 | + .map(|bound| (bound, LoopDir::Ascending)); |
| 172 | + } |
| 173 | + } |
| 174 | + _ => {} |
| 121 | 175 | } |
| 122 | 176 | } |
| 177 | + None |
| 178 | +} |
| 123 | 179 | |
| 124 | | - false |
| 180 | +fn loop_step_const( |
| 181 | + func: &Function, |
| 182 | + lp: &crate::ir::walk::NaturalLoop, |
| 183 | + param_idx: usize, |
| 184 | + index: ValueId, |
| 185 | +) -> Option<i64> { |
| 186 | + let mut step: Option<i64> = None; |
| 187 | + for &latch in &lp.latches { |
| 188 | + let next = |
| 189 | + edge_arg_value(func.block(latch).terminator.as_ref()?, lp.header, param_idx)?; |
| 190 | + let latch_step = update_step_const(func, index, next)?; |
| 191 | + if latch_step == 0 { |
| 192 | + return None; |
| 193 | + } |
| 194 | + match step { |
| 195 | + Some(prev) if prev != latch_step => return None, |
| 196 | + Some(_) => {} |
| 197 | + None => step = Some(latch_step), |
| 198 | + } |
| 199 | + } |
| 200 | + step |
| 201 | +} |
| 202 | + |
| 203 | +fn edge_arg_value(term: &Terminator, target: BlockId, param_idx: usize) -> Option<ValueId> { |
| 204 | + match term { |
| 205 | + Terminator::Branch(dest, args) if *dest == target => args.get(param_idx).copied(), |
| 206 | + Terminator::CondBranch { |
| 207 | + true_dest, |
| 208 | + true_args, |
| 209 | + false_dest, |
| 210 | + false_args, |
| 211 | + .. |
| 212 | + } => { |
| 213 | + if *true_dest == target { |
| 214 | + true_args.get(param_idx).copied() |
| 215 | + } else if *false_dest == target { |
| 216 | + false_args.get(param_idx).copied() |
| 217 | + } else { |
| 218 | + None |
| 219 | + } |
| 220 | + } |
| 221 | + _ => None, |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | +fn update_step_const(func: &Function, index: ValueId, next: ValueId) -> Option<i64> { |
| 226 | + let next = strip_int_casts(func, next); |
| 227 | + if next == index { |
| 228 | + return Some(0); |
| 229 | + } |
| 230 | + |
| 231 | + let kind = find_inst_kind(func, next)?; |
| 232 | + match kind { |
| 233 | + InstKind::IAdd(lhs, rhs) => { |
| 234 | + if *lhs == index { |
| 235 | + resolve_int_scalar(func, *rhs) |
| 236 | + } else if *rhs == index { |
| 237 | + resolve_int_scalar(func, *lhs) |
| 238 | + } else { |
| 239 | + None |
| 240 | + } |
| 241 | + } |
| 242 | + InstKind::ISub(lhs, rhs) if *lhs == index => { |
| 243 | + resolve_int_scalar(func, *rhs).map(|step| -step) |
| 244 | + } |
| 245 | + _ => None, |
| 246 | + } |
| 247 | +} |
| 248 | + |
| 249 | +fn resolve_int_scalar(func: &Function, value: ValueId) -> Option<i64> { |
| 250 | + let kind = find_inst_kind(func, value)?; |
| 251 | + match kind { |
| 252 | + InstKind::ConstInt(v, _) => Some(*v), |
| 253 | + InstKind::IntExtend(src, _, _) | InstKind::IntTrunc(src, _) => { |
| 254 | + resolve_int_scalar(func, *src) |
| 255 | + } |
| 256 | + _ => resolve_const_int(func, value), |
| 257 | + } |
| 258 | +} |
| 259 | + |
| 260 | +fn strip_int_casts(func: &Function, mut value: ValueId) -> ValueId { |
| 261 | + loop { |
| 262 | + let Some(kind) = find_inst_kind(func, value) else { return value }; |
| 263 | + match kind { |
| 264 | + InstKind::IntExtend(src, _, _) | InstKind::IntTrunc(src, _) => value = *src, |
| 265 | + _ => return value, |
| 266 | + } |
| 267 | + } |
| 268 | +} |
| 269 | + |
| 270 | +fn find_inst_kind<'a>(func: &'a Function, value: ValueId) -> Option<&'a InstKind> { |
| 271 | + for block in &func.blocks { |
| 272 | + for inst in &block.insts { |
| 273 | + if inst.id == value { |
| 274 | + return Some(&inst.kind); |
| 275 | + } |
| 276 | + } |
| 277 | + } |
| 278 | + None |
| 125 | 279 | } |
| 126 | 280 | |
| 127 | 281 | #[cfg(test)] |
@@ -187,4 +341,267 @@ mod tests { |
| 187 | 341 | .any(|i| matches!(i.kind, InstKind::RuntimeCall(RuntimeFunc::CheckBounds, _))); |
| 188 | 342 | assert!(!has_check, "CheckBounds should be removed"); |
| 189 | 343 | } |
| 344 | + |
| 345 | + #[test] |
| 346 | + fn bce_removes_canonical_loop_iv_check() { |
| 347 | + let mut m = Module::new("test".into()); |
| 348 | + let mut f = Function::new("test".into(), vec![], IrType::Void); |
| 349 | + let span = crate::lexer::Span { |
| 350 | + file_id: 0, |
| 351 | + start: crate::lexer::Position { line: 0, col: 0 }, |
| 352 | + end: crate::lexer::Position { line: 0, col: 0 }, |
| 353 | + }; |
| 354 | + |
| 355 | + let header = f.create_block("header"); |
| 356 | + let body = f.create_block("body"); |
| 357 | + let exit = f.create_block("exit"); |
| 358 | + |
| 359 | + let init = f.next_value_id(); |
| 360 | + f.register_type(init, IrType::Int(IntWidth::I32)); |
| 361 | + f.block_mut(f.entry).insts.push(Inst { |
| 362 | + id: init, |
| 363 | + ty: IrType::Int(IntWidth::I32), |
| 364 | + span, |
| 365 | + kind: InstKind::ConstInt(1, IntWidth::I32), |
| 366 | + }); |
| 367 | + let zero = f.next_value_id(); |
| 368 | + f.register_type(zero, IrType::Int(IntWidth::I32)); |
| 369 | + f.block_mut(f.entry).insts.push(Inst { |
| 370 | + id: zero, |
| 371 | + ty: IrType::Int(IntWidth::I32), |
| 372 | + span, |
| 373 | + kind: InstKind::ConstInt(0, IntWidth::I32), |
| 374 | + }); |
| 375 | + f.block_mut(f.entry).terminator = Some(Terminator::Branch(header, vec![init, zero])); |
| 376 | + |
| 377 | + let iv = f.next_value_id(); |
| 378 | + f.register_type(iv, IrType::Int(IntWidth::I32)); |
| 379 | + let sum = f.next_value_id(); |
| 380 | + f.register_type(sum, IrType::Int(IntWidth::I32)); |
| 381 | + f.block_mut(header).params.push(BlockParam { id: iv, ty: IrType::Int(IntWidth::I32) }); |
| 382 | + f.block_mut(header).params.push(BlockParam { id: sum, ty: IrType::Int(IntWidth::I32) }); |
| 383 | + |
| 384 | + let bound = f.next_value_id(); |
| 385 | + f.register_type(bound, IrType::Int(IntWidth::I32)); |
| 386 | + f.block_mut(header).insts.push(Inst { |
| 387 | + id: bound, |
| 388 | + ty: IrType::Int(IntWidth::I32), |
| 389 | + span, |
| 390 | + kind: InstKind::ConstInt(4, IntWidth::I32), |
| 391 | + }); |
| 392 | + let cond = f.next_value_id(); |
| 393 | + f.register_type(cond, IrType::Bool); |
| 394 | + f.block_mut(header).insts.push(Inst { |
| 395 | + id: cond, |
| 396 | + ty: IrType::Bool, |
| 397 | + span, |
| 398 | + kind: InstKind::ICmp(CmpOp::Le, iv, bound), |
| 399 | + }); |
| 400 | + f.block_mut(header).terminator = Some(Terminator::CondBranch { |
| 401 | + cond, |
| 402 | + true_dest: body, |
| 403 | + true_args: vec![], |
| 404 | + false_dest: exit, |
| 405 | + false_args: vec![], |
| 406 | + }); |
| 407 | + |
| 408 | + let iv64 = f.next_value_id(); |
| 409 | + f.register_type(iv64, IrType::Int(IntWidth::I64)); |
| 410 | + f.block_mut(body).insts.push(Inst { |
| 411 | + id: iv64, |
| 412 | + ty: IrType::Int(IntWidth::I64), |
| 413 | + span, |
| 414 | + kind: InstKind::IntExtend(iv, IntWidth::I64, true), |
| 415 | + }); |
| 416 | + let lo = f.next_value_id(); |
| 417 | + f.register_type(lo, IrType::Int(IntWidth::I64)); |
| 418 | + f.block_mut(body).insts.push(Inst { |
| 419 | + id: lo, |
| 420 | + ty: IrType::Int(IntWidth::I64), |
| 421 | + span, |
| 422 | + kind: InstKind::ConstInt(1, IntWidth::I64), |
| 423 | + }); |
| 424 | + let hi = f.next_value_id(); |
| 425 | + f.register_type(hi, IrType::Int(IntWidth::I64)); |
| 426 | + f.block_mut(body).insts.push(Inst { |
| 427 | + id: hi, |
| 428 | + ty: IrType::Int(IntWidth::I64), |
| 429 | + span, |
| 430 | + kind: InstKind::ConstInt(4, IntWidth::I64), |
| 431 | + }); |
| 432 | + let check = f.next_value_id(); |
| 433 | + f.register_type(check, IrType::Void); |
| 434 | + f.block_mut(body).insts.push(Inst { |
| 435 | + id: check, |
| 436 | + ty: IrType::Void, |
| 437 | + span, |
| 438 | + kind: InstKind::RuntimeCall(RuntimeFunc::CheckBounds, vec![iv64, lo, hi]), |
| 439 | + }); |
| 440 | + let step = f.next_value_id(); |
| 441 | + f.register_type(step, IrType::Int(IntWidth::I32)); |
| 442 | + f.block_mut(body).insts.push(Inst { |
| 443 | + id: step, |
| 444 | + ty: IrType::Int(IntWidth::I32), |
| 445 | + span, |
| 446 | + kind: InstKind::ConstInt(1, IntWidth::I32), |
| 447 | + }); |
| 448 | + let next_iv = f.next_value_id(); |
| 449 | + f.register_type(next_iv, IrType::Int(IntWidth::I32)); |
| 450 | + f.block_mut(body).insts.push(Inst { |
| 451 | + id: next_iv, |
| 452 | + ty: IrType::Int(IntWidth::I32), |
| 453 | + span, |
| 454 | + kind: InstKind::IAdd(iv, step), |
| 455 | + }); |
| 456 | + let next_sum = f.next_value_id(); |
| 457 | + f.register_type(next_sum, IrType::Int(IntWidth::I32)); |
| 458 | + f.block_mut(body).insts.push(Inst { |
| 459 | + id: next_sum, |
| 460 | + ty: IrType::Int(IntWidth::I32), |
| 461 | + span, |
| 462 | + kind: InstKind::IAdd(sum, step), |
| 463 | + }); |
| 464 | + f.block_mut(body).terminator = |
| 465 | + Some(Terminator::Branch(header, vec![next_iv, next_sum])); |
| 466 | + f.block_mut(exit).terminator = Some(Terminator::Return(None)); |
| 467 | + m.add_function(f); |
| 468 | + |
| 469 | + let pass = Bce; |
| 470 | + assert!(pass.run(&mut m), "canonical counted-loop bounds check should be removed"); |
| 471 | + let has_check = m.functions[0].block(body).insts.iter().any(|inst| { |
| 472 | + matches!(inst.kind, InstKind::RuntimeCall(RuntimeFunc::CheckBounds, _)) |
| 473 | + }); |
| 474 | + assert!(!has_check, "loop body should no longer contain CheckBounds"); |
| 475 | + } |
| 476 | + |
| 477 | + #[test] |
| 478 | + fn bce_keeps_loop_check_when_bounds_are_tighter_than_trip_range() { |
| 479 | + let mut m = Module::new("test".into()); |
| 480 | + let mut f = Function::new("test".into(), vec![], IrType::Void); |
| 481 | + let span = crate::lexer::Span { |
| 482 | + file_id: 0, |
| 483 | + start: crate::lexer::Position { line: 0, col: 0 }, |
| 484 | + end: crate::lexer::Position { line: 0, col: 0 }, |
| 485 | + }; |
| 486 | + |
| 487 | + let header = f.create_block("header"); |
| 488 | + let body = f.create_block("body"); |
| 489 | + let exit = f.create_block("exit"); |
| 490 | + |
| 491 | + let init = f.next_value_id(); |
| 492 | + f.register_type(init, IrType::Int(IntWidth::I32)); |
| 493 | + f.block_mut(f.entry).insts.push(Inst { |
| 494 | + id: init, |
| 495 | + ty: IrType::Int(IntWidth::I32), |
| 496 | + span, |
| 497 | + kind: InstKind::ConstInt(1, IntWidth::I32), |
| 498 | + }); |
| 499 | + let zero = f.next_value_id(); |
| 500 | + f.register_type(zero, IrType::Int(IntWidth::I32)); |
| 501 | + f.block_mut(f.entry).insts.push(Inst { |
| 502 | + id: zero, |
| 503 | + ty: IrType::Int(IntWidth::I32), |
| 504 | + span, |
| 505 | + kind: InstKind::ConstInt(0, IntWidth::I32), |
| 506 | + }); |
| 507 | + f.block_mut(f.entry).terminator = Some(Terminator::Branch(header, vec![init, zero])); |
| 508 | + |
| 509 | + let iv = f.next_value_id(); |
| 510 | + f.register_type(iv, IrType::Int(IntWidth::I32)); |
| 511 | + let sum = f.next_value_id(); |
| 512 | + f.register_type(sum, IrType::Int(IntWidth::I32)); |
| 513 | + f.block_mut(header).params.push(BlockParam { id: iv, ty: IrType::Int(IntWidth::I32) }); |
| 514 | + f.block_mut(header).params.push(BlockParam { id: sum, ty: IrType::Int(IntWidth::I32) }); |
| 515 | + |
| 516 | + let bound = f.next_value_id(); |
| 517 | + f.register_type(bound, IrType::Int(IntWidth::I32)); |
| 518 | + f.block_mut(header).insts.push(Inst { |
| 519 | + id: bound, |
| 520 | + ty: IrType::Int(IntWidth::I32), |
| 521 | + span, |
| 522 | + kind: InstKind::ConstInt(4, IntWidth::I32), |
| 523 | + }); |
| 524 | + let cond = f.next_value_id(); |
| 525 | + f.register_type(cond, IrType::Bool); |
| 526 | + f.block_mut(header).insts.push(Inst { |
| 527 | + id: cond, |
| 528 | + ty: IrType::Bool, |
| 529 | + span, |
| 530 | + kind: InstKind::ICmp(CmpOp::Le, iv, bound), |
| 531 | + }); |
| 532 | + f.block_mut(header).terminator = Some(Terminator::CondBranch { |
| 533 | + cond, |
| 534 | + true_dest: body, |
| 535 | + true_args: vec![], |
| 536 | + false_dest: exit, |
| 537 | + false_args: vec![], |
| 538 | + }); |
| 539 | + |
| 540 | + let iv64 = f.next_value_id(); |
| 541 | + f.register_type(iv64, IrType::Int(IntWidth::I64)); |
| 542 | + f.block_mut(body).insts.push(Inst { |
| 543 | + id: iv64, |
| 544 | + ty: IrType::Int(IntWidth::I64), |
| 545 | + span, |
| 546 | + kind: InstKind::IntExtend(iv, IntWidth::I64, true), |
| 547 | + }); |
| 548 | + let lo = f.next_value_id(); |
| 549 | + f.register_type(lo, IrType::Int(IntWidth::I64)); |
| 550 | + f.block_mut(body).insts.push(Inst { |
| 551 | + id: lo, |
| 552 | + ty: IrType::Int(IntWidth::I64), |
| 553 | + span, |
| 554 | + kind: InstKind::ConstInt(1, IntWidth::I64), |
| 555 | + }); |
| 556 | + let hi = f.next_value_id(); |
| 557 | + f.register_type(hi, IrType::Int(IntWidth::I64)); |
| 558 | + f.block_mut(body).insts.push(Inst { |
| 559 | + id: hi, |
| 560 | + ty: IrType::Int(IntWidth::I64), |
| 561 | + span, |
| 562 | + kind: InstKind::ConstInt(3, IntWidth::I64), |
| 563 | + }); |
| 564 | + let check = f.next_value_id(); |
| 565 | + f.register_type(check, IrType::Void); |
| 566 | + f.block_mut(body).insts.push(Inst { |
| 567 | + id: check, |
| 568 | + ty: IrType::Void, |
| 569 | + span, |
| 570 | + kind: InstKind::RuntimeCall(RuntimeFunc::CheckBounds, vec![iv64, lo, hi]), |
| 571 | + }); |
| 572 | + let step = f.next_value_id(); |
| 573 | + f.register_type(step, IrType::Int(IntWidth::I32)); |
| 574 | + f.block_mut(body).insts.push(Inst { |
| 575 | + id: step, |
| 576 | + ty: IrType::Int(IntWidth::I32), |
| 577 | + span, |
| 578 | + kind: InstKind::ConstInt(1, IntWidth::I32), |
| 579 | + }); |
| 580 | + let next_iv = f.next_value_id(); |
| 581 | + f.register_type(next_iv, IrType::Int(IntWidth::I32)); |
| 582 | + f.block_mut(body).insts.push(Inst { |
| 583 | + id: next_iv, |
| 584 | + ty: IrType::Int(IntWidth::I32), |
| 585 | + span, |
| 586 | + kind: InstKind::IAdd(iv, step), |
| 587 | + }); |
| 588 | + let next_sum = f.next_value_id(); |
| 589 | + f.register_type(next_sum, IrType::Int(IntWidth::I32)); |
| 590 | + f.block_mut(body).insts.push(Inst { |
| 591 | + id: next_sum, |
| 592 | + ty: IrType::Int(IntWidth::I32), |
| 593 | + span, |
| 594 | + kind: InstKind::IAdd(sum, step), |
| 595 | + }); |
| 596 | + f.block_mut(body).terminator = |
| 597 | + Some(Terminator::Branch(header, vec![next_iv, next_sum])); |
| 598 | + f.block_mut(exit).terminator = Some(Terminator::Return(None)); |
| 599 | + m.add_function(f); |
| 600 | + |
| 601 | + let pass = Bce; |
| 602 | + assert!( |
| 603 | + !pass.run(&mut m), |
| 604 | + "loop trip range 1..4 exceeds checked upper bound 3, so CheckBounds must remain" |
| 605 | + ); |
| 606 | + } |
| 190 | 607 | } |