core: Add use_demo_lib option

Allows injecting calls to demo library functions for debugging.
This commit is contained in:
David Mak 2024-05-30 13:23:08 +08:00
parent 520e1adc56
commit 4d0d4e0eae
6 changed files with 377 additions and 37 deletions

View File

@ -14,7 +14,7 @@ use inkwell::{
OptimizationLevel,
};
use itertools::Itertools;
use nac3core::codegen::{CodeGenLLVMOptions, CodeGenTargetMachineOptions, gen_func_impl};
use nac3core::codegen::{CodeGenLLVMOptions, CodeGenOptions, CodeGenTargetMachineOptions, gen_func_impl};
use nac3core::toplevel::builtins::get_exn_constructor;
use nac3core::typecheck::typedef::{TypeEnum, Unifier, VarMap};
use nac3parser::{
@ -113,8 +113,8 @@ struct Nac3 {
string_store: Arc<RwLock<HashMap<String, i32>>>,
exception_ids: Arc<RwLock<HashMap<usize, usize>>>,
deferred_eval_store: DeferredEvaluationStore,
/// LLVM-related options for code generation.
llvm_options: CodeGenLLVMOptions,
/// Options for code generation.
codegen_options: CodeGenOptions,
}
create_exception!(nac3artiq, CompileError, exceptions::PyException);
@ -607,7 +607,7 @@ impl Nac3 {
let (registry, handles) = WorkerRegistry::create_workers(
threads,
top_level.clone(),
&self.llvm_options,
&self.codegen_options,
&f
);
registry.add_task(task);
@ -674,13 +674,13 @@ impl Nac3 {
global_option = global.get_next_global();
}
let target_machine = self.llvm_options.target
.create_target_machine(self.llvm_options.opt_level)
let target_machine = self.codegen_options.llvm.target
.create_target_machine(self.codegen_options.llvm.opt_level)
.expect("couldn't create target machine");
let pass_options = PassBuilderOptions::create();
pass_options.set_merge_functions(true);
let passes = format!("default<O{}>", self.llvm_options.opt_level as u32);
let passes = format!("default<O{}>", self.codegen_options.llvm.opt_level as u32);
let result = main.run_passes(passes.as_str(), &target_machine, pass_options);
if let Err(err) = result {
panic!("Failed to run optimization for module `main`: {}", err.to_string());
@ -733,7 +733,7 @@ impl Nac3 {
/// target [isa].
fn get_llvm_target_machine(&self) -> TargetMachine {
Nac3::get_llvm_target_options(self.isa)
.create_target_machine(self.llvm_options.opt_level)
.create_target_machine(self.codegen_options.llvm.opt_level)
.expect("couldn't create target machine")
}
}
@ -908,10 +908,13 @@ impl Nac3 {
string_store: Arc::default(),
exception_ids: Arc::default(),
deferred_eval_store: DeferredEvaluationStore::new(),
llvm_options: CodeGenLLVMOptions {
opt_level: OptimizationLevel::Default,
target: Nac3::get_llvm_target_options(isa),
}
codegen_options: CodeGenOptions {
use_demo_lib: false,
llvm: CodeGenLLVMOptions {
opt_level: OptimizationLevel::Default,
target: Nac3::get_llvm_target_options(isa),
},
},
})
}

View File

@ -738,7 +738,7 @@ pub fn call_numpy_min<'ctx, G: CodeGenerator + ?Sized>(
let n = NDArrayValue::from_ptr_val(n, llvm_usize, None);
let n_sz = irrt::call_ndarray_calc_size(generator, ctx, &n.dim_sizes());
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None {
if ctx.registry.codegen_options.llvm.opt_level == OptimizationLevel::None {
let n_sz_eqz = ctx.builder
.build_int_compare(
IntPredicate::NE,
@ -956,7 +956,7 @@ pub fn call_numpy_max<'ctx, G: CodeGenerator + ?Sized>(
let n = NDArrayValue::from_ptr_val(n, llvm_usize, None);
let n_sz = irrt::call_ndarray_calc_size(generator, ctx, &n.dim_sizes());
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None {
if ctx.registry.codegen_options.llvm.opt_level == OptimizationLevel::None {
let n_sz_eqz = ctx.builder
.build_int_compare(
IntPredicate::NE,

View File

@ -0,0 +1,316 @@
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue, PointerValue};
use itertools::Either;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// Invokes `dbl_nan` in the demo library.
pub fn call_dbl_nan<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>) -> FloatValue<'ctx> {
const FN_NAME: &str = "dbl_nan";
assert!(ctx.registry.codegen_options.use_demo_lib);
let llvm_f64 = ctx.ctx.f64_type();
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"mustprogress",
"nofree",
"norecurse",
"nosync",
"nounwind",
"sspstrong",
"willreturn",
"readnone",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[], "")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Invokes `dbl_inf` in the demo library.
pub fn call_dbl_inf<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>) -> FloatValue<'ctx> {
const FN_NAME: &str = "dbl_inf";
assert!(ctx.registry.codegen_options.use_demo_lib);
let llvm_f64 = ctx.ctx.f64_type();
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"mustprogress",
"nofree",
"norecurse",
"nosync",
"nounwind",
"sspstrong",
"willreturn",
"readnone",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[], "")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Invokes `output_bool` in the demo library.
pub fn call_output_bool<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_bool";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i1 = ctx.ctx.bool_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i1.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i1.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_int32` in the demo library.
pub fn call_output_int32<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_int32";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i32 = ctx.ctx.i32_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i32.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i32.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_int64` in the demo library.
pub fn call_output_int64<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_int64";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i64 = ctx.ctx.i64_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i64.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i64.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_uint32` in the demo library.
pub fn call_output_uint32<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_uint32";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i32 = ctx.ctx.i32_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i32.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i32.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_uint64` in the demo library.
pub fn call_output_uint64<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_uint64";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i64 = ctx.ctx.i64_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i64.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i64.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_float64` in the demo library.
pub fn call_output_float64<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: FloatValue<'ctx>) {
const FN_NAME: &str = "output_float64";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_f64 = ctx.ctx.f64_type();
debug_assert_eq!(value.get_type(), llvm_f64);
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_f64.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_str` in the demo library.
pub fn call_output_str<'ctx>(
generator: &mut dyn CodeGenerator,
ctx: &mut CodeGenContext<'ctx, '_>,
value: PointerValue<'ctx>,
) {
const FN_NAME: &str = "output_str";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_str = ctx.get_llvm_type(generator, ctx.primitives.str).into_pointer_type();
debug_assert_eq!(value.get_type(), llvm_str);
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_str.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}

View File

@ -42,6 +42,7 @@ use std::thread;
pub mod builtin_fns;
pub mod classes;
pub mod concrete_type;
pub mod demo_fns;
pub mod expr;
pub mod extern_fns;
mod generator;
@ -64,6 +65,16 @@ pub struct StaticValueStore {
pub type VarValue<'ctx> = (PointerValue<'ctx>, Option<Arc<dyn StaticValue + Send + Sync>>, i64);
/// Additional options for codegen.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CodeGenOptions {
/// Whether to use the demo library during codegen.
pub use_demo_lib: bool,
/// Options related to LLVM codegen.
pub llvm: CodeGenLLVMOptions,
}
/// Additional options for LLVM during codegen.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CodeGenLLVMOptions {
@ -245,8 +256,8 @@ pub struct WorkerRegistry {
top_level_ctx: Arc<TopLevelContext>,
static_value_store: Arc<Mutex<StaticValueStore>>,
/// LLVM-related options for code generation.
pub llvm_options: CodeGenLLVMOptions,
/// Code generation options.
pub codegen_options: CodeGenOptions,
}
impl WorkerRegistry {
@ -256,7 +267,7 @@ impl WorkerRegistry {
pub fn create_workers<G: CodeGenerator + Send + 'static>(
generators: Vec<Box<G>>,
top_level_ctx: Arc<TopLevelContext>,
llvm_options: &CodeGenLLVMOptions,
codegen_options: &CodeGenOptions,
f: &Arc<WithCall>,
) -> (Arc<WorkerRegistry>, Vec<thread::JoinHandle<()>>) {
let (sender, receiver) = unbounded();
@ -277,7 +288,7 @@ impl WorkerRegistry {
task_count,
wait_condvar,
top_level_ctx,
llvm_options: llvm_options.clone(),
codegen_options: codegen_options.clone(),
});
let mut handles = Vec::new();
@ -382,11 +393,12 @@ impl WorkerRegistry {
let pass_options = PassBuilderOptions::create();
let target_machine = self
.llvm_options
.codegen_options
.llvm
.target
.create_target_machine(self.llvm_options.opt_level)
.unwrap_or_else(|| panic!("could not create target machine from properties {:?}", self.llvm_options.target));
let passes = format!("default<O{}>", self.llvm_options.opt_level as u32);
.create_target_machine(self.codegen_options.llvm.opt_level)
.unwrap_or_else(|| panic!("could not create target machine from properties {:?}", self.codegen_options.llvm.target));
let passes = format!("default<O{}>", self.codegen_options.llvm.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 `{}`: {}",
@ -828,7 +840,7 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte
),
/* directory */ "",
/* producer */ "NAC3",
/* is_optimized */ registry.llvm_options.opt_level != OptimizationLevel::None,
/* is_optimized */ registry.codegen_options.llvm.opt_level != OptimizationLevel::None,
/* compiler command line flags */ "",
/* runtime_ver */ 0,
/* split_name */ "",
@ -863,7 +875,7 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte
/* is_definition */ true,
/* scope_line */ row as u32,
/* flags */ inkwell::debug_info::DIFlags::PUBLIC,
/* is_optimized */ registry.llvm_options.opt_level != OptimizationLevel::None,
/* is_optimized */ registry.codegen_options.llvm.opt_level != OptimizationLevel::None,
);
fn_val.set_subprogram(func_scope);

View File

@ -937,7 +937,7 @@ pub fn ndarray_matmul_2d<'ctx, G: CodeGenerator>(
}
}
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None {
if ctx.registry.codegen_options.llvm.opt_level == OptimizationLevel::None {
let lhs_dim1 = unsafe {
lhs.dim_sizes().get_typed_unchecked(ctx, generator, &llvm_usize.const_int(1, false), None)
};

View File

@ -12,7 +12,7 @@ use std::collections::HashSet;
use nac3core::{
codegen::{
concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions,
concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions, CodeGenOptions,
CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry,
},
symbol_resolver::SymbolResolver,
@ -68,6 +68,11 @@ struct CommandLineArgs {
/// Additional target features to enable/disable, specified using the `+`/`-` prefixes.
#[arg(long)]
target_features: Option<String>,
/// Enables the demo library for use in compiled executables. This requires `demo.o` to be
/// present when linking the executable.
#[arg(long, default_value_t = false)]
fuse_demo_lib: bool,
}
fn handle_typevar_definition(
@ -258,6 +263,7 @@ fn main() {
triple,
mcpu,
target_features,
fuse_demo_lib,
} = cli;
Target::initialize_all(&InitializationConfig::default());
@ -371,14 +377,17 @@ fn main() {
instance_to_stmt[""].clone()
};
let llvm_options = CodeGenLLVMOptions {
opt_level,
target: CodeGenTargetMachineOptions {
triple,
cpu: mcpu,
features: target_features,
reloc_mode: RelocMode::PIC,
..host_target_machine
let codegen_options = CodeGenOptions {
use_demo_lib: fuse_demo_lib,
llvm: CodeGenLLVMOptions {
opt_level,
target: CodeGenTargetMachineOptions {
triple,
cpu: mcpu,
features: target_features,
reloc_mode: RelocMode::PIC,
..host_target_machine
},
},
};
@ -405,7 +414,7 @@ fn main() {
let threads = (0..threads)
.map(|i| Box::new(DefaultCodeGenerator::new(format!("module{i}"), SIZE_T)))
.collect();
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, &llvm_options, &f);
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, &codegen_options, &f);
registry.add_task(task);
registry.wait_tasks_complete(handles);
@ -444,8 +453,8 @@ fn main() {
function_iter = func.get_next_function();
}
let target_machine = llvm_options.target
.create_target_machine(llvm_options.opt_level)
let target_machine = codegen_options.llvm.target
.create_target_machine(codegen_options.llvm.opt_level)
.expect("couldn't create target machine");
let pass_options = PassBuilderOptions::create();