use super::{ super::symbol_resolver::ValueEnum, expr::destructure_range, irrt::{handle_slice_indices, list_slice_assignment}, CodeGenContext, CodeGenerator, }; use crate::{ codegen::{ classes::{ListValue, RangeValue}, expr::gen_binop_expr, gen_in_range_check, }, toplevel::{ DefinitionId, helper::PRIMITIVE_DEF_IDS, numpy::unpack_ndarray_tvars, TopLevelDef, }, typecheck::typedef::{FunSignature, Type, TypeEnum}, }; use inkwell::{ attributes::{Attribute, AttributeLoc}, basic_block::BasicBlock, types::{BasicType, BasicTypeEnum}, values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue}, IntPredicate, }; use nac3parser::ast::{ Constant, ExcepthandlerKind, Expr, ExprKind, Location, Stmt, StmtKind, StrRef, }; use std::convert::TryFrom; /// See [`CodeGenerator::gen_var_alloc`]. pub fn gen_var<'ctx>( ctx: &mut CodeGenContext<'ctx, '_>, ty: BasicTypeEnum<'ctx>, name: Option<&str>, ) -> Result, String> { // Restore debug location let di_loc = ctx.debug_info.0.create_debug_location( ctx.ctx, ctx.current_loc.row as u32, ctx.current_loc.column as u32, ctx.debug_info.2, None, ); // put the alloca in init block let current = ctx.builder.get_insert_block().unwrap(); // position before the last branching instruction... ctx.builder.position_before(&ctx.init_bb.get_last_instruction().unwrap()); ctx.builder.set_current_debug_location(di_loc); let ptr = ctx.builder.build_alloca(ty, name.unwrap_or("")).unwrap(); ctx.builder.position_at_end(current); ctx.builder.set_current_debug_location(di_loc); Ok(ptr) } /// See [`CodeGenerator::gen_array_var_alloc`]. pub fn gen_array_var<'ctx, 'a, T: BasicType<'ctx>>( ctx: &mut CodeGenContext<'ctx, 'a>, ty: T, size: IntValue<'ctx>, name: Option<&str>, ) -> Result, String> { // Restore debug location let di_loc = ctx.debug_info.0.create_debug_location( ctx.ctx, ctx.current_loc.row as u32, ctx.current_loc.column as u32, ctx.debug_info.2, None, ); // put the alloca in init block let current = ctx.builder.get_insert_block().unwrap(); // position before the last branching instruction... ctx.builder.position_before(&ctx.init_bb.get_last_instruction().unwrap()); ctx.builder.set_current_debug_location(di_loc); let ptr = ctx.builder.build_array_alloca(ty, size, name.unwrap_or("")).unwrap(); ctx.builder.position_at_end(current); ctx.builder.set_current_debug_location(di_loc); Ok(ptr) } /// See [`CodeGenerator::gen_store_target`]. pub fn gen_store_target<'ctx, G: CodeGenerator>( generator: &mut G, ctx: &mut CodeGenContext<'ctx, '_>, pattern: &Expr>, name: Option<&str>, ) -> Result>, String> { let llvm_usize = generator.get_size_type(ctx.ctx); // very similar to gen_expr, but we don't do an extra load at the end // and we flatten nested tuples Ok(Some(match &pattern.node { ExprKind::Name { id, .. } => match ctx.var_assignment.get(id) { None => { let ptr_ty = ctx.get_llvm_type(generator, pattern.custom.unwrap()); let ptr = generator.gen_var_alloc(ctx, ptr_ty, name)?; ctx.var_assignment.insert(*id, (ptr, None, 0)); ptr } Some(v) => { let (ptr, counter) = (v.0, v.2); ctx.var_assignment.insert(*id, (ptr, None, counter)); ptr } } ExprKind::Attribute { value, attr, .. } => { let index = ctx.get_attr_index(value.custom.unwrap(), *attr); let val = if let Some(v) = generator.gen_expr(ctx, value)? { v.to_basic_value_enum(ctx, generator, value.custom.unwrap())? } else { return Ok(None) }; let BasicValueEnum::PointerValue(ptr) = val else { unreachable!(); }; unsafe { ctx.builder.build_in_bounds_gep( ptr, &[ ctx.ctx.i32_type().const_zero(), ctx.ctx.i32_type().const_int(index as u64, false), ], name.unwrap_or(""), ) }.unwrap() } ExprKind::Subscript { value, slice, .. } => { match ctx.unifier.get_ty_immutable(value.custom.unwrap()).as_ref() { TypeEnum::TList { .. } => { let v = generator .gen_expr(ctx, value)? .unwrap() .to_basic_value_enum(ctx, generator, value.custom.unwrap())? .into_pointer_value(); let v = ListValue::from_ptr_val(v, llvm_usize, None); let len = v.load_size(ctx, Some("len")); let raw_index = generator .gen_expr(ctx, slice)? .unwrap() .to_basic_value_enum(ctx, generator, slice.custom.unwrap())? .into_int_value(); let raw_index = ctx.builder .build_int_s_extend(raw_index, generator.get_size_type(ctx.ctx), "sext") .unwrap(); // handle negative index let is_negative = ctx.builder .build_int_compare( IntPredicate::SLT, raw_index, generator.get_size_type(ctx.ctx).const_zero(), "is_neg", ) .unwrap(); let adjusted = ctx.builder.build_int_add(raw_index, len, "adjusted").unwrap(); let index = ctx .builder .build_select(is_negative, adjusted, raw_index, "index") .map(BasicValueEnum::into_int_value) .unwrap(); // 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( IntPredicate::ULT, index, len, "inbound", ) .unwrap(); ctx.make_assert( generator, bound_check, "0:IndexError", "index {0} out of bounds 0:{1}", [Some(raw_index), Some(len), None], slice.location, ); v.data().ptr_offset(ctx, generator, index, name) } TypeEnum::TObj { obj_id, .. } if *obj_id == PRIMITIVE_DEF_IDS.ndarray => { todo!() } _ => unreachable!(), } } _ => unreachable!(), })) } /// See [`CodeGenerator::gen_assign`]. pub fn gen_assign<'ctx, G: CodeGenerator>( generator: &mut G, ctx: &mut CodeGenContext<'ctx, '_>, target: &Expr>, value: ValueEnum<'ctx>, ) -> Result<(), String> { let llvm_usize = generator.get_size_type(ctx.ctx); match &target.node { ExprKind::Tuple { elts, .. } => { let BasicValueEnum::StructValue(v) = value.to_basic_value_enum(ctx, generator, target.custom.unwrap())? else { unreachable!() }; for (i, elt) in elts.iter().enumerate() { let v = ctx .builder .build_extract_value(v, u32::try_from(i).unwrap(), "struct_elem") .unwrap(); generator.gen_assign(ctx, elt, v.into())?; } } ExprKind::Subscript { value: ls, slice, .. } if matches!(&slice.node, ExprKind::Slice { .. }) => { let ExprKind::Slice { lower, upper, step } = &slice.node else { unreachable!() }; let ls = generator .gen_expr(ctx, ls)? .unwrap() .to_basic_value_enum(ctx, generator, ls.custom.unwrap())? .into_pointer_value(); let ls = ListValue::from_ptr_val(ls, llvm_usize, None); let Some((start, end, step)) = handle_slice_indices(lower, upper, step, ctx, generator, ls)? else { return Ok(()) }; let value = value .to_basic_value_enum(ctx, generator, target.custom.unwrap())? .into_pointer_value(); let value = ListValue::from_ptr_val(value, llvm_usize, None); let ty = match &*ctx.unifier.get_ty_immutable(target.custom.unwrap()) { TypeEnum::TList { ty } => *ty, TypeEnum::TObj { obj_id, .. } if *obj_id == PRIMITIVE_DEF_IDS.ndarray => { unpack_ndarray_tvars(&mut ctx.unifier, target.custom.unwrap()).0 } _ => unreachable!(), }; let ty = ctx.get_llvm_type(generator, ty); let Some(src_ind) = handle_slice_indices(&None, &None, &None, ctx, generator, value)? else { return Ok(()) }; list_slice_assignment(generator, ctx, ty, ls, (start, end, step), value, src_ind); } _ => { let name = if let ExprKind::Name { id, .. } = &target.node { format!("{id}.addr") } else { String::from("target.addr") }; let Some(ptr) = generator.gen_store_target(ctx, target, Some(name.as_str()))? else { return Ok(()) }; if let ExprKind::Name { id, .. } = &target.node { let (_, static_value, counter) = ctx.var_assignment.get_mut(id).unwrap(); *counter += 1; if let ValueEnum::Static(s) = &value { *static_value = Some(s.clone()); } } let val = value.to_basic_value_enum(ctx, generator, target.custom.unwrap())?; ctx.builder.build_store(ptr, val).unwrap(); } }; Ok(()) } /// See [`CodeGenerator::gen_for`]. pub fn gen_for( generator: &mut G, ctx: &mut CodeGenContext<'_, '_>, stmt: &Stmt>, ) -> Result<(), String> { let StmtKind::For { iter, target, body, orelse, .. } = &stmt.node else { unreachable!() }; // var_assignment static values may be changed in another branch // if so, remove the static value as it may not be correct in this branch let var_assignment = ctx.var_assignment.clone(); 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().and_then(BasicBlock::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, "for.orelse") }; // Whether the iterable is a range() expression let is_iterable_range_expr = ctx.unifier.unioned(iter.custom.unwrap(), ctx.primitives.range); // The BB containing the increment expression let incr_bb = ctx.ctx.append_basic_block(current, "for.incr"); // The BB containing the loop condition check let cond_bb = ctx.ctx.append_basic_block(current, "for.cond"); // store loop bb information and restore it later let loop_bb = ctx.loop_target.replace((incr_bb, cont_bb)); let iter_val = if let Some(v) = generator.gen_expr(ctx, iter)? { v.to_basic_value_enum( ctx, generator, iter.custom.unwrap(), )? } else { return Ok(()) }; if is_iterable_range_expr { let iter_val = RangeValue::from_ptr_val(iter_val.into_pointer_value(), Some("range")); // Internal variable for loop; Cannot be assigned let i = generator.gen_var_alloc(ctx, int32.into(), Some("for.i.addr"))?; // Variable declared in "target" expression of the loop; Can be reassigned *or* shadowed let Some(target_i) = generator.gen_store_target(ctx, target, Some("for.target.addr"))? else { unreachable!() }; let (start, stop, step) = destructure_range(ctx, iter_val); ctx.builder.build_store(i, start).unwrap(); // Check "If step is zero, ValueError is raised." let rangenez = ctx.builder .build_int_compare(IntPredicate::NE, step, int32.const_zero(), "") .unwrap(); ctx.make_assert( generator, rangenez, "ValueError", "range() arg 3 must not be zero", [None, None, None], ctx.current_loc ); ctx.builder.build_unconditional_branch(cond_bb).unwrap(); { ctx.builder.position_at_end(cond_bb); ctx.builder .build_conditional_branch( gen_in_range_check( ctx, ctx.builder.build_load(i, "").map(BasicValueEnum::into_int_value).unwrap(), stop, step, ), body_bb, orelse_bb, ) .unwrap(); } ctx.builder.position_at_end(incr_bb); let next_i = ctx.builder .build_int_add( ctx.builder.build_load(i, "").map(BasicValueEnum::into_int_value).unwrap(), step, "inc", ) .unwrap(); ctx.builder.build_store(i, next_i).unwrap(); ctx.builder.build_unconditional_branch(cond_bb).unwrap(); ctx.builder.position_at_end(body_bb); ctx.builder .build_store( target_i, ctx.builder.build_load(i, "").map(BasicValueEnum::into_int_value).unwrap(), ) .unwrap(); generator.gen_block(ctx, body.iter())?; } else { 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()).unwrap(); let len = ctx .build_gep_and_load( iter_val.into_pointer_value(), &[zero, int32.const_int(1, false)], Some("len") ) .into_int_value(); ctx.builder.build_unconditional_branch(cond_bb).unwrap(); ctx.builder.position_at_end(cond_bb); let index = ctx.builder .build_load(index_addr, "for.index") .map(BasicValueEnum::into_int_value) .unwrap(); let cmp = ctx.builder.build_int_compare(IntPredicate::SLT, index, len, "cond").unwrap(); ctx.builder.build_conditional_branch(cmp, body_bb, orelse_bb).unwrap(); ctx.builder.position_at_end(incr_bb); let index = ctx.builder.build_load(index_addr, "").map(BasicValueEnum::into_int_value).unwrap(); let inc = ctx.builder.build_int_add(index, size_t.const_int(1, true), "inc").unwrap(); ctx.builder.build_store(index_addr, inc).unwrap(); ctx.builder.build_unconditional_branch(cond_bb).unwrap(); ctx.builder.position_at_end(body_bb); let arr_ptr = ctx .build_gep_and_load(iter_val.into_pointer_value(), &[zero, zero], Some("arr.addr")) .into_pointer_value(); let index = ctx.builder .build_load(index_addr, "for.index") .map(BasicValueEnum::into_int_value) .unwrap(); let val = ctx.build_gep_and_load(arr_ptr, &[index], Some("val")); generator.gen_assign(ctx, target, val.into())?; generator.gen_block(ctx, body.iter())?; } for (k, (_, _, counter)) in &var_assignment { 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(incr_bb).unwrap(); } if !orelse.is_empty() { ctx.builder.position_at_end(orelse_bb); generator.gen_block(ctx, orelse.iter())?; if !ctx.is_terminated() { ctx.builder.build_unconditional_branch(cont_bb).unwrap(); } } for (k, (_, _, counter)) in &var_assignment { 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; Ok(()) } /// Generates a C-style `for` construct using lambdas, similar to the following C code: /// /// ```c /// for (x... = init(); cond(x...); update(x...)) { /// body(x...); /// } /// ``` /// /// * `init` - A lambda containing IR statements declaring and initializing loop variables. The /// return value is a [Clone] value which will be passed to the other lambdas. /// * `cond` - A lambda containing IR statements checking whether the loop should continue /// executing. The result value must be an `i1` indicating if the loop should continue. /// * `body` - A lambda containing IR statements within the loop body. /// * `update` - A lambda containing IR statements updating loop variables. pub fn gen_for_callback<'ctx, 'a, I, InitFn, CondFn, BodyFn, UpdateFn>( generator: &mut dyn CodeGenerator, ctx: &mut CodeGenContext<'ctx, 'a>, init: InitFn, cond: CondFn, body: BodyFn, update: UpdateFn, ) -> Result<(), String> where I: Clone, InitFn: FnOnce(&mut dyn CodeGenerator, &mut CodeGenContext<'ctx, 'a>) -> Result, CondFn: FnOnce(&mut dyn CodeGenerator, &mut CodeGenContext<'ctx, 'a>, I) -> Result, String>, BodyFn: FnOnce(&mut dyn CodeGenerator, &mut CodeGenContext<'ctx, 'a>, I) -> Result<(), String>, UpdateFn: FnOnce(&mut dyn CodeGenerator, &mut CodeGenContext<'ctx, 'a>, I) -> Result<(), String>, { let current = ctx.builder.get_insert_block().and_then(BasicBlock::get_parent).unwrap(); let init_bb = ctx.ctx.append_basic_block(current, "for.init"); // The BB containing the loop condition check let cond_bb = ctx.ctx.append_basic_block(current, "for.cond"); let body_bb = ctx.ctx.append_basic_block(current, "for.body"); // The BB containing the increment expression let update_bb = ctx.ctx.append_basic_block(current, "for.update"); let cont_bb = ctx.ctx.append_basic_block(current, "for.end"); // store loop bb information and restore it later let loop_bb = ctx.loop_target.replace((update_bb, cont_bb)); ctx.builder.build_unconditional_branch(init_bb).unwrap(); let loop_var = { ctx.builder.position_at_end(init_bb); let result = init(generator, ctx)?; ctx.builder.build_unconditional_branch(cond_bb).unwrap(); result }; ctx.builder.position_at_end(cond_bb); let cond = cond(generator, ctx, loop_var.clone())?; assert_eq!(cond.get_type().get_bit_width(), ctx.ctx.bool_type().get_bit_width()); ctx.builder .build_conditional_branch(cond, body_bb, cont_bb) .unwrap(); ctx.builder.position_at_end(body_bb); body(generator, ctx, loop_var.clone())?; ctx.builder.build_unconditional_branch(update_bb).unwrap(); ctx.builder.position_at_end(update_bb); update(generator, ctx, loop_var)?; ctx.builder.build_unconditional_branch(cond_bb).unwrap(); ctx.builder.position_at_end(cont_bb); ctx.loop_target = loop_bb; Ok(()) } /// Generates a C-style monotonically-increasing `for` construct using lambdas, similar to the /// following C code: /// /// ```c /// for (int x = init_val; x /* < or <= ; see `max_val` */ max_val; x += incr_val) { /// body(x); /// } /// ``` /// /// * `init_val` - The initial value of the loop variable. The type of this value will also be used /// as the type of the loop variable. /// * `max_val` - A tuple containing the maximum value of the loop variable, and whether the maximum /// value should be treated as inclusive (as opposed to exclusive). /// * `body` - A lambda containing IR statements within the loop body. /// * `incr_val` - The value to increment the loop variable on each iteration. pub fn gen_for_callback_incrementing<'ctx, 'a, BodyFn>( generator: &mut dyn CodeGenerator, ctx: &mut CodeGenContext<'ctx, 'a>, init_val: IntValue<'ctx>, max_val: (IntValue<'ctx>, bool), body: BodyFn, incr_val: IntValue<'ctx>, ) -> Result<(), String> where BodyFn: FnOnce(&mut dyn CodeGenerator, &mut CodeGenContext<'ctx, 'a>, IntValue<'ctx>) -> Result<(), String>, { let init_val_t = init_val.get_type(); gen_for_callback( generator, ctx, |generator, ctx| { let i_addr = generator.gen_var_alloc(ctx, init_val_t.into(), None)?; ctx.builder.build_store(i_addr, init_val).unwrap(); Ok(i_addr) }, |_, ctx, i_addr| { let cmp_op = if max_val.1 { IntPredicate::ULE } else { IntPredicate::ULT }; let i = ctx.builder .build_load(i_addr, "") .map(BasicValueEnum::into_int_value) .unwrap(); let max_val = ctx.builder .build_int_z_extend_or_bit_cast(max_val.0, init_val_t, "") .unwrap(); Ok(ctx.builder.build_int_compare(cmp_op, i, max_val, "").unwrap()) }, |generator, ctx, i_addr| { let i = ctx.builder .build_load(i_addr, "") .map(BasicValueEnum::into_int_value) .unwrap(); body(generator, ctx, i) }, |_, ctx, i_addr| { let i = ctx.builder .build_load(i_addr, "") .map(BasicValueEnum::into_int_value) .unwrap(); let incr_val = ctx.builder .build_int_z_extend_or_bit_cast(incr_val, init_val_t, "") .unwrap(); let i = ctx.builder.build_int_add(i, incr_val, "").unwrap(); ctx.builder.build_store(i_addr, i).unwrap(); Ok(()) }, ) } /// See [`CodeGenerator::gen_while`]. pub fn gen_while( generator: &mut G, ctx: &mut CodeGenContext<'_, '_>, stmt: &Stmt>, ) -> Result<(), String> { let StmtKind::While { test, body, orelse, .. } = &stmt.node else { unreachable!() }; // var_assignment static values may be changed in another branch // if so, remove the static value as it may not be correct in this branch 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, "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, "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).unwrap(); ctx.builder.position_at_end(test_bb); let test = if let Some(v) = generator.gen_expr(ctx, test)? { v.to_basic_value_enum(ctx, generator, test.custom.unwrap())? } else { for bb in [body_bb, cont_bb] { ctx.builder.position_at_end(bb); ctx.builder.build_unreachable().unwrap(); } return Ok(()) }; let BasicValueEnum::IntValue(test) = test else { unreachable!() }; ctx.builder .build_conditional_branch(generator.bool_to_i1(ctx, test), body_bb, orelse_bb) .unwrap(); ctx.builder.position_at_end(body_bb); generator.gen_block(ctx, body.iter())?; for (k, (_, _, counter)) in &var_assignment { 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).unwrap(); } if !orelse.is_empty() { ctx.builder.position_at_end(orelse_bb); generator.gen_block(ctx, orelse.iter())?; if !ctx.is_terminated() { ctx.builder.build_unconditional_branch(cont_bb).unwrap(); } } for (k, (_, _, counter)) in &var_assignment { 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; Ok(()) } /// See [`CodeGenerator::gen_if`]. pub fn gen_if( generator: &mut G, ctx: &mut CodeGenContext<'_, '_>, stmt: &Stmt>, ) -> Result<(), String> { let StmtKind::If { test, body, orelse, .. } = &stmt.node else { unreachable!() }; // var_assignment static values may be changed in another branch // if so, remove the static value as it may not be correct in this branch 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, "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, "if.cont")); cont_bb.unwrap() } else { ctx.ctx.append_basic_block(current, "if.orelse") }; ctx.builder.build_unconditional_branch(test_bb).unwrap(); ctx.builder.position_at_end(test_bb); let test = generator .gen_expr(ctx, test) .and_then(|v| v.map(|v| v.to_basic_value_enum(ctx, generator, test.custom.unwrap())).transpose())?; if let Some(BasicValueEnum::IntValue(test)) = test { ctx.builder .build_conditional_branch(generator.bool_to_i1(ctx, test), body_bb, orelse_bb) .unwrap(); }; ctx.builder.position_at_end(body_bb); generator.gen_block(ctx, body.iter())?; for (k, (_, _, counter)) in &var_assignment { let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap(); if counter != counter2 { *static_val = None; } } if !ctx.is_terminated() { if cont_bb.is_none() { cont_bb = Some(ctx.ctx.append_basic_block(current, "cont")); } ctx.builder.build_unconditional_branch(cont_bb.unwrap()).unwrap(); } if !orelse.is_empty() { ctx.builder.position_at_end(orelse_bb); generator.gen_block(ctx, orelse.iter())?; if !ctx.is_terminated() { if cont_bb.is_none() { cont_bb = Some(ctx.ctx.append_basic_block(current, "cont")); } ctx.builder.build_unconditional_branch(cont_bb.unwrap()).unwrap(); } } if let Some(cont_bb) = cont_bb { ctx.builder.position_at_end(cont_bb); } for (k, (_, _, counter)) in &var_assignment { let (_, static_val, counter2) = ctx.var_assignment.get_mut(k).unwrap(); if counter != counter2 { *static_val = None; } } Ok(()) } pub fn final_proxy<'ctx>( ctx: &mut CodeGenContext<'ctx, '_>, target: BasicBlock<'ctx>, block: BasicBlock<'ctx>, final_data: &mut (PointerValue, Vec>, Vec>), ) { let (final_state, final_targets, final_paths) = final_data; let prev = ctx.builder.get_insert_block().unwrap(); ctx.builder.position_at_end(block); unsafe { ctx.builder.build_store(*final_state, target.get_address().unwrap()).unwrap(); } ctx.builder.position_at_end(prev); final_targets.push(target); final_paths.push(block); } /// Inserts the declaration of the builtin function with the specified `symbol` name, and returns /// the function. pub fn get_builtins<'ctx>( generator: &mut dyn CodeGenerator, ctx: &mut CodeGenContext<'ctx, '_>, symbol: &str, ) -> FunctionValue<'ctx> { ctx.module.get_function(symbol).unwrap_or_else(|| { let ty = match symbol { "__nac3_raise" => ctx .ctx .void_type() .fn_type(&[ctx.get_llvm_type(generator, ctx.primitives.exception).into()], false), "__nac3_resume" | "__nac3_end_catch" => ctx.ctx.void_type().fn_type(&[], false), _ => unimplemented!(), }; let fun = ctx.module.add_function(symbol, ty, None); if symbol == "__nac3_raise" || symbol == "__nac3_resume" { fun.add_attribute( AttributeLoc::Function, ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id("noreturn"), 0), ); } fun }) } pub fn exn_constructor<'ctx>( ctx: &mut CodeGenContext<'ctx, '_>, obj: Option<(Type, ValueEnum<'ctx>)>, _fun: (&FunSignature, DefinitionId), mut args: Vec<(Option, ValueEnum<'ctx>)>, generator: &mut dyn CodeGenerator, ) -> Result>, String> { let (zelf_ty, zelf) = obj.unwrap(); let zelf = zelf.to_basic_value_enum(ctx, generator, zelf_ty)?.into_pointer_value(); let int32 = ctx.ctx.i32_type(); let zero = int32.const_zero(); let zelf_id = if let TypeEnum::TObj { obj_id, .. } = &*ctx.unifier.get_ty(zelf_ty) { obj_id.0 } else { unreachable!() }; let defs = ctx.top_level.definitions.read(); let def = defs[zelf_id].read(); let TopLevelDef::Class { name: zelf_name, .. } = &*def else { unreachable!() }; let exception_name = format!("{}:{}", ctx.resolver.get_exception_id(zelf_id), zelf_name); unsafe { let id_ptr = ctx.builder.build_in_bounds_gep(zelf, &[zero, zero], "exn.id").unwrap(); let id = ctx.resolver.get_string_id(&exception_name); ctx.builder.build_store(id_ptr, int32.const_int(id as u64, false)).unwrap(); let empty_string = ctx.gen_const(generator, &Constant::Str(String::new()), ctx.primitives.str); let ptr = ctx.builder .build_in_bounds_gep(zelf, &[zero, int32.const_int(5, false)], "exn.msg") .unwrap(); let msg = if args.is_empty() { empty_string.unwrap() } else { args.remove(0).1.to_basic_value_enum(ctx, generator, ctx.primitives.str)? }; ctx.builder.build_store(ptr, msg).unwrap(); for i in &[6, 7, 8] { let value = if args.is_empty() { ctx.ctx.i64_type().const_zero().into() } else { args.remove(0).1.to_basic_value_enum(ctx, generator, ctx.primitives.int64)? }; let ptr = ctx.builder .build_in_bounds_gep(zelf, &[zero, int32.const_int(*i, false)], "exn.param") .unwrap(); ctx.builder.build_store(ptr, value).unwrap(); } // set file, func to empty string for i in &[1, 4] { let ptr = ctx.builder .build_in_bounds_gep(zelf, &[zero, int32.const_int(*i, false)], "exn.str") .unwrap(); ctx.builder.build_store(ptr, empty_string.unwrap()).unwrap(); } // set ints to zero for i in &[2, 3] { let ptr = ctx.builder .build_in_bounds_gep(zelf, &[zero, int32.const_int(*i, false)], "exn.ints") .unwrap(); ctx.builder.build_store(ptr, zero).unwrap(); } } Ok(Some(zelf.into())) } /// Generates IR for a `raise` statement. /// /// * `exception` - The exception thrown by the `raise` statement. /// * `loc` - The location where the exception is raised from. pub fn gen_raise<'ctx>( generator: &mut dyn CodeGenerator, ctx: &mut CodeGenContext<'ctx, '_>, exception: Option<&BasicValueEnum<'ctx>>, loc: Location, ) { if let Some(exception) = exception { unsafe { let int32 = ctx.ctx.i32_type(); let zero = int32.const_zero(); let exception = exception.into_pointer_value(); let file_ptr = ctx.builder .build_in_bounds_gep(exception, &[zero, int32.const_int(1, false)], "file_ptr") .unwrap(); let filename = ctx.gen_string(generator, loc.file.0); ctx.builder.build_store(file_ptr, filename).unwrap(); let row_ptr = ctx.builder .build_in_bounds_gep(exception, &[zero, int32.const_int(2, false)], "row_ptr") .unwrap(); ctx.builder.build_store(row_ptr, int32.const_int(loc.row as u64, false)).unwrap(); let col_ptr = ctx.builder .build_in_bounds_gep(exception, &[zero, int32.const_int(3, false)], "col_ptr") .unwrap(); ctx.builder.build_store(col_ptr, int32.const_int(loc.column as u64, false)).unwrap(); let current_fun = ctx.builder.get_insert_block().unwrap().get_parent().unwrap(); let fun_name = ctx.gen_string(generator, current_fun.get_name().to_str().unwrap()); let name_ptr = ctx.builder .build_in_bounds_gep(exception, &[zero, int32.const_int(4, false)], "name_ptr") .unwrap(); ctx.builder.build_store(name_ptr, fun_name).unwrap(); } let raise = get_builtins(generator, ctx, "__nac3_raise"); let exception = *exception; ctx.build_call_or_invoke(raise, &[exception], "raise"); } else { let resume = get_builtins(generator, ctx, "__nac3_resume"); ctx.build_call_or_invoke(resume, &[], "resume"); } ctx.builder.build_unreachable().unwrap(); } /// Generates IR for a `try` statement. pub fn gen_try<'ctx, 'a, G: CodeGenerator>( generator: &mut G, ctx: &mut CodeGenContext<'ctx, 'a>, target: &Stmt>, ) -> Result<(), String> { let StmtKind::Try { body, handlers, orelse, finalbody, .. } = &target.node else { unreachable!() }; // if we need to generate anything related to exception, we must have personality defined let personality_symbol = ctx.top_level.personality_symbol.as_ref().unwrap(); let personality = ctx.module.get_function(personality_symbol).unwrap_or_else(|| { let ty = ctx.ctx.i32_type().fn_type(&[], true); ctx.module.add_function(personality_symbol, ty, None) }); let exception_type = ctx.get_llvm_type(generator, ctx.primitives.exception); let ptr_type = ctx.ctx.i8_type().ptr_type(inkwell::AddressSpace::default()); let current_block = ctx.builder.get_insert_block().unwrap(); let current_fun = current_block.get_parent().unwrap(); let landingpad = ctx.ctx.append_basic_block(current_fun, "try.landingpad"); let dispatcher = ctx.ctx.append_basic_block(current_fun, "try.dispatch"); let mut dispatcher_end = dispatcher; ctx.builder.position_at_end(dispatcher); let exn = ctx.builder.build_phi(exception_type, "exn").unwrap(); ctx.builder.position_at_end(current_block); let mut cleanup = None; let mut old_loop_target = None; let mut old_return = None; let mut final_data = None; let has_cleanup = !finalbody.is_empty(); if has_cleanup { let final_state = generator.gen_var_alloc(ctx, ptr_type.into(), Some("try.final_state.addr"))?; final_data = Some((final_state, Vec::new(), Vec::new())); if let Some((continue_target, break_target)) = ctx.loop_target { let break_proxy = ctx.ctx.append_basic_block(current_fun, "try.break"); let continue_proxy = ctx.ctx.append_basic_block(current_fun, "try.continue"); final_proxy(ctx, break_target, break_proxy, final_data.as_mut().unwrap()); final_proxy(ctx, continue_target, continue_proxy, final_data.as_mut().unwrap()); old_loop_target = ctx.loop_target.replace((continue_proxy, break_proxy)); } let return_proxy = ctx.ctx.append_basic_block(current_fun, "try.return"); if let Some(return_target) = ctx.return_target { final_proxy(ctx, return_target, return_proxy, final_data.as_mut().unwrap()); } else { let return_target = ctx.ctx.append_basic_block(current_fun, "try.return_target"); ctx.builder.position_at_end(return_target); let return_value = ctx.return_buffer .map(|v| ctx.builder.build_load(v, "$ret").unwrap()); ctx.builder.build_return(return_value.as_ref().map(|v| v as &dyn BasicValue)).unwrap(); ctx.builder.position_at_end(current_block); final_proxy(ctx, return_target, return_proxy, final_data.as_mut().unwrap()); } old_return = ctx.return_target.replace(return_proxy); cleanup = Some(ctx.ctx.append_basic_block(current_fun, "try.cleanup")); } let mut clauses = Vec::new(); let mut found_catch_all = false; for handler_node in handlers { let ExcepthandlerKind::ExceptHandler { type_, .. } = &handler_node.node; // none or Exception if type_.is_none() || ctx .unifier .unioned(type_.as_ref().unwrap().custom.unwrap(), ctx.primitives.exception) { clauses.push(None); found_catch_all = true; break; } let type_ = type_.as_ref().unwrap(); let exn_name = ctx.resolver.get_type_name( &ctx.top_level.definitions.read(), &mut ctx.unifier, type_.custom.unwrap(), ); let obj_id = if let TypeEnum::TObj { obj_id, .. } = &*ctx.unifier.get_ty(type_.custom.unwrap()) { *obj_id } else { unreachable!() }; let exception_name = format!("{}:{}", ctx.resolver.get_exception_id(obj_id.0), exn_name); let exn_id = ctx.resolver.get_string_id(&exception_name); let exn_id_global = ctx.module.add_global(ctx.ctx.i32_type(), None, &format!("exn.{exn_id}")); exn_id_global.set_initializer(&ctx.ctx.i32_type().const_int(exn_id as u64, false)); clauses.push(Some(exn_id_global.as_pointer_value().as_basic_value_enum())); } let mut all_clauses = clauses.clone(); if let Some(old_clauses) = &ctx.outer_catch_clauses { if !found_catch_all { all_clauses.extend_from_slice(&old_clauses.0); } } let old_clauses = ctx.outer_catch_clauses.replace((all_clauses, dispatcher, exn)); let old_unwind = ctx.unwind_target.replace(landingpad); generator.gen_block(ctx, body.iter())?; if ctx.builder.get_insert_block().unwrap().get_terminator().is_none() { generator.gen_block(ctx, orelse.iter())?; } let body = ctx.builder.get_insert_block().unwrap(); // reset old_clauses and old_unwind let (all_clauses, _, _) = ctx.outer_catch_clauses.take().unwrap(); ctx.outer_catch_clauses = old_clauses; ctx.unwind_target = old_unwind; ctx.return_target = old_return; ctx.loop_target = old_loop_target.or(ctx.loop_target).take(); let old_unwind = if finalbody.is_empty() { None } else { let final_landingpad = ctx.ctx.append_basic_block(current_fun, "try.catch.final"); ctx.builder.position_at_end(final_landingpad); ctx.builder .build_landing_pad( ctx.ctx.struct_type(&[ptr_type.into(), exception_type], false), personality, &[], true, "try.catch.final", ) .unwrap(); ctx.builder.build_unconditional_branch(cleanup.unwrap()).unwrap(); ctx.builder.position_at_end(body); ctx.unwind_target.replace(final_landingpad) }; // run end_catch before continue/break/return let mut final_proxy_lambda = |ctx: &mut CodeGenContext<'ctx, 'a>, target: BasicBlock<'ctx>, block: BasicBlock<'ctx>| final_proxy(ctx, target, block, final_data.as_mut().unwrap()); let mut redirect_lambda = |ctx: &mut CodeGenContext<'ctx, 'a>, target: BasicBlock<'ctx>, block: BasicBlock<'ctx>| { ctx.builder.position_at_end(block); ctx.builder.build_unconditional_branch(target).unwrap(); ctx.builder.position_at_end(body); }; let redirect = if has_cleanup { &mut final_proxy_lambda as &mut dyn FnMut(&mut CodeGenContext<'ctx, 'a>, BasicBlock<'ctx>, BasicBlock<'ctx>) } else { &mut redirect_lambda as &mut dyn FnMut(&mut CodeGenContext<'ctx, 'a>, BasicBlock<'ctx>, BasicBlock<'ctx>) }; let resume = get_builtins(generator, ctx, "__nac3_resume"); let end_catch = get_builtins(generator, ctx, "__nac3_end_catch"); if let Some((continue_target, break_target)) = ctx.loop_target.take() { let break_proxy = ctx.ctx.append_basic_block(current_fun, "try.break"); let continue_proxy = ctx.ctx.append_basic_block(current_fun, "try.continue"); ctx.builder.position_at_end(break_proxy); ctx.builder.build_call(end_catch, &[], "end_catch").unwrap(); ctx.builder.position_at_end(continue_proxy); ctx.builder.build_call(end_catch, &[], "end_catch").unwrap(); ctx.builder.position_at_end(body); redirect(ctx, break_target, break_proxy); redirect(ctx, continue_target, continue_proxy); ctx.loop_target = Some((continue_proxy, break_proxy)); old_loop_target = Some((continue_target, break_target)); } let return_proxy = ctx.ctx.append_basic_block(current_fun, "try.return"); ctx.builder.position_at_end(return_proxy); ctx.builder.build_call(end_catch, &[], "end_catch").unwrap(); let return_target = ctx.return_target.take().unwrap_or_else(|| { let doreturn = ctx.ctx.append_basic_block(current_fun, "try.doreturn"); ctx.builder.position_at_end(doreturn); let return_value = ctx.return_buffer.map(|v| ctx.builder.build_load(v, "$ret").unwrap()); ctx.builder.build_return(return_value.as_ref().map(|v| v as &dyn BasicValue)).unwrap(); doreturn }); redirect(ctx, return_target, return_proxy); ctx.return_target = Some(return_proxy); old_return = Some(return_target); let mut post_handlers = Vec::new(); let exnid = if handlers.is_empty() { None } else { ctx.builder.position_at_end(dispatcher); unsafe { let zero = ctx.ctx.i32_type().const_zero(); let exnid_ptr = ctx.builder .build_gep( exn.as_basic_value().into_pointer_value(), &[zero, zero], "exnidptr", ) .unwrap(); Some(ctx.builder.build_load(exnid_ptr, "exnid").unwrap()) } }; for (handler_node, exn_type) in handlers.iter().zip(clauses.iter()) { let ExcepthandlerKind::ExceptHandler { type_, name, body } = &handler_node.node; let handler_bb = ctx.ctx.append_basic_block(current_fun, "try.handler"); ctx.builder.position_at_end(handler_bb); if let Some(name) = name { let exn_ty = ctx.get_llvm_type(generator, type_.as_ref().unwrap().custom.unwrap()); let exn_store = generator.gen_var_alloc(ctx, exn_ty, Some("try.exn_store.addr"))?; ctx.var_assignment.insert(*name, (exn_store, None, 0)); ctx.builder.build_store(exn_store, exn.as_basic_value()).unwrap(); } generator.gen_block(ctx, body.iter())?; let current = ctx.builder.get_insert_block().unwrap(); // only need to call end catch if not terminated // otherwise, we already handled in return/break/continue/raise if current.get_terminator().is_none() { ctx.builder.build_call(end_catch, &[], "end_catch").unwrap(); } post_handlers.push(current); ctx.builder.position_at_end(dispatcher_end); if let Some(exn_type) = exn_type { let dispatcher_cont = ctx.ctx.append_basic_block(current_fun, "try.dispatcher_cont"); let actual_id = exnid.unwrap().into_int_value(); let expected_id = ctx .builder .build_load(exn_type.into_pointer_value(), "expected_id") .map(BasicValueEnum::into_int_value) .unwrap(); let result = ctx.builder .build_int_compare(IntPredicate::EQ, actual_id, expected_id, "exncheck") .unwrap(); ctx.builder.build_conditional_branch(result, handler_bb, dispatcher_cont).unwrap(); dispatcher_end = dispatcher_cont; } else { ctx.builder.build_unconditional_branch(handler_bb).unwrap(); break; } } ctx.unwind_target = old_unwind; ctx.loop_target = old_loop_target.or(ctx.loop_target).take(); ctx.return_target = old_return; ctx.builder.position_at_end(landingpad); let clauses: Vec<_> = if finalbody.is_empty() { &all_clauses } else { &clauses } .iter() .map(|v| v.unwrap_or(ptr_type.const_zero().into())) .collect(); let landingpad_value = ctx .builder .build_landing_pad( ctx.ctx.struct_type(&[ptr_type.into(), exception_type], false), personality, &clauses, has_cleanup, "try.landingpad", ) .map(BasicValueEnum::into_struct_value) .unwrap(); let exn_val = ctx.builder.build_extract_value(landingpad_value, 1, "exn").unwrap(); ctx.builder.build_unconditional_branch(dispatcher).unwrap(); exn.add_incoming(&[(&exn_val, landingpad)]); if dispatcher_end.get_terminator().is_none() { ctx.builder.position_at_end(dispatcher_end); if let Some(cleanup) = cleanup { ctx.builder.build_unconditional_branch(cleanup).unwrap(); } else if let Some((_, outer_dispatcher, phi)) = ctx.outer_catch_clauses { phi.add_incoming(&[(&exn_val, dispatcher_end)]); ctx.builder.build_unconditional_branch(outer_dispatcher).unwrap(); } else { ctx.build_call_or_invoke(resume, &[], "resume"); ctx.builder.build_unreachable().unwrap(); } } if finalbody.is_empty() { let tail = ctx.ctx.append_basic_block(current_fun, "try.tail"); if body.get_terminator().is_none() { ctx.builder.position_at_end(body); ctx.builder.build_unconditional_branch(tail).unwrap(); } if matches!(cleanup, Some(cleanup) if cleanup.get_terminator().is_none()) { ctx.builder.position_at_end(cleanup.unwrap()); ctx.builder.build_unconditional_branch(tail).unwrap(); } for post_handler in post_handlers { if post_handler.get_terminator().is_none() { ctx.builder.position_at_end(post_handler); ctx.builder.build_unconditional_branch(tail).unwrap(); } } ctx.builder.position_at_end(tail); } else { // exception path let cleanup = cleanup.unwrap(); ctx.builder.position_at_end(cleanup); generator.gen_block(ctx, finalbody.iter())?; if !ctx.is_terminated() { ctx.build_call_or_invoke(resume, &[], "resume"); ctx.builder.build_unreachable().unwrap(); } // normal path let (final_state, mut final_targets, final_paths) = final_data.unwrap(); let tail = ctx.ctx.append_basic_block(current_fun, "try.tail"); final_targets.push(tail); let finalizer = ctx.ctx.append_basic_block(current_fun, "try.finally"); ctx.builder.position_at_end(finalizer); generator.gen_block(ctx, finalbody.iter())?; if !ctx.is_terminated() { let dest = ctx.builder.build_load(final_state, "final_dest").unwrap(); ctx.builder.build_indirect_branch(dest, &final_targets).unwrap(); } for block in &final_paths { if block.get_terminator().is_none() { ctx.builder.position_at_end(*block); ctx.builder.build_unconditional_branch(finalizer).unwrap(); } } for block in [body].iter().chain(post_handlers.iter()) { if block.get_terminator().is_none() { ctx.builder.position_at_end(*block); unsafe { ctx.builder.build_store(final_state, tail.get_address().unwrap()).unwrap(); } ctx.builder.build_unconditional_branch(finalizer).unwrap(); } } ctx.builder.position_at_end(tail); } Ok(()) } /// See [`CodeGenerator::gen_with`]. pub fn gen_with( _: &mut G, _: &mut CodeGenContext<'_, '_>, stmt: &Stmt>, ) -> Result<(), String> { // TODO: Implement with statement after finishing exceptions Err(format!("With statement with custom types is not yet supported (at {})", stmt.location)) } /// Generates IR for a `return` statement. pub fn gen_return( generator: &mut G, ctx: &mut CodeGenContext<'_, '_>, value: &Option>>>, ) -> Result<(), String> { let func = ctx.builder.get_insert_block().and_then(BasicBlock::get_parent).unwrap(); let value = if let Some(v_expr) = value.as_ref() { if let Some(v) = generator.gen_expr(ctx, v_expr).transpose() { Some( v.and_then(|v| v.to_basic_value_enum(ctx, generator, v_expr.custom.unwrap()))? ) } else { return Ok(()) } } else { None }; if let Some(return_target) = ctx.return_target { if let Some(value) = value { ctx.builder.build_store(ctx.return_buffer.unwrap(), value).unwrap(); } ctx.builder.build_unconditional_branch(return_target).unwrap(); } else if ctx.need_sret { // sret ctx.builder.build_store(ctx.return_buffer.unwrap(), value.unwrap()).unwrap(); ctx.builder.build_return(None).unwrap(); } else { // Remap boolean return type into i1 let value = value.map(|v| { let expected_ty = func.get_type().get_return_type().unwrap(); let ret_val = v.as_basic_value_enum(); if expected_ty.is_int_type() && ret_val.is_int_value() { let ret_type = expected_ty.into_int_type(); let ret_val = ret_val.into_int_value(); if ret_type.get_bit_width() == 1 && ret_val.get_type().get_bit_width() != 1 { generator.bool_to_i1(ctx, ret_val) } else { ret_val }.into() } else { ret_val } }); let value = value.as_ref().map(|v| v as &dyn BasicValue); ctx.builder.build_return(value).unwrap(); } Ok(()) } /// See [`CodeGenerator::gen_stmt`]. pub fn gen_stmt( generator: &mut G, ctx: &mut CodeGenContext<'_, '_>, stmt: &Stmt>, ) -> Result<(), String> { ctx.current_loc = stmt.location; let loc = ctx.debug_info.0.create_debug_location( ctx.ctx, ctx.current_loc.row as u32, ctx.current_loc.column as u32, ctx.debug_info.2, None, ); ctx.builder.set_current_debug_location(loc); match &stmt.node { StmtKind::Pass { .. } => {} StmtKind::Expr { value, .. } => { generator.gen_expr(ctx, value)?; } StmtKind::Return { value, .. } => { gen_return(generator, ctx, value)?; } StmtKind::AnnAssign { target, value, .. } => { if let Some(value) = value { let Some(value) = generator.gen_expr(ctx, value)? else { return Ok(()) }; generator.gen_assign(ctx, target, value)?; } } StmtKind::Assign { targets, value, .. } => { let Some(value) = generator.gen_expr(ctx, value)? else { return Ok(()) }; for target in targets { generator.gen_assign(ctx, target, value.clone())?; } } StmtKind::Continue { .. } => { ctx.builder.build_unconditional_branch(ctx.loop_target.unwrap().0).unwrap(); } StmtKind::Break { .. } => { ctx.builder.build_unconditional_branch(ctx.loop_target.unwrap().1).unwrap(); } StmtKind::If { .. } => generator.gen_if(ctx, stmt)?, StmtKind::While { .. } => generator.gen_while(ctx, stmt)?, StmtKind::For { .. } => generator.gen_for(ctx, stmt)?, StmtKind::With { .. } => generator.gen_with(ctx, stmt)?, StmtKind::AugAssign { target, op, value, .. } => { let value = gen_binop_expr(generator, ctx, target, op, value, stmt.location, true)?; generator.gen_assign(ctx, target, value.unwrap())?; } StmtKind::Try { .. } => gen_try(generator, ctx, stmt)?, StmtKind::Raise { exc, .. } => { if let Some(exc) = exc { let exc = if let Some(v) = generator.gen_expr(ctx, exc)? { v.to_basic_value_enum(ctx, generator, exc.custom.unwrap())? } else { return Ok(()) }; gen_raise(generator, ctx, Some(&exc), stmt.location); } else { gen_raise(generator, ctx, None, stmt.location); } } StmtKind::Assert { test, msg, .. } => { let test = if let Some(v) = generator.gen_expr(ctx, test)? { v.to_basic_value_enum(ctx, generator, test.custom.unwrap())? } else { return Ok(()) }; let err_msg = match msg { Some(msg) => if let Some(v) = generator.gen_expr(ctx, msg)? { v.to_basic_value_enum(ctx, generator, msg.custom.unwrap())? } else { return Ok(()) }, None => ctx.gen_string(generator, ""), }; ctx.make_assert_impl( generator, test.into_int_value(), "0:AssertionError", err_msg, [None, None, None], stmt.location, ); } _ => unimplemented!() }; Ok(()) } /// Generates IR for a block statement contains `stmts`. pub fn gen_block<'a, G: CodeGenerator, I: Iterator>>>( generator: &mut G, ctx: &mut CodeGenContext<'_, '_>, stmts: I, ) -> Result<(), String> { for stmt in stmts { generator.gen_stmt(ctx, stmt)?; if ctx.is_terminated() { break; } } Ok(()) }