diff --git a/nac3core/src/codegen/mod.rs b/nac3core/src/codegen/mod.rs index 72ba85e..3c213b3 100644 --- a/nac3core/src/codegen/mod.rs +++ b/nac3core/src/codegen/mod.rs @@ -77,7 +77,8 @@ pub struct CodeGenContext<'ctx, 'a> { pub const_strings: HashMap>, // stores the alloca for variables pub init_bb: BasicBlock<'ctx>, - // the first one is the test_bb, and the second one is bb after the loop + /// The header and exit basic blocks of a loop in this context. See + /// https://llvm.org/docs/LoopTerminology.html for explanation of these terminology. pub loop_target: Option<(BasicBlock<'ctx>, BasicBlock<'ctx>)>, // unwind target bb pub unwind_target: Option>, diff --git a/nac3core/src/codegen/stmt.rs b/nac3core/src/codegen/stmt.rs index cf339bd..b310097 100644 --- a/nac3core/src/codegen/stmt.rs +++ b/nac3core/src/codegen/stmt.rs @@ -13,8 +13,8 @@ use inkwell::{ attributes::{Attribute, AttributeLoc}, basic_block::BasicBlock, types::BasicTypeEnum, - values::{BasicValue, BasicValueEnum, FunctionValue, PointerValue}, - IntPredicate::EQ, + values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue}, + IntPredicate, }; use nac3parser::ast::{ Constant, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind, StrRef, @@ -107,7 +107,7 @@ pub fn gen_store_target<'ctx, 'a, G: CodeGenerator>( ); // handle negative index let is_negative = ctx.builder.build_int_compare( - inkwell::IntPredicate::SLT, + IntPredicate::SLT, raw_index, generator.get_size_type(ctx.ctx).const_zero(), "is_neg", @@ -120,7 +120,7 @@ pub fn gen_store_target<'ctx, 'a, G: CodeGenerator>( // unsigned less than is enough, because negative index after adjustment is // bigger than the length (for unsigned cmp) let bound_check = ctx.builder.build_int_compare( - inkwell::IntPredicate::ULT, + IntPredicate::ULT, index, len, "inbound", @@ -193,7 +193,13 @@ pub fn gen_assign<'ctx, 'a, G: CodeGenerator>( } } _ => { - let ptr = generator.gen_store_target(ctx, target, Some("target.addr"))?; + let name = if let ExprKind::Name { id, .. } = &target.node { + format!("{}.addr", id.to_string()) + } else { + String::from("target.addr") + }; + let ptr = generator.gen_store_target(ctx, target, Some(name.as_str()))?; + if let ExprKind::Name { id, .. } = &target.node { let (_, static_value, counter) = ctx.var_assignment.get_mut(id).unwrap(); *counter += 1; @@ -208,6 +214,26 @@ pub fn gen_assign<'ctx, 'a, G: CodeGenerator>( Ok(()) } +/// Generates a sequence of IR which checks whether [value] does not exceed the upper bound of the +/// range as defined by [stop] and [step]. +/// +/// Note that the generated IR will **not** check whether value is part of the range or whether +/// value exceeds the lower bound of the range (as evident by the missing `start` argument). +/// +/// Returns an [IntValue] representing the result of whether the [value] is in the range. +fn gen_in_range_check<'ctx, 'a>( + ctx: &CodeGenContext<'ctx, 'a>, + value: IntValue<'ctx>, + stop: IntValue<'ctx>, + step: IntValue<'ctx>, +) -> IntValue<'ctx> { + let sign = ctx.builder.build_int_compare(IntPredicate::SGT, step, ctx.ctx.i32_type().const_zero(), ""); + let lo = ctx.builder.build_select(sign, value, stop, "").into_int_value(); + let hi = ctx.builder.build_select(sign, stop, value, "").into_int_value(); + + ctx.builder.build_int_compare(IntPredicate::SLT, lo, hi, "cmp") +} + pub fn gen_for<'ctx, 'a, G: CodeGenerator>( generator: &mut G, ctx: &mut CodeGenContext<'ctx, 'a>, @@ -221,60 +247,82 @@ pub fn gen_for<'ctx, 'a, G: CodeGenerator>( let int32 = ctx.ctx.i32_type(); let size_t = generator.get_size_type(ctx.ctx); let zero = int32.const_zero(); - let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); - let test_bb = ctx.ctx.append_basic_block(current, "test"); - let body_bb = ctx.ctx.append_basic_block(current, "body"); - let cont_bb = ctx.ctx.append_basic_block(current, "cont"); + let current = ctx.builder.get_insert_block().and_then(|bb| bb.get_parent()).unwrap(); + let body_bb = ctx.ctx.append_basic_block(current, "for.body"); + let cont_bb = ctx.ctx.append_basic_block(current, "for.end"); // if there is no orelse, we just go to cont_bb let orelse_bb = - if orelse.is_empty() { cont_bb } else { ctx.ctx.append_basic_block(current, "orelse") }; + if orelse.is_empty() { cont_bb } else { ctx.ctx.append_basic_block(current, "for.orelse") }; + + // Whether the iterable is a range() expression + let is_iterable_range_expr = ctx.unifier.unioned(iter.custom.unwrap(), ctx.primitives.range); + + // The target BB of the loop backedge + let backedge_bb_target = if is_iterable_range_expr { + body_bb + } else { + ctx.ctx.append_basic_block(current, "for.cond") + }; + // store loop bb information and restore it later - let loop_bb = ctx.loop_target.replace((test_bb, cont_bb)); + let loop_bb = ctx.loop_target.replace((backedge_bb_target, cont_bb)); let iter_val = generator.gen_expr(ctx, iter)?.unwrap().to_basic_value_enum( ctx, generator, iter.custom.unwrap(), )?; - if ctx.unifier.unioned(iter.custom.unwrap(), ctx.primitives.range) { - // setup + if is_iterable_range_expr { let iter_val = iter_val.into_pointer_value(); + // Internal variable for loop; Cannot be assigned let i = generator.gen_var_alloc(ctx, int32.into(), Some("for.i.addr"))?; - let user_i = generator.gen_store_target(ctx, target, Some("for.user_i.addr"))?; - let (start, end, step) = destructure_range(ctx, iter_val); - ctx.builder.build_store(i, ctx.builder.build_int_sub(start, step, "start_init")); - ctx.builder.build_unconditional_branch(test_bb); - ctx.builder.position_at_end(test_bb); - let sign = ctx.builder.build_int_compare( - inkwell::IntPredicate::SGT, - step, - int32.const_zero(), - "sign", + // Variable declared in "target" expression of the loop; Can be reassigned *or* shadowed + let target_i = generator.gen_store_target(ctx, target, Some("for.target.addr"))?; + let (start, stop, step) = destructure_range(ctx, iter_val); + + ctx.builder.build_store(i, start); + + // Check "If step is zero, ValueError is raised." + let rangenez = ctx.builder.build_int_compare(IntPredicate::NE, step, int32.const_zero(), ""); + ctx.make_assert( + generator, + rangenez, + "ValueError", + "range() arg 3 must not be zero", + [None, None, None], + ctx.current_loc ); - // add and test - let tmp = ctx.builder.build_int_add( - ctx.builder.build_load(i, "i").into_int_value(), - step, - "start_loop", - ); - ctx.builder.build_store(i, tmp); - ctx.builder.build_store(user_i, tmp); - // // if step > 0, continue when i < end - let cmp1 = ctx.builder.build_int_compare(inkwell::IntPredicate::SLT, tmp, end, "cmp1"); - // if step < 0, continue when i > end - let cmp2 = ctx.builder.build_int_compare(inkwell::IntPredicate::SGT, tmp, end, "cmp2"); - let pos = ctx.builder.build_and(sign, cmp1, "pos"); - let neg = ctx.builder.build_and(ctx.builder.build_not(sign, "inv"), cmp2, "neg"); + ctx.builder.build_conditional_branch( - ctx.builder.build_or(pos, neg, "or"), + gen_in_range_check(ctx, start, stop, step), body_bb, orelse_bb, ); + ctx.builder.position_at_end(body_bb); + ctx.builder.build_store(target_i, ctx.builder.build_load(i, "").into_int_value()); + gen_block(generator, ctx, body.iter())?; + + // Test if next element is still in range + let next_i = ctx.builder.build_int_add( + ctx.builder.build_load(i, "").into_int_value(), + step, + "next_i", + ); + let cond_cont_bb = ctx.ctx.append_basic_block(current, "for.cond.cont"); + ctx.builder.build_conditional_branch( + gen_in_range_check(ctx, next_i, stop, step), + cond_cont_bb, + orelse_bb, + ); + + ctx.builder.position_at_end(cond_cont_bb); + ctx.builder.build_store(i, next_i); } else { - let counter = generator.gen_var_alloc(ctx, size_t.into(), Some("for.counter.addr"))?; - // counter = -1 - ctx.builder.build_store(counter, size_t.const_int(u64::max_value(), true)); + let test_bb = backedge_bb_target; + + let index_addr = generator.gen_var_alloc(ctx, size_t.into(), Some("for.index.addr"))?; + ctx.builder.build_store(index_addr, size_t.const_zero()); let len = ctx .build_gep_and_load( iter_val.into_pointer_value(), @@ -282,30 +330,36 @@ pub fn gen_for<'ctx, 'a, G: CodeGenerator>( ) .into_int_value(); ctx.builder.build_unconditional_branch(test_bb); + ctx.builder.position_at_end(test_bb); - let tmp = ctx.builder.build_load(counter, "i").into_int_value(); - let tmp = ctx.builder.build_int_add(tmp, size_t.const_int(1, false), "inc"); - ctx.builder.build_store(counter, tmp); - let cmp = ctx.builder.build_int_compare(inkwell::IntPredicate::SLT, tmp, len, "cmp"); + let index = ctx.builder.build_load(index_addr, "for.index").into_int_value(); + let cmp = ctx.builder.build_int_compare(IntPredicate::SLT, index, len, "cond"); ctx.builder.build_conditional_branch(cmp, body_bb, orelse_bb); + ctx.builder.position_at_end(body_bb); let arr_ptr = ctx .build_gep_and_load(iter_val.into_pointer_value(), &[zero, zero]) .into_pointer_value(); - let val = ctx.build_gep_and_load(arr_ptr, &[tmp]); + let val = ctx.build_gep_and_load(arr_ptr, &[index]); generator.gen_assign(ctx, target, val.into())?; + gen_block(generator, ctx, body.iter())?; + + let index = ctx.builder.build_load(index_addr, "for.index").into_int_value(); + let inc = ctx.builder.build_int_add(index, size_t.const_int(1, true), ""); + ctx.builder.build_store(index_addr, inc); } - gen_block(generator, ctx, body.iter())?; for (k, (_, _, counter)) in var_assignment.iter() { let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap(); if counter != counter2 { *static_val = None; } } + if !ctx.is_terminated() { - ctx.builder.build_unconditional_branch(test_bb); + ctx.builder.build_unconditional_branch(backedge_bb_target); } + if !orelse.is_empty() { ctx.builder.position_at_end(orelse_bb); gen_block(generator, ctx, orelse.iter())?; @@ -313,12 +367,14 @@ pub fn gen_for<'ctx, 'a, G: CodeGenerator>( ctx.builder.build_unconditional_branch(cont_bb); } } + for (k, (_, _, counter)) in var_assignment.iter() { let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap(); if counter != counter2 { *static_val = None; } } + ctx.builder.position_at_end(cont_bb); ctx.loop_target = loop_bb; } else { @@ -338,12 +394,12 @@ pub fn gen_while<'ctx, 'a, G: CodeGenerator>( let var_assignment = ctx.var_assignment.clone(); let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); - let test_bb = ctx.ctx.append_basic_block(current, "test"); - let body_bb = ctx.ctx.append_basic_block(current, "body"); - let cont_bb = ctx.ctx.append_basic_block(current, "cont"); + let test_bb = ctx.ctx.append_basic_block(current, "while.test"); + let body_bb = ctx.ctx.append_basic_block(current, "while.body"); + let cont_bb = ctx.ctx.append_basic_block(current, "while.cont"); // if there is no orelse, we just go to cont_bb let orelse_bb = - if orelse.is_empty() { cont_bb } else { ctx.ctx.append_basic_block(current, "orelse") }; + if orelse.is_empty() { cont_bb } else { ctx.ctx.append_basic_block(current, "while.orelse") }; // store loop bb information and restore it later let loop_bb = ctx.loop_target.replace((test_bb, cont_bb)); ctx.builder.build_unconditional_branch(test_bb); @@ -401,15 +457,15 @@ pub fn gen_if<'ctx, 'a, G: CodeGenerator>( let var_assignment = ctx.var_assignment.clone(); let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); - let test_bb = ctx.ctx.append_basic_block(current, "test"); - let body_bb = ctx.ctx.append_basic_block(current, "body"); + let test_bb = ctx.ctx.append_basic_block(current, "if.test"); + let body_bb = ctx.ctx.append_basic_block(current, "if.body"); let mut cont_bb = None; // if there is no orelse, we just go to cont_bb let orelse_bb = if orelse.is_empty() { - cont_bb = Some(ctx.ctx.append_basic_block(current, "cont")); + cont_bb = Some(ctx.ctx.append_basic_block(current, "if.cont")); cont_bb.unwrap() } else { - ctx.ctx.append_basic_block(current, "orelse") + ctx.ctx.append_basic_block(current, "if.orelse") }; ctx.builder.build_unconditional_branch(test_bb); ctx.builder.position_at_end(test_bb); @@ -844,7 +900,7 @@ pub fn gen_try<'ctx, 'a, G: CodeGenerator>( .builder .build_load(exn_type.into_pointer_value(), "expected_id") .into_int_value(); - let result = ctx.builder.build_int_compare(EQ, actual_id, expected_id, "exncheck"); + let result = ctx.builder.build_int_compare(IntPredicate::EQ, actual_id, expected_id, "exncheck"); ctx.builder.build_conditional_branch(result, handler_bb, dispatcher_cont); dispatcher_end = dispatcher_cont; } else { diff --git a/nac3standalone/demo/src/loop.py b/nac3standalone/demo/src/loop.py index 4129365..8f383fe 100644 --- a/nac3standalone/demo/src/loop.py +++ b/nac3standalone/demo/src/loop.py @@ -1,9 +1,12 @@ +# For Loop using an increasing range() expression as its iterable + @extern def output_int32(x: int32): ... def run() -> int32: - for _ in range(10): - output_int32(_) - _ = 0 + i = 0 + for i in range(10): + output_int32(i) + output_int32(i) return 0 diff --git a/nac3standalone/demo/src/loop_decr.py b/nac3standalone/demo/src/loop_decr.py new file mode 100644 index 0000000..59afb1b --- /dev/null +++ b/nac3standalone/demo/src/loop_decr.py @@ -0,0 +1,12 @@ +# For Loop using a decreasing range() expression as its iterable + +@extern +def output_int32(x: int32): + ... + +def run() -> int32: + i = 0 + for i in range(10, 0, -1): + output_int32(i) + output_int32(i) + return 0 diff --git a/nac3standalone/demo/src/loop_iterable.py b/nac3standalone/demo/src/loop_iterable.py new file mode 100644 index 0000000..d97ab30 --- /dev/null +++ b/nac3standalone/demo/src/loop_iterable.py @@ -0,0 +1,17 @@ +# For Loop using a list as its iterable + +@extern +def output_int32(x: int32): + ... + +def run() -> int32: + l = [0, 1, 2, 3, 4] + + # i: int32 # declaration-without-initializer not yet supported + i = 0 # i must be declared before the loop; this is not necessary in Python + for i in l: + output_int32(i) + i = 0 + output_int32(i) + output_int32(i) + return 0 diff --git a/nac3standalone/demo/src/loop_mutate_var.py b/nac3standalone/demo/src/loop_mutate_var.py new file mode 100644 index 0000000..3ac5c2c --- /dev/null +++ b/nac3standalone/demo/src/loop_mutate_var.py @@ -0,0 +1,14 @@ +# For Loop using an range() expression as its iterable, additionally reassigning the target on each iteration + +@extern +def output_int32(x: int32): + ... + +def run() -> int32: + i = 0 + for i in range(10): + output_int32(i) + i = 0 + output_int32(i) + output_int32(i) + return 0