Fix #284 #316

Merged
sb10q merged 7 commits from issue-284 into master 2023-09-11 11:27:35 +08:00
6 changed files with 164 additions and 61 deletions

View File

@ -77,7 +77,8 @@ pub struct CodeGenContext<'ctx, 'a> {
pub const_strings: HashMap<String, BasicValueEnum<'ctx>>, pub const_strings: HashMap<String, BasicValueEnum<'ctx>>,
// stores the alloca for variables // stores the alloca for variables
pub init_bb: BasicBlock<'ctx>, 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>)>, pub loop_target: Option<(BasicBlock<'ctx>, BasicBlock<'ctx>)>,
// unwind target bb // unwind target bb
pub unwind_target: Option<BasicBlock<'ctx>>, pub unwind_target: Option<BasicBlock<'ctx>>,

View File

@ -13,8 +13,8 @@ use inkwell::{
attributes::{Attribute, AttributeLoc}, attributes::{Attribute, AttributeLoc},
basic_block::BasicBlock, basic_block::BasicBlock,
types::BasicTypeEnum, types::BasicTypeEnum,
values::{BasicValue, BasicValueEnum, FunctionValue, PointerValue}, values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue},
IntPredicate::EQ, IntPredicate,
}; };
use nac3parser::ast::{ use nac3parser::ast::{
Constant, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind, StrRef, Constant, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind, StrRef,
@ -107,7 +107,7 @@ pub fn gen_store_target<'ctx, 'a, G: CodeGenerator>(
); );
// handle negative index // handle negative index
let is_negative = ctx.builder.build_int_compare( let is_negative = ctx.builder.build_int_compare(
inkwell::IntPredicate::SLT, IntPredicate::SLT,
raw_index, raw_index,
generator.get_size_type(ctx.ctx).const_zero(), generator.get_size_type(ctx.ctx).const_zero(),
"is_neg", "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 // unsigned less than is enough, because negative index after adjustment is
// bigger than the length (for unsigned cmp) // bigger than the length (for unsigned cmp)
let bound_check = ctx.builder.build_int_compare( let bound_check = ctx.builder.build_int_compare(
inkwell::IntPredicate::ULT, IntPredicate::ULT,
index, index,
len, len,
"inbound", "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 { if let ExprKind::Name { id, .. } = &target.node {
let (_, static_value, counter) = ctx.var_assignment.get_mut(id).unwrap(); let (_, static_value, counter) = ctx.var_assignment.get_mut(id).unwrap();
*counter += 1; *counter += 1;
@ -208,6 +214,26 @@ pub fn gen_assign<'ctx, 'a, G: CodeGenerator>(
Ok(()) 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>( pub fn gen_for<'ctx, 'a, G: CodeGenerator>(
generator: &mut G, generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>, ctx: &mut CodeGenContext<'ctx, 'a>,
@ -221,60 +247,82 @@ pub fn gen_for<'ctx, 'a, G: CodeGenerator>(
let int32 = ctx.ctx.i32_type(); let int32 = ctx.ctx.i32_type();
let size_t = generator.get_size_type(ctx.ctx); let size_t = generator.get_size_type(ctx.ctx);
let zero = int32.const_zero(); let zero = int32.const_zero();
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); let current = ctx.builder.get_insert_block().and_then(|bb| bb.get_parent()).unwrap();
let test_bb = ctx.ctx.append_basic_block(current, "test"); let body_bb = ctx.ctx.append_basic_block(current, "for.body");
let body_bb = ctx.ctx.append_basic_block(current, "body"); let cont_bb = ctx.ctx.append_basic_block(current, "for.end");
let cont_bb = ctx.ctx.append_basic_block(current, "cont");
// if there is no orelse, we just go to cont_bb // if there is no orelse, we just go to cont_bb
let orelse_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 // 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( let iter_val = generator.gen_expr(ctx, iter)?.unwrap().to_basic_value_enum(
ctx, ctx,
generator, generator,
iter.custom.unwrap(), iter.custom.unwrap(),
)?; )?;
if ctx.unifier.unioned(iter.custom.unwrap(), ctx.primitives.range) { if is_iterable_range_expr {
// setup
let iter_val = iter_val.into_pointer_value(); 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 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"))?; // Variable declared in "target" expression of the loop; Can be reassigned *or* shadowed
let (start, end, step) = destructure_range(ctx, iter_val); let target_i = generator.gen_store_target(ctx, target, Some("for.target.addr"))?;
ctx.builder.build_store(i, ctx.builder.build_int_sub(start, step, "start_init")); let (start, stop, step) = destructure_range(ctx, iter_val);
ctx.builder.build_unconditional_branch(test_bb);
ctx.builder.position_at_end(test_bb); ctx.builder.build_store(i, start);
let sign = ctx.builder.build_int_compare(
inkwell::IntPredicate::SGT, // Check "If step is zero, ValueError is raised."
step, let rangenez = ctx.builder.build_int_compare(IntPredicate::NE, step, int32.const_zero(), "");
int32.const_zero(), ctx.make_assert(
"sign", 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_conditional_branch(
ctx.builder.build_or(pos, neg, "or"), gen_in_range_check(ctx, start, stop, step),
body_bb, body_bb,
orelse_bb, orelse_bb,
); );
ctx.builder.position_at_end(body_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 { } else {
let counter = generator.gen_var_alloc(ctx, size_t.into(), Some("for.counter.addr"))?; let test_bb = backedge_bb_target;
// counter = -1
ctx.builder.build_store(counter, size_t.const_int(u64::max_value(), true)); 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 let len = ctx
.build_gep_and_load( .build_gep_and_load(
iter_val.into_pointer_value(), iter_val.into_pointer_value(),
@ -282,30 +330,36 @@ pub fn gen_for<'ctx, 'a, G: CodeGenerator>(
) )
.into_int_value(); .into_int_value();
ctx.builder.build_unconditional_branch(test_bb); ctx.builder.build_unconditional_branch(test_bb);
ctx.builder.position_at_end(test_bb); ctx.builder.position_at_end(test_bb);
let tmp = ctx.builder.build_load(counter, "i").into_int_value(); let index = ctx.builder.build_load(index_addr, "for.index").into_int_value();
let tmp = ctx.builder.build_int_add(tmp, size_t.const_int(1, false), "inc"); let cmp = ctx.builder.build_int_compare(IntPredicate::SLT, index, len, "cond");
ctx.builder.build_store(counter, tmp);
let cmp = ctx.builder.build_int_compare(inkwell::IntPredicate::SLT, tmp, len, "cmp");
ctx.builder.build_conditional_branch(cmp, body_bb, orelse_bb); ctx.builder.build_conditional_branch(cmp, body_bb, orelse_bb);
ctx.builder.position_at_end(body_bb); ctx.builder.position_at_end(body_bb);
let arr_ptr = ctx let arr_ptr = ctx
.build_gep_and_load(iter_val.into_pointer_value(), &[zero, zero]) .build_gep_and_load(iter_val.into_pointer_value(), &[zero, zero])
.into_pointer_value(); .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())?; 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() { for (k, (_, _, counter)) in var_assignment.iter() {
let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap(); let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap();
if counter != counter2 { if counter != counter2 {
*static_val = None; *static_val = None;
} }
} }
if !ctx.is_terminated() { if !ctx.is_terminated() {
ctx.builder.build_unconditional_branch(test_bb); ctx.builder.build_unconditional_branch(backedge_bb_target);
} }
if !orelse.is_empty() { if !orelse.is_empty() {
ctx.builder.position_at_end(orelse_bb); ctx.builder.position_at_end(orelse_bb);
gen_block(generator, ctx, orelse.iter())?; 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); ctx.builder.build_unconditional_branch(cont_bb);
} }
} }
for (k, (_, _, counter)) in var_assignment.iter() { for (k, (_, _, counter)) in var_assignment.iter() {
let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap(); let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap();
if counter != counter2 { if counter != counter2 {
*static_val = None; *static_val = None;
} }
} }
ctx.builder.position_at_end(cont_bb); ctx.builder.position_at_end(cont_bb);
ctx.loop_target = loop_bb; ctx.loop_target = loop_bb;
} else { } else {
@ -338,12 +394,12 @@ pub fn gen_while<'ctx, 'a, G: CodeGenerator>(
let var_assignment = ctx.var_assignment.clone(); let var_assignment = ctx.var_assignment.clone();
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
let test_bb = ctx.ctx.append_basic_block(current, "test"); let test_bb = ctx.ctx.append_basic_block(current, "while.test");
let body_bb = ctx.ctx.append_basic_block(current, "body"); let body_bb = ctx.ctx.append_basic_block(current, "while.body");
let cont_bb = ctx.ctx.append_basic_block(current, "cont"); let cont_bb = ctx.ctx.append_basic_block(current, "while.cont");
// if there is no orelse, we just go to cont_bb // if there is no orelse, we just go to cont_bb
let orelse_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 // 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((test_bb, cont_bb));
ctx.builder.build_unconditional_branch(test_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 var_assignment = ctx.var_assignment.clone();
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
let test_bb = ctx.ctx.append_basic_block(current, "test"); let test_bb = ctx.ctx.append_basic_block(current, "if.test");
let body_bb = ctx.ctx.append_basic_block(current, "body"); let body_bb = ctx.ctx.append_basic_block(current, "if.body");
let mut cont_bb = None; let mut cont_bb = None;
// if there is no orelse, we just go to cont_bb // if there is no orelse, we just go to cont_bb
let orelse_bb = if orelse.is_empty() { 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() cont_bb.unwrap()
} else { } 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.build_unconditional_branch(test_bb);
ctx.builder.position_at_end(test_bb); ctx.builder.position_at_end(test_bb);
@ -844,7 +900,7 @@ pub fn gen_try<'ctx, 'a, G: CodeGenerator>(
.builder .builder
.build_load(exn_type.into_pointer_value(), "expected_id") .build_load(exn_type.into_pointer_value(), "expected_id")
.into_int_value(); .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); ctx.builder.build_conditional_branch(result, handler_bb, dispatcher_cont);
dispatcher_end = dispatcher_cont; dispatcher_end = dispatcher_cont;
} else { } else {

View File

@ -1,9 +1,12 @@
# For Loop using an increasing range() expression as its iterable
@extern @extern
def output_int32(x: int32): def output_int32(x: int32):
... ...
def run() -> int32: def run() -> int32:
for _ in range(10): i = 0
output_int32(_) for i in range(10):
_ = 0 output_int32(i)
output_int32(i)
return 0 return 0

View File

@ -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

View File

@ -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

View File

@ -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