Compare commits

..

10 Commits

Author SHA1 Message Date
David Mak 47d9f8d5c6 meta: Improve documentation for various modified classes 2023-09-20 16:59:56 +08:00
David Mak bc1383e956 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-20 15:14:02 +08:00
David Mak 41ad8a9282 core: Move bitcode verification error message into panic message 2023-09-20 15:14:02 +08:00
David Mak bf443d7704 core: Minor refactor allocate_list 2023-09-20 15:14:02 +08:00
David Mak 960f6c3329 core: Add name to build_gep_and_load 2023-09-20 15:14:02 +08:00
David Mak 35803958ff core: Remove emit_llvm from CodeGenLLVMOptions
We instead output an LLVM bitcode file when the option is specified on
the command-line.
2023-09-20 15:14:02 +08:00
David Mak 3d5942987b standalone: Add ability to execute via lli 2023-09-20 15:14:02 +08:00
David Mak e9ef7a2af5 standalone: Implement __nac3_raise for demo 2023-09-20 15:14:00 +08:00
David Mak 2c89ff0687 core: Replace deprecated _ExtInt with _BitInt 2023-09-20 15:11:54 +08:00
David Mak 1b6e0e386c meta: Update dependencies 2023-09-20 14:59:57 +08:00
8 changed files with 110 additions and 51 deletions

View File

@ -8,7 +8,7 @@ use std::sync::Arc;
use inkwell::{
memory_buffer::MemoryBuffer,
module::{Linkage, Module},
passes::{PassManager, PassManagerBuilder},
passes::PassBuilderOptions,
targets::*,
OptimizationLevel,
};
@ -654,12 +654,16 @@ impl Nac3 {
global_option = global.get_next_global();
}
let builder = PassManagerBuilder::create();
builder.set_optimization_level(OptimizationLevel::Aggressive);
let passes = PassManager::create(());
builder.set_inliner_with_threshold(255);
builder.populate_module_pass_manager(&passes);
passes.run_on(&main);
let target_machine = self.llvm_options.target
.create_target_machine(self.llvm_options.opt_level)
.expect("couldn't create target machine");
let pass_options = PassBuilderOptions::create();
pass_options.set_merge_functions(true);
let result = main.run_passes("default<O3>", &target_machine, pass_options);
if let Err(err) = result {
panic!("Failed to run optimization for module `main`: {}", err.to_string());
}
link_fn(&main)
}

View File

@ -59,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>,
@ -196,6 +198,7 @@ impl<'ctx, 'a> CodeGenContext<'ctx, 'a> {
)
}
/// Generates an LLVM variable for a [constant value][value] with a given [type][ty].
pub fn gen_const(
&mut self,
generator: &mut dyn CodeGenerator,
@ -257,6 +260,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,
@ -301,6 +305,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,
@ -423,6 +428,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,
@ -522,6 +528,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>,
@ -553,6 +560,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>)>,
@ -629,6 +637,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>,
@ -788,6 +797,8 @@ pub fn gen_call<'ctx, 'a, G: CodeGenerator>(
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>,
@ -805,7 +816,8 @@ pub fn destructure_range<'ctx, 'a>(
(start, end, step)
}
/// Allocates a List structure with the given [type][ty] and [length].
/// Allocates a List structure with the given [type][ty] and [length]. The name of the resulting
/// LLVM value is `{name}.addr`, or `list.addr` if [name] is not specified.
///
/// Returns an instance of [PointerValue] pointing to the List structure. The List structure is
/// defined as `type { ty*, size_t }` in LLVM, where the first element stores the pointer to the
@ -855,6 +867,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>,
@ -1024,6 +1037,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>,
@ -1120,6 +1140,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>,
@ -1191,7 +1212,7 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
elements[0].get_type()
};
let length = generator.get_size_type(ctx.ctx).const_int(elements.len() as u64, false);
let arr_str_ptr = allocate_list(generator, ctx, ty, length, Some("list.addr"));
let arr_str_ptr = allocate_list(generator, ctx, ty, length, Some("list"));
let arr_ptr = ctx.build_gep_and_load(arr_str_ptr, &[zero, zero], Some("list.ptr.addr"))
.into_pointer_value();
unsafe {
@ -1631,7 +1652,7 @@ pub fn gen_expr<'ctx, 'a, G: CodeGenerator>(
.into_int_value(),
step,
);
let res_array_ret = allocate_list(generator, ctx, ty, length, Some("ret.addr"));
let res_array_ret = allocate_list(generator, ctx, ty, length, Some("ret"));
let res_ind =
handle_slice_indices(&None, &None, &None, ctx, generator, res_array_ret)?;
list_slice_assignment(

View File

@ -181,7 +181,7 @@ pub trait CodeGenerator {
gen_stmt(self, ctx, stmt)
}
/// Converts the value of [a boolean-like value][bool_value] into an `i1`.
/// See [bool_to_i1].
fn bool_to_i1<'ctx, 'a>(
&self,
ctx: &CodeGenContext<'ctx, 'a>,
@ -190,7 +190,7 @@ pub trait CodeGenerator {
bool_to_i1(&ctx.builder, bool_value)
}
/// Converts the value of [a boolean-like value][bool_value] into an `i8`.
/// See [bool_to_i8].
fn bool_to_i8<'ctx, 'a>(
&self,
ctx: &CodeGenContext<'ctx, 'a>,

View File

@ -16,7 +16,7 @@ use inkwell::{
builder::Builder,
context::Context,
module::Module,
passes::{PassManager, PassManagerBuilder},
passes::PassBuilderOptions,
targets::{CodeModel, RelocMode, Target, TargetMachine, TargetTriple},
types::{AnyType, BasicType, BasicTypeEnum},
values::{BasicValueEnum, FunctionValue, IntValue, PhiValue, PointerValue},
@ -33,7 +33,6 @@ use std::sync::{
Arc,
};
use std::thread;
use lazy_static::lazy_static;
pub mod concrete_type;
pub mod expr;
@ -55,12 +54,6 @@ pub struct StaticValueStore {
pub type VarValue<'ctx> = (PointerValue<'ctx>, Option<Arc<dyn StaticValue + Send + Sync>>, i64);
lazy_static!(
// HACK: The Mutex is a work-around for issue
// https://git.m-labs.hk/M-Labs/nac3/issues/275
static ref PASSES_INIT_LOCK: Mutex<AtomicBool> = Mutex::new(AtomicBool::new(true));
);
/// Additional options for LLVM during codegen.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CodeGenLLVMOptions {
@ -303,23 +296,11 @@ impl WorkerRegistry {
context.i32_type().const_int(4, false),
);
let passes = PassManager::create(&module);
// HACK: This critical section is a work-around for issue
// https://git.m-labs.hk/M-Labs/nac3/issues/275
{
let _data = PASSES_INIT_LOCK.lock();
let pass_builder = PassManagerBuilder::create();
pass_builder.set_optimization_level(self.llvm_options.opt_level);
pass_builder.populate_function_pass_manager(&passes);
}
let mut errors = HashSet::new();
while let Some(task) = self.receiver.recv().unwrap() {
match gen_func(&context, generator, self, builder, module, task) {
Ok(result) => {
builder = result.0;
passes.run_on(&result.2);
module = result.1;
}
Err((old_builder, e)) => {
@ -342,6 +323,19 @@ impl WorkerRegistry {
panic!("{}", err.to_string())
}
let pass_options = PassBuilderOptions::create();
let target_machine = self.llvm_options.target.create_target_machine(
self.llvm_options.opt_level
).expect(format!("could not create target machine from properties {:?}", self.llvm_options.target).as_str());
let passes = format!("default<O{}>", self.llvm_options.opt_level as u32);
let result = module.run_passes(passes.as_str(), &target_machine, pass_options);
if let Err(err) = result {
panic!("Failed to run optimization for module `{}`: {}",
module.get_name().to_str().unwrap(),
err.to_string());
}
f.run(&module);
let mut lock = self.task_count.lock();
*lock += 1;
@ -363,7 +357,7 @@ pub struct CodeGenTask {
/// 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
/// 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,
@ -478,7 +472,7 @@ 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
/// 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
@ -517,6 +511,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,
@ -841,6 +836,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,
@ -858,7 +862,7 @@ pub fn gen_func<'ctx, G: CodeGenerator>(
})
}
/// Converts the value of [a boolean-like value][bool_value] into an `i1`.
/// 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(
@ -872,7 +876,7 @@ fn bool_to_i1<'ctx>(builder: &Builder<'ctx>, bool_value: IntValue<'ctx>) -> IntV
}
}
/// Converts the value of [a boolean-like value][bool_value] into an `i8`.
/// Converts the value of a boolean-like value `bool_value` into an `i8`.
fn bool_to_i8<'ctx>(
builder: &Builder<'ctx>,
ctx: &'ctx Context,

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>,
@ -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>,
@ -535,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>,
@ -632,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>,
@ -683,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>,
@ -1005,6 +1026,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>,
@ -1014,6 +1036,7 @@ 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>,
@ -1062,6 +1085,7 @@ pub fn gen_return<'ctx, 'a, G: CodeGenerator>(
Ok(())
}
/// See [CodeGenerator::gen_stmt].
pub fn gen_stmt<'ctx, 'a, G: CodeGenerator>(
generator: &mut G,
ctx: &mut CodeGenContext<'ctx, 'a>,
@ -1153,6 +1177,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

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

@ -79,12 +79,12 @@ 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 {
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
state,
exception_object,
context
).unwrap();
exit(101);
}

View File

@ -1,7 +1,7 @@
use clap::Parser;
use inkwell::{
memory_buffer::MemoryBuffer,
passes::{PassManager, PassManagerBuilder},
passes::PassBuilderOptions,
targets::*,
OptimizationLevel,
};
@ -364,16 +364,17 @@ fn main() {
function_iter = func.get_next_function();
}
let builder = PassManagerBuilder::create();
builder.set_optimization_level(OptimizationLevel::Aggressive);
let passes = PassManager::create(());
builder.set_inliner_with_threshold(255);
builder.populate_module_pass_manager(&passes);
passes.run_on(&main);
let target_machine = llvm_options.target
.create_target_machine(llvm_options.opt_level)
.expect("couldn't create target machine");
let pass_options = PassBuilderOptions::create();
pass_options.set_merge_functions(true);
let result = main.run_passes("default<O3>", &target_machine, pass_options);
if let Err(err) = result {
panic!("Failed to run optimization for module `main`: {}", err.to_string());
}
target_machine
.write_to_file(&main, FileType::Object, Path::new("module.o"))
.expect("couldn't write module to file");