Compare commits

..

7 Commits

Author SHA1 Message Date
David Mak 48c6498d1f core: Fix restoration of loop target in try statement
old_loop_target is only assigned if ctx.loop_target is overwritten,
meaning that if ctx.loop_target is never overwritten, ctx.loop_target
will always be overwritten to None.

We fix this by only restoring from old_loop_target if we previously
assigned to old_loop_target.
2023-09-28 20:00:02 +08:00
David Mak 2a38d5160e meta: Respect opt flags when performing whole-module optimization 2023-09-28 19:58:54 +08:00
David Mak b39831b388 standalone: Update demos
- Add `output_str` for printing a string
- Add demo_test.py to test interop
2023-09-28 19:58:53 +08:00
David Mak cb39f61e79 core: Fix passing structure arguments to extern functions
All parameters with a structure type in extern functions are marked as
`byref` instead of `byval`, as most ABIs require the first several
arguments to be passed in registers before spilling into the stack.

`byval` breaks this contract by explicitly requiring all arguments to be
 passed in the stack, breaking interop with libraries written in other
 languages.
2023-09-28 15:02:35 +08:00
David Mak 176f250bdb core: Fix missing conversion to i1 for IfExp 2023-09-28 10:06:40 +08:00
David Mak acdb1de6fe meta: Improve documentation for various modified classes 2023-09-25 15:42:07 +08:00
David Mak 31dcd2dde9 core: Use i8 for boolean variable allocation
In LLVM, i1 represents a 1-byte integer with a single valid bit; The
rest of the 7 upper bits are undefined. This causes problems when
using these variables in memory operations (e.g. memcpy/memmove as
needed by List slicing and assignment).

We fix this by treating all local boolean variables as i8 so that they
are well-defined for memory operations. Function ABIs will continue to
use i1, as memory operations cannot be directly performed on function
arguments or return types, instead they are always converted back into
local boolean variables (which are i8s anyways).

Fixes #315.
2023-09-25 15:42:07 +08:00
33 changed files with 379 additions and 288 deletions

View File

@ -450,7 +450,7 @@ fn rpc_codegen_callback_fn<'ctx, 'a>(
let alloc_bb = ctx.ctx.append_basic_block(current_function, "rpc.continue");
let tail_bb = ctx.ctx.append_basic_block(current_function, "rpc.tail");
let ret_ty = ctx.get_llvm_type(generator, fun.0.ret);
let ret_ty = ctx.get_llvm_abi_type(generator, fun.0.ret);
let need_load = !ret_ty.is_pointer_type();
let slot = ctx.builder.build_alloca(ret_ty, "rpc.ret.slot");
let slotgen = ctx.builder.build_bitcast(slot, ptr_type, "rpc.ret.ptr");

View File

@ -134,9 +134,7 @@ impl StaticValue for PythonValue {
PrimitiveValue::U32(val) => ctx.ctx.i32_type().const_int(*val as u64, false).into(),
PrimitiveValue::U64(val) => ctx.ctx.i64_type().const_int(*val as u64, false).into(),
PrimitiveValue::F64(val) => ctx.ctx.f64_type().const_float(*val).into(),
PrimitiveValue::Bool(val) => {
ctx.ctx.bool_type().const_int(*val as u64, false).into()
}
PrimitiveValue::Bool(val) => ctx.ctx.i8_type().const_int(*val as u64, false).into(),
});
}
if let Some(global) = ctx.module.get_global(&self.id.to_string()) {
@ -808,7 +806,7 @@ impl InnerResolver {
} else if ty_id == self.primitive_ids.bool {
let val: bool = obj.extract().unwrap();
self.id_to_primitive.write().insert(id, PrimitiveValue::Bool(val));
Ok(Some(ctx.ctx.bool_type().const_int(val as u64, false).into()))
Ok(Some(ctx.ctx.i8_type().const_int(val as u64, false).into()))
} else if ty_id == self.primitive_ids.float || ty_id == self.primitive_ids.float64 {
let val: f64 = obj.extract().unwrap();
self.id_to_primitive.write().insert(id, PrimitiveValue::F64(val));

View File

@ -4,6 +4,7 @@ use crate::{
codegen::{
concrete_type::{ConcreteFuncArg, ConcreteTypeEnum, ConcreteTypeStore},
get_llvm_type,
get_llvm_abi_type,
irrt::*,
stmt::gen_raise,
CodeGenContext, CodeGenTask,
@ -58,6 +59,8 @@ pub fn get_subst_key(
}
impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
/// Builds a sequence of `getelementptr` and `load` instructions which stores the value of a
/// struct field into an LLVM value.
pub fn build_gep_and_load(
&mut self,
ptr: PointerValue<'ctx>,
@ -103,7 +106,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
SymbolValue::I64(v) => self.ctx.i64_type().const_int(*v as u64, true).into(),
SymbolValue::U32(v) => self.ctx.i32_type().const_int(*v as u64, false).into(),
SymbolValue::U64(v) => self.ctx.i64_type().const_int(*v as u64, false).into(),
SymbolValue::Bool(v) => self.ctx.bool_type().const_int(*v as u64, true).into(),
SymbolValue::Bool(v) => self.ctx.i8_type().const_int(*v as u64, true).into(),
SymbolValue::Double(v) => self.ctx.f64_type().const_float(*v).into(),
SymbolValue::Str(v) => {
let str_ptr =
@ -160,6 +163,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
}
}
/// See [get_llvm_type].
pub fn get_llvm_type(
&mut self,
generator: &mut dyn CodeGenerator,
@ -177,6 +181,25 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
)
}
/// See [get_llvm_abi_type].
pub fn get_llvm_abi_type(
&mut self,
generator: &mut dyn CodeGenerator,
ty: Type,
) -> BasicTypeEnum<'ctx> {
get_llvm_abi_type(
self.ctx,
&self.module,
generator,
&mut self.unifier,
self.top_level,
&mut self.type_cache,
&self.primitives,
ty,
)
}
/// Generates an LLVM variable for a [constant value][value] with a given [type][ty].
pub fn gen_const(
&mut self,
generator: &mut dyn CodeGenerator,
@ -186,7 +209,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
match value {
Constant::Bool(v) => {
assert!(self.unifier.unioned(ty, self.primitives.bool));
let ty = self.ctx.bool_type();
let ty = self.ctx.i8_type();
ty.const_int(if *v { 1 } else { 0 }, false).into()
}
Constant::Int(val) => {
@ -238,6 +261,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
}
}
/// Generates a binary operation `op` between two integral operands `lhs` and `rhs`.
pub fn gen_int_ops(
&mut self,
generator: &mut dyn CodeGenerator,
@ -282,6 +306,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
}
}
/// Generates a binary operation `op` between two floating-point operands `lhs` and `rhs`.
pub fn gen_float_ops(
&mut self,
op: &Operator,
@ -406,6 +431,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
}
}
/// Helper function for generating a LLVM variable storing a [String].
pub fn gen_string<S: Into<String>>(
&mut self,
generator: &mut dyn CodeGenerator,
@ -505,6 +531,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
}
}
/// See [CodeGenerator::gen_constructor].
pub fn gen_constructor<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -536,6 +563,7 @@ pub fn gen_constructor<'ctx, 'a, G: CodeGenerator>(
}
}
/// See [CodeGenerator::gen_func_instance].
pub fn gen_func_instance<'ctx, 'a>(
ctx: &mut CodeGenContext<'ctx, 'a>,
obj: Option<(Type, ValueEnum<'ctx>)>,
@ -612,6 +640,7 @@ pub fn gen_func_instance<'ctx, 'a>(
}
}
/// See [CodeGenerator::gen_call].
pub fn gen_call<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -715,18 +744,19 @@ pub fn gen_call<'ctx, 'a, G: CodeGenerator>(
let ret_type = if ctx.unifier.unioned(fun.0.ret, ctx.primitives.none) {
None
} else {
Some(ctx.get_llvm_type(generator, fun.0.ret))
Some(ctx.get_llvm_abi_type(generator, fun.0.ret))
};
let has_sret = ret_type.map_or(false, |ret_type| need_sret(ctx.ctx, ret_type));
let mut byrefs = Vec::new();
let mut params =
args.iter().enumerate().map(|(i, arg)| match ctx.get_llvm_type(generator, arg.ty) {
let mut params = args.iter().enumerate()
.map(|(i, arg)| match ctx.get_llvm_abi_type(generator, arg.ty) {
BasicTypeEnum::StructType(ty) if is_extern => {
byrefs.push((i, ty));
ty.ptr_type(AddressSpace::default()).into()
},
x => x
}.into()).collect_vec();
}.into())
.collect_vec();
if has_sret {
params.insert(0, ret_type.unwrap().ptr_type(AddressSpace::default()).into());
}
@ -758,9 +788,30 @@ pub fn gen_call<'ctx, 'a, G: CodeGenerator>(
}
fun_val
});
// Convert boolean parameter values into i1
let param_vals = (&fun_val.get_params()).iter().zip(param_vals)
.map(|(p, v)| {
if p.is_int_value() && v.is_int_value() {
let expected_ty = p.into_int_value().get_type();
let param_val = v.into_int_value();
if expected_ty.get_bit_width() == 1 && param_val.get_type().get_bit_width() != 1 {
generator.bool_to_i1(ctx, param_val)
} else {
param_val
}.into()
} else {
v
}
})
.collect_vec();
Ok(ctx.build_call_or_invoke(fun_val, &param_vals, "call"))
}
/// Generates three LLVM variables representing the start, stop, and step values of a [range] class
/// respectively.
pub fn destructure_range<'ctx, 'a>(
ctx: &mut CodeGenContext<'ctx, 'a>,
range: PointerValue<'ctx>,
@ -829,6 +880,7 @@ pub fn allocate_list<'ctx, 'a, G: CodeGenerator>(
arr_str_ptr
}
/// Generates LLVM IR for a [list comprehension expression][expr].
pub fn gen_comprehension<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -963,12 +1015,14 @@ pub fn gen_comprehension<'ctx, 'a, G: CodeGenerator>(
let val = ctx.build_gep_and_load(arr_ptr, &[tmp], Some("val"));
generator.gen_assign(ctx, target, val.into())?;
}
for cond in ifs.iter() {
let result = generator
.gen_expr(ctx, cond)?
.unwrap()
.to_basic_value_enum(ctx, generator, cond.custom.unwrap())?
.into_int_value();
let result = generator.bool_to_i1(ctx, result);
let succ = ctx.ctx.append_basic_block(current, "then");
ctx.builder.build_conditional_branch(result, succ, test_bb);
@ -996,6 +1050,13 @@ pub fn gen_comprehension<'ctx, 'a, G: CodeGenerator>(
}
}
/// Generates LLVM IR for a [binary operator expression][expr].
///
/// * `left` - The left-hand side of the binary operator.
/// * `op` - The operator applied on the operands.
/// * `right` - The right-hand side of the binary operator.
/// * `loc` - The location of the full expression.
/// * `is_aug_assign` - Whether the binary operator expression is also an assignment operator.
pub fn gen_binop_expr<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -1092,6 +1153,7 @@ pub fn gen_binop_expr<'ctx, 'a, G: CodeGenerator>(
}
}
/// See [CodeGenerator::gen_expr].
pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -1231,6 +1293,7 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
.unwrap()
.to_basic_value_enum(ctx, generator, values[0].custom.unwrap())?
.into_int_value();
let left = generator.bool_to_i1(ctx, left);
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
let a_bb = ctx.ctx.append_basic_block(current, "a");
let b_bb = ctx.ctx.append_basic_block(current, "b");
@ -1239,7 +1302,7 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
let (a, b) = match op {
Boolop::Or => {
ctx.builder.position_at_end(a_bb);
let a = ctx.ctx.bool_type().const_int(1, false);
let a = ctx.ctx.i8_type().const_int(1, false);
ctx.builder.build_unconditional_branch(cont_bb);
ctx.builder.position_at_end(b_bb);
let b = generator
@ -1247,6 +1310,7 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
.unwrap()
.to_basic_value_enum(ctx, generator, values[1].custom.unwrap())?
.into_int_value();
let b = generator.bool_to_i8(ctx, b);
ctx.builder.build_unconditional_branch(cont_bb);
(a, b)
}
@ -1257,15 +1321,16 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
.unwrap()
.to_basic_value_enum(ctx, generator, values[1].custom.unwrap())?
.into_int_value();
let a = generator.bool_to_i8(ctx, a);
ctx.builder.build_unconditional_branch(cont_bb);
ctx.builder.position_at_end(b_bb);
let b = ctx.ctx.bool_type().const_int(0, false);
let b = ctx.ctx.i8_type().const_zero();
ctx.builder.build_unconditional_branch(cont_bb);
(a, b)
}
};
ctx.builder.position_at_end(cont_bb);
let phi = ctx.builder.build_phi(ctx.ctx.bool_type(), "phi");
let phi = ctx.builder.build_phi(ctx.ctx.i8_type(), "");
phi.add_incoming(&[(&a, a_bb), (&b, b_bb)]);
phi.as_basic_value().into()
}
@ -1399,6 +1464,7 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
.unwrap()
.to_basic_value_enum(ctx, generator, test.custom.unwrap())?
.into_int_value();
let test = generator.bool_to_i1(ctx, test);
let body_ty = body.custom.unwrap();
let is_none = ctx.unifier.get_representative(body_ty) == ctx.primitives.none;
let result = if !is_none {

View File

@ -1,5 +1,5 @@
use crate::{
codegen::{expr::*, stmt::*, CodeGenContext},
codegen::{expr::*, stmt::*, bool_to_i1, bool_to_i8, CodeGenContext},
symbol_resolver::ValueEnum,
toplevel::{DefinitionId, TopLevelDef},
typecheck::typedef::{FunSignature, Type},
@ -7,7 +7,7 @@ use crate::{
use inkwell::{
context::Context,
types::{BasicTypeEnum, IntType},
values::{BasicValueEnum, PointerValue},
values::{BasicValueEnum, IntValue, PointerValue},
};
use nac3parser::ast::{Expr, Stmt, StrRef};
@ -180,6 +180,24 @@ pub trait CodeGenerator {
{
gen_stmt(self, ctx, stmt)
}
/// See [bool_to_i1].
fn bool_to_i1<'ctx, 'a>(
&self,
ctx: &CodeGenContext<'ctx, 'a>,
bool_value: IntValue<'ctx>
) -> IntValue<'ctx> {
bool_to_i1(&ctx.builder, bool_value)
}
/// See [bool_to_i8].
fn bool_to_i8<'ctx, 'a>(
&self,
ctx: &CodeGenContext<'ctx, 'a>,
bool_value: IntValue<'ctx>
) -> IntValue<'ctx> {
bool_to_i8(&ctx.builder, &ctx.ctx, bool_value)
}
}
pub struct DefaultCodeGenerator {

View File

@ -9,6 +9,7 @@ use crate::{
use crossbeam::channel::{unbounded, Receiver, Sender};
use inkwell::{
AddressSpace,
IntPredicate,
OptimizationLevel,
attributes::{Attribute, AttributeLoc},
basic_block::BasicBlock,
@ -18,7 +19,7 @@ use inkwell::{
passes::PassBuilderOptions,
targets::{CodeModel, RelocMode, Target, TargetMachine, TargetTriple},
types::{AnyType, BasicType, BasicTypeEnum},
values::{BasicValueEnum, FunctionValue, PhiValue, PointerValue},
values::{BasicValueEnum, FunctionValue, IntValue, PhiValue, PointerValue},
debug_info::{
DebugInfoBuilder, DICompileUnit, DISubprogram, AsDIScope, DIFlagsConstants, DIScope
},
@ -353,6 +354,10 @@ pub struct CodeGenTask {
pub id: usize,
}
/// Retrieves the [LLVM type][BasicTypeEnum] corresponding to the [Type].
///
/// This function is used to obtain the in-memory representation of `ty`, e.g. a `bool` variable
/// would be represented by an `i8`.
fn get_llvm_type<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
@ -464,6 +469,34 @@ fn get_llvm_type<'ctx>(
})
}
/// Retrieves the [LLVM type][BasicTypeEnum] corresponding to the [Type].
///
/// This function is used mainly to obtain the ABI representation of `ty`, e.g. a `bool` is
/// would be represented by an `i1`.
///
/// The difference between the in-memory representation (as returned by [get_llvm_type]) and the
/// ABI representation is that the in-memory representation must be at least byte-sized and must
/// be byte-aligned for the variable to be addressable in memory, whereas there is no such
/// restriction for ABI representations.
fn get_llvm_abi_type<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
generator: &mut dyn CodeGenerator,
unifier: &mut Unifier,
top_level: &TopLevelContext,
type_cache: &mut HashMap<Type, BasicTypeEnum<'ctx>>,
primitives: &PrimitiveStore,
ty: Type,
) -> BasicTypeEnum<'ctx> {
// If the type is used in the definition of a function, return `i1` instead of `i8` for ABI
// consistency.
return if unifier.unioned(ty, primitives.bool) {
ctx.bool_type().into()
} else {
get_llvm_type(ctx, module, generator, unifier, top_level, type_cache, primitives, ty)
}
}
fn need_sret<'ctx>(ctx: &'ctx Context, ty: BasicTypeEnum<'ctx>) -> bool {
fn need_sret_impl<'ctx>(ctx: &'ctx Context, ty: BasicTypeEnum<'ctx>, maybe_large: bool) -> bool {
match ty {
@ -477,6 +510,7 @@ fn need_sret<'ctx>(ctx: &'ctx Context, ty: BasicTypeEnum<'ctx>) -> bool {
need_sret_impl(ctx, ty, true)
}
/// Implementation for generating LLVM IR for a function.
pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenContext) -> Result<(), String>> (
context: &'ctx Context,
generator: &mut G,
@ -533,7 +567,7 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte
(primitives.uint32, context.i32_type().into()),
(primitives.uint64, context.i64_type().into()),
(primitives.float, context.f64_type().into()),
(primitives.bool, context.bool_type().into()),
(primitives.bool, context.i8_type().into()),
(primitives.str, {
let name = "str";
match module.get_struct_type(name) {
@ -591,14 +625,14 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte
let ret_type = if unifier.unioned(ret, primitives.none) {
None
} else {
Some(get_llvm_type(context, &module, generator, &mut unifier, top_level_ctx.as_ref(), &mut type_cache, &primitives, ret))
Some(get_llvm_abi_type(context, &module, generator, &mut unifier, top_level_ctx.as_ref(), &mut type_cache, &primitives, ret))
};
let has_sret = ret_type.map_or(false, |ty| need_sret(context, ty));
let mut params = args
.iter()
.map(|arg| {
get_llvm_type(
get_llvm_abi_type(
context,
&module,
generator,
@ -646,19 +680,35 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte
let offset = if has_sret { 1 } else { 0 };
for (n, arg) in args.iter().enumerate() {
let param = fn_val.get_nth_param((n as u32) + offset).unwrap();
let alloca = builder.build_alloca(
get_llvm_type(
context,
&module,
generator,
&mut unifier,
top_level_ctx.as_ref(),
&mut type_cache,
&primitives,
arg.ty,
),
&arg.name.to_string(),
let local_type = get_llvm_type(
context,
&module,
generator,
&mut unifier,
top_level_ctx.as_ref(),
&mut type_cache,
&primitives,
arg.ty,
);
let alloca = builder.build_alloca(
local_type,
&format!("{}.addr", &arg.name.to_string()),
);
// Remap boolean parameters into i8
let param = if local_type.is_int_type() && param.is_int_value() {
let expected_ty = local_type.into_int_type();
let param_val = param.into_int_value();
if expected_ty.get_bit_width() == 8 && param_val.get_type().get_bit_width() == 1 {
bool_to_i8(&builder, &context, param_val)
} else {
param_val
}.into()
} else {
param
};
builder.build_store(alloca, param);
var_assignment.insert(arg.name, (alloca, None, 0));
}
@ -785,6 +835,15 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte
Ok((builder, module, fn_val))
}
/// Generates LLVM IR for a function.
///
/// * `context` - The [LLVM Context][Context] used in generating the function body.
/// * `generator` - The [CodeGenerator] for generating various program constructs.
/// * `registry` - The [WorkerRegistry] responsible for monitoring this function generation task.
/// * `builder` - The [Builder] used for generating LLVM IR.
/// * `module` - The [Module] of which the generated LLVM function will be inserted into.
/// * `task` - The [CodeGenTask] associated with this function generation task.
///
pub fn gen_func<'ctx, G: CodeGenerator>(
context: &'ctx Context,
generator: &mut G,
@ -801,3 +860,40 @@ pub fn gen_func<'ctx, G: CodeGenerator>(
Ok(())
})
}
/// Converts the value of a boolean-like value `bool_value` into an `i1`.
fn bool_to_i1<'ctx>(builder: &Builder<'ctx>, bool_value: IntValue<'ctx>) -> IntValue<'ctx> {
if bool_value.get_type().get_bit_width() != 1 {
builder.build_int_compare(
IntPredicate::NE,
bool_value,
bool_value.get_type().const_zero(),
"tobool"
)
} else {
bool_value
}
}
/// Converts the value of a boolean-like value `bool_value` into an `i8`.
fn bool_to_i8<'ctx>(
builder: &Builder<'ctx>,
ctx: &'ctx Context,
bool_value: IntValue<'ctx>
) -> IntValue<'ctx> {
let value_bits = bool_value.get_type().get_bit_width();
match value_bits {
8 => bool_value,
1 => builder.build_int_z_extend(bool_value, ctx.i8_type(), "frombool"),
_ => bool_to_i8(
builder,
ctx,
builder.build_int_compare(
IntPredicate::NE,
bool_value,
bool_value.get_type().const_zero(),
""
)
),
}
}

View File

@ -21,6 +21,7 @@ use nac3parser::ast::{
};
use std::convert::TryFrom;
/// See [CodeGenerator::gen_var_alloc].
pub fn gen_var<'ctx, 'a>(
ctx: &mut CodeGenContext<'ctx, 'a>,
ty: BasicTypeEnum<'ctx>,
@ -35,6 +36,7 @@ pub fn gen_var<'ctx, 'a>(
Ok(ptr)
}
/// See [CodeGenerator::gen_store_target].
pub fn gen_store_target<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -144,6 +146,7 @@ pub fn gen_store_target<'ctx, 'a, G: CodeGenerator>(
})
}
/// See [CodeGenerator::gen_assign].
pub fn gen_assign<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -214,13 +217,21 @@ 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].
/// 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.
/// The generated IR is equivalent to the following Rust code:
///
/// ```rust,ignore
/// let sign = step > 0;
/// let (lo, hi) = if sign { (value, stop) } else { (stop, value) };
/// let cmp = lo < hi;
/// ```
///
/// Returns an `i1` [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>,
@ -234,6 +245,7 @@ fn gen_in_range_check<'ctx, 'a>(
ctx.builder.build_int_compare(IntPredicate::SLT, lo, hi, "cmp")
}
/// See [CodeGenerator::gen_for].
pub fn gen_for<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -384,6 +396,7 @@ pub fn gen_for<'ctx, 'a, G: CodeGenerator>(
Ok(())
}
/// See [CodeGenerator::gen_while].
pub fn gen_while<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -411,7 +424,7 @@ pub fn gen_while<'ctx, 'a, G: CodeGenerator>(
test.custom.unwrap(),
)?;
if let BasicValueEnum::IntValue(test) = test {
ctx.builder.build_conditional_branch(test, body_bb, orelse_bb);
ctx.builder.build_conditional_branch(generator.bool_to_i1(ctx, test), body_bb, orelse_bb);
} else {
unreachable!()
};
@ -447,6 +460,7 @@ pub fn gen_while<'ctx, 'a, G: CodeGenerator>(
Ok(())
}
/// See [CodeGenerator::gen_if].
pub fn gen_if<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -470,13 +484,11 @@ pub fn gen_if<'ctx, 'a, G: CodeGenerator>(
};
ctx.builder.build_unconditional_branch(test_bb);
ctx.builder.position_at_end(test_bb);
let test = generator.gen_expr(ctx, test)?.unwrap().to_basic_value_enum(
ctx,
generator,
test.custom.unwrap(),
)?;
let test = generator.gen_expr(ctx, test)?
.unwrap()
.to_basic_value_enum(ctx, generator, test.custom.unwrap())?;
if let BasicValueEnum::IntValue(test) = test {
ctx.builder.build_conditional_branch(test, body_bb, orelse_bb);
ctx.builder.build_conditional_branch(generator.bool_to_i1(ctx, test), body_bb, orelse_bb);
} else {
unreachable!()
};
@ -537,6 +549,8 @@ pub fn final_proxy<'ctx, 'a>(
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, 'a>(
generator: &mut dyn CodeGenerator,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -634,6 +648,10 @@ pub fn exn_constructor<'ctx, 'a>(
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, 'a>(
generator: &mut dyn CodeGenerator,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -685,6 +703,7 @@ pub fn gen_raise<'ctx, 'a>(
ctx.builder.build_unreachable();
}
/// Generates IR for a `try` statement.
pub fn gen_try<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -1006,6 +1025,7 @@ pub fn gen_try<'ctx, 'a, G: CodeGenerator>(
}
}
/// See [CodeGenerator::gen_with].
pub fn gen_with<'ctx, 'a, G: CodeGenerator>(
_: &mut G,
_: &mut CodeGenContext<'ctx, 'a>,
@ -1015,11 +1035,13 @@ pub fn gen_with<'ctx, 'a, G: CodeGenerator>(
Err(format!("With statement with custom types is not yet supported (at {})", stmt.location))
}
/// Generates IR for a `return` statement.
pub fn gen_return<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
value: &Option<Box<Expr<Option<Type>>>>,
) -> Result<(), String> {
let func = ctx.builder.get_insert_block().and_then(|bb| bb.get_parent()).unwrap();
let value = value
.as_ref()
.map(|v_expr| {
@ -1038,12 +1060,31 @@ pub fn gen_return<'ctx, 'a, G: CodeGenerator>(
ctx.builder.build_store(ctx.return_buffer.unwrap(), value.unwrap());
ctx.builder.build_return(None);
} 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);
ctx.builder.build_return(value.into());
}
Ok(())
}
/// See [CodeGenerator::gen_stmt].
pub fn gen_stmt<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -1135,6 +1176,7 @@ pub fn gen_stmt<'ctx, 'a, G: CodeGenerator>(
Ok(())
}
/// Generates IR for a block statement contains `stmts`.
pub fn gen_block<'ctx, 'a, 'b, G: CodeGenerator, I: Iterator<Item = &'b Stmt<Option<Type>>>>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,

View File

@ -24,7 +24,6 @@ use nac3parser::{
use parking_lot::RwLock;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use inkwell::targets::{InitializationConfig, Target};
struct Resolver {
id_to_type: HashMap<StrRef, Type>,

View File

@ -59,6 +59,7 @@ impl Display for SymbolValue {
}
pub trait StaticValue {
/// Returns a unique identifier for this value.
fn get_unique_identifier(&self) -> u64;
fn get_const_obj<'ctx, 'a>(
@ -67,6 +68,7 @@ pub trait StaticValue {
generator: &mut dyn CodeGenerator,
) -> BasicValueEnum<'ctx>;
/// Converts this value to a LLVM [BasicValueEnum].
fn to_basic_value_enum<'ctx, 'a>(
&self,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -74,12 +76,14 @@ pub trait StaticValue {
expected_ty: Type,
) -> Result<BasicValueEnum<'ctx>, String>;
/// Returns a field within this value.
fn get_field<'ctx, 'a>(
&self,
name: StrRef,
ctx: &mut CodeGenContext<'ctx, 'a>,
) -> Option<ValueEnum<'ctx>>;
/// Returns a single element of this tuple.
fn get_tuple_element<'ctx>(&self, index: u32) -> Option<ValueEnum<'ctx>>;
}

View File

@ -1050,7 +1050,7 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
let uint32 = ctx.primitives.uint32;
let uint64 = ctx.primitives.uint64;
let float = ctx.primitives.float;
let llvm_i1 = ctx.ctx.bool_type().as_basic_type_enum();
let llvm_i8 = ctx.ctx.i8_type().as_basic_type_enum();
let llvm_i32 = ctx.ctx.i32_type().as_basic_type_enum();
let llvm_i64 = ctx.ctx.i64_type().as_basic_type_enum();
let llvm_f64 = ctx.ctx.f64_type().as_basic_type_enum();
@ -1060,7 +1060,7 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
let n_val = args[1].1.clone().to_basic_value_enum(ctx, generator, n_ty)?;
let mut is_type = |a: Type, b: Type| ctx.unifier.unioned(a, b);
let (fun_name, arg_ty) = if is_type(m_ty, n_ty) && is_type(n_ty, boolean) {
("llvm.umin.i1", llvm_i1)
("llvm.umin.i8", llvm_i8)
} else if is_type(m_ty, n_ty) && is_type(n_ty, int32) {
("llvm.smin.i32", llvm_i32)
} else if is_type(m_ty, n_ty) && is_type(n_ty, int64) {
@ -1112,7 +1112,7 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
let uint32 = ctx.primitives.uint32;
let uint64 = ctx.primitives.uint64;
let float = ctx.primitives.float;
let llvm_i1 = ctx.ctx.bool_type().as_basic_type_enum();
let llvm_i8 = ctx.ctx.i8_type().as_basic_type_enum();
let llvm_i32 = ctx.ctx.i32_type().as_basic_type_enum();
let llvm_i64 = ctx.ctx.i64_type().as_basic_type_enum();
let llvm_f64 = ctx.ctx.f64_type().as_basic_type_enum();
@ -1122,7 +1122,7 @@ pub fn get_builtins(primitives: &mut (PrimitiveStore, Unifier)) -> BuiltinInfo {
let n_val = args[1].1.clone().to_basic_value_enum(ctx, generator, n_ty)?;
let mut is_type = |a: Type, b: Type| ctx.unifier.unioned(a, b);
let (fun_name, arg_ty) = if is_type(m_ty, n_ty) && is_type(n_ty, boolean) {
("llvm.umax.i1", llvm_i1)
("llvm.umax.i8", llvm_i8)
} else if is_type(m_ty, n_ty) && is_type(n_ty, int32) {
("llvm.smax.i32", llvm_i32)
} else if is_type(m_ty, n_ty) && is_type(n_ty, int64) {

View File

@ -1,114 +0,0 @@
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define min(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a < _b ? _a : _b; \
})
#if __SIZEOF_POINTER__ == 8
#define usize uint64_t
#elif __SIZEOF_POINTER__ == 4
#define usize uint32_t
#elif __SIZEOF_POINTER__ == 2
#define usize uint16_t
#endif
struct cslice {
const void *data;
usize len;
};
void output_int32(const int32_t x, bool newline) {
printf("%d", x);
if (newline) {
fputc('\n', stdout);
}
}
void output_int64(const int64_t x) {
printf("%ld", x);
if (newline) {
fputc('\n', stdout);
}
}
void output_uint32(const uint32_t x) {
printf("%d", x);
if (newline) {
fputc('\n', stdout);
}
}
void output_uint64(const uint64_t x) {
printf("%ld", x);
if (newline) {
fputc('\n', stdout);
}
}
void output_asciiart(const int32_t x) {
const char* chars = " .,-:;i+hHM$*#@ ";
if (x < 0) {
fputc('\n', stdout);
} else {
fputc(chars[x], stdout);
}
if (newline) {
fputc('\n', stdout);
}
}
void output_str(struct cslice *slice, bool newline) {
for (usize i = 0; i < slice->len; ++i) {
fputc(((const char *) slice->data)[i], stdout);
}
if (newline) {
fputc('\n', stdout);
}
}
void output_int32_list(struct cslice *slice) {
fputc('[', stdout);
for (usize i = 0; i < slice->len; ++i) {
if (i == slice->len - 1) {
printf("%d", ((const int32_t *) slice->data)[i]);
} else {
printf("%d, ", ((const int32_t *) slice->data)[i]);
}
}
puts("]");
}
uint32_t __nac3_personality(
__attribute__((unused)) uint32_t state,
__attribute__((unused)) uint32_t exception_object,
__attribute__((unused)) uint32_t context) {
assert(false && "__nac3_personality not implemented");
exit(101);
__builtin_unreachable();
}
uint32_t __nac3_raise(uint32_t state, uint32_t exception_object, uint32_t context) {
printf("__nac3_raise(state: %x, exception_object: %x, context: %x\n", state, exception_object, context);
exit(101);
__builtin_unreachable();
}
extern int32_t run();
int main() {
run();
}

View File

@ -23,60 +23,30 @@ mod cslice {
}
#[no_mangle]
pub extern "C" fn output_int32(x: i32, newline: bool) {
let str = format!("{x}");
if newline {
println!("{str}");
} else {
print!("{str}");
}
pub extern "C" fn output_int32(x: i32) {
println!("{}", x);
}
#[no_mangle]
pub extern "C" fn output_int64(x: i64, newline: bool) {
let str = format!("{x}");
if newline {
println!("{str}");
} else {
print!("{str}");
}
pub extern "C" fn output_int64(x: i64) {
println!("{}", x);
}
#[no_mangle]
pub extern "C" fn output_uint32(x: u32, newline: bool) {
let str = format!("{x}");
if newline {
println!("{str}");
} else {
print!("{str}");
}
pub extern "C" fn output_uint32(x: u32) {
println!("{}", x);
}
#[no_mangle]
pub extern "C" fn output_uint64(x: u64, newline: bool) {
let str = format!("{x}");
if newline {
println!("{str}");
} else {
print!("{str}");
}
pub extern "C" fn output_uint64(x: u64) {
println!("{}", x);
}
#[no_mangle]
pub extern "C" fn output_float64(x: f64, newline: bool) {
pub extern "C" fn output_float64(x: f64) {
// debug output to preserve the digits after the decimal points
// to match python `print` function
let str = format!("{:?}", x);
if newline {
println!("{str}");
} else {
print!("{str}");
}
println!("{:?}", x);
}
#[no_mangle]
@ -90,18 +60,15 @@ pub extern "C" fn output_asciiart(x: i32) {
}
#[no_mangle]
pub extern "C" fn output_str(x: &cslice::CSlice<u8>, newline: bool) {
pub extern "C" fn output_str(x: &cslice::CSlice<u8>) {
for e in x.as_ref().iter() {
print!("{}", char::from(*e));
}
if newline {
println!("");
}
println!();
}
#[no_mangle]
pub extern "C" fn output_int32_list(x: &cslice::CSlice<i32>, newline: bool) {
pub extern "C" fn output_int32_list(x: &cslice::CSlice<i32>) {
print!("[");
let mut it = x.as_ref().iter().peekable();
while let Some(e) = it.next() {
@ -111,11 +78,7 @@ pub extern "C" fn output_int32_list(x: &cslice::CSlice<i32>, newline: bool) {
print!("{}, ", e);
}
}
print!("]");
if newline {
println!("");
}
println!("]");
}
#[no_mangle]
@ -126,10 +89,10 @@ pub extern "C" fn __nac3_personality(_state: u32, _exception_object: u32, _conte
#[no_mangle]
pub extern "C" fn __nac3_raise(state: u32, exception_object: u32, context: u32) -> u32 {
writeln!(io::stderr(),
"__nac3_raise(state: {:#010x}, _exception_object: {:#010x}, _context: {:#010x})",
state,
exception_object,
context
"__nac3_raise(state: {:#010x}, exception_object: {:#010x}, context: {:#010x})",
state,
exception_object,
context
).unwrap();
exit(101);
}

View File

@ -48,12 +48,6 @@ def patch(module):
else:
sys.stdout.write(" .,-:;i+hHM$*#@ "[x])
def output(x, newline: bool=True):
if newline:
print(x)
else:
print(x, end="")
def extern(fun):
name = fun.__name__
if name == "output_asciiart":
@ -67,7 +61,7 @@ def patch(module):
"output_float64",
"output_str",
}:
return output
return print
else:
raise NotImplementedError

View File

@ -1,21 +0,0 @@
#!/usr/bin/env bash
set -e
if [ -z "$1" ]; then
echo "No argument supplied"
exit 1
fi
if [ -e ../../target/release/nac3standalone ]; then
nac3standalone=../../target/release/nac3standalone
else
# used by Nix builds
nac3standalone=../../target/x86_64-unknown-linux-gnu/release/nac3standalone
fi
rm -f *.o *.bc
$nac3standalone --emit-llvm "$@"
gcc -c -std=c11 -Wall -Wextra -pedantic-errors -Werror=pedantic -O3 -o demo.o demo.c
clang -S -Wall -Wextra -O3 -emit-llvm -o irrt.bc ../../nac3core/src/codegen/irrt/irrt.c
lli --extra-object demo.o --extra-module irrt.bc main.bc

View File

@ -0,0 +1,30 @@
# Different cases for using boolean variables in boolean contexts.
# Tests whether all boolean variables (expressed as i8s) are lowered into i1s before used in branching instruction (`br`)
def bfunc(b: bool) -> bool:
return not b
def run() -> int32:
b1 = True
b2 = False
if b1:
pass
if not b2:
pass
while b2:
pass
l = [i for i in range(10) if b2]
b_and = True and False
b_or = True or False
b_and = b1 and b2
b_or = b1 or b2
bfunc(b1)
return 0

View File

@ -1,9 +1,9 @@
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
@extern
def output_int64(x: int64, newline: bool=True):
def output_int64(x: int64):
...

View File

@ -1,5 +1,5 @@
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
def f1(a: int32 = 4):

View File

@ -1,21 +1,21 @@
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
@extern
def output_int64(x: int64, newline: bool=True):
def output_int64(x: int64):
...
@extern
def output_uint32(x: uint32, newline: bool=True):
def output_uint32(x: uint32):
...
@extern
def output_uint64(x: uint64, newline: bool=True):
def output_uint64(x: uint64):
...
@extern
def output_int32_list(x: list[int32], newline: bool=True):
def output_int32_list(x: list[int32]):
...
@extern
@ -23,7 +23,7 @@ def output_asciiart(x: int32):
...
@extern
def output_str(x: str, newline: bool=True):
def output_str(x: str):
...
def test_output_int32():
@ -47,8 +47,7 @@ def test_output_int32_list():
output_int32_list([0, 1, 3, 5, 10])
def test_output_str_family():
output_str("hello ", newline=False)
output_str("world")
output_str("hello world")
def run() -> int32:
test_output_int32()

View File

@ -1,7 +1,7 @@
from __future__ import annotations
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
class A:

View File

@ -1,5 +1,5 @@
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...

View File

@ -0,0 +1,17 @@
@extern
def output_int32(x: int32):
...
@extern
def output_int32_list(x: list[int32]):
...
def run() -> int32:
bl = [True, False]
bl1 = bl[:]
bl1[1:] = [True]
output_int32_list([int32(b) for b in bl1])
output_int32_list([int32(b) for b in bl1])
return 0

View File

@ -1,9 +1,9 @@
@extern
def output_int32_list(x: list[int32], newline: bool=True):
def output_int32_list(x: list[int32]):
...
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
class A:

View File

@ -1,7 +1,7 @@
# For Loop using an increasing range() expression as its iterable
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
def run() -> int32:

View File

@ -1,7 +1,7 @@
# For Loop using a decreasing range() expression as its iterable
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
def run() -> int32:

View File

@ -1,7 +1,7 @@
# For Loop using a list as its iterable
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
def run() -> int32:

View File

@ -1,7 +1,7 @@
# For Loop using an range() expression as its iterable, additionally reassigning the target on each iteration
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
def run() -> int32:

View File

@ -2,15 +2,15 @@
# Taken from https://book.pythontips.com/en/latest/for_-_else.html
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
@extern
def output_float64(x: float, newline: bool=True):
def output_float64(x: float):
...
@extern
def output_str(x: str, newline: bool=True):
def output_str(x: str):
...
def run() -> int32:
@ -18,16 +18,16 @@ def run() -> int32:
for x in range(2, n):
try:
if n % x == 0:
output_int32(n, newline=False)
output_str(" equals ", newline=False)
output_int32(x, newline=False)
output_str(" * ", newline=False)
output_int32(n)
output_str(" equals ")
output_int32(x)
output_str(" * ")
output_float64(n / x)
except: # Assume this is intended to catch x == 0
break
else:
# loop fell through without finding a factor
output_int32(n, newline=False)
output_int32(n)
output_str(" is a prime number")
return 0

View File

@ -1,17 +1,17 @@
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
@extern
def output_uint32(x: uint32, newline: bool=True):
def output_uint32(x: uint32):
...
@extern
def output_int64(x: int64, newline: bool=True):
def output_int64(x: int64):
...
@extern
def output_uint64(x: uint64, newline: bool=True):
def output_uint64(x: uint64):
...
@extern
def output_float64(x: float, newline: bool=True):
def output_float64(x: float):
...

View File

@ -1,19 +1,19 @@
from __future__ import annotations
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
@extern
def output_uint32(x: uint32, newline: bool=True):
def output_uint32(x: uint32):
...
@extern
def output_int64(x: int64, newline: bool=True):
def output_int64(x: int64):
...
@extern
def output_uint64(x: uint64, newline: bool=True):
def output_uint64(x: uint64):
...
@extern
def output_float64(x: float, newline: bool=True):
def output_float64(x: float):
...
def run() -> int32:

View File

@ -1,5 +1,5 @@
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
class A:

View File

@ -1,5 +1,5 @@
@extern
def output_float64(f: float, newline: bool=True):
def output_float64(f: float):
...

View File

@ -1,7 +1,7 @@
from __future__ import annotations
@extern
def output_int32(a: int32, newline: bool=True):
def output_int32(a: int32):
...
class A:

View File

@ -1,9 +1,9 @@
@extern
def output_int32_list(x: list[int32], newline: bool=True):
def output_int32_list(x: list[int32]):
...
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
class A:

View File

@ -1,5 +1,5 @@
@extern
def output_int32(x: int32, newline: bool=True):
def output_int32(x: int32):
...
class A: