From 4d0d4e0eaefc51a7db69cc7a6ca136c069a6a2f1 Mon Sep 17 00:00:00 2001 From: David Mak Date: Thu, 30 May 2024 13:23:08 +0800 Subject: [PATCH] core: Add use_demo_lib option Allows injecting calls to demo library functions for debugging. --- nac3artiq/src/lib.rs | 27 +-- nac3core/src/codegen/builtin_fns.rs | 4 +- nac3core/src/codegen/demo_fns.rs | 316 ++++++++++++++++++++++++++++ nac3core/src/codegen/mod.rs | 32 ++- nac3core/src/codegen/numpy.rs | 2 +- nac3standalone/src/main.rs | 33 +-- 6 files changed, 377 insertions(+), 37 deletions(-) create mode 100644 nac3core/src/codegen/demo_fns.rs diff --git a/nac3artiq/src/lib.rs b/nac3artiq/src/lib.rs index f8f029ff..b98cd5ec 100644 --- a/nac3artiq/src/lib.rs +++ b/nac3artiq/src/lib.rs @@ -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>>, exception_ids: Arc>>, 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", self.llvm_options.opt_level as u32); + let passes = format!("default", 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), + }, + }, }) } diff --git a/nac3core/src/codegen/builtin_fns.rs b/nac3core/src/codegen/builtin_fns.rs index 1fbfd712..a5eb1325 100644 --- a/nac3core/src/codegen/builtin_fns.rs +++ b/nac3core/src/codegen/builtin_fns.rs @@ -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, diff --git a/nac3core/src/codegen/demo_fns.rs b/nac3core/src/codegen/demo_fns.rs new file mode 100644 index 00000000..019b1d60 --- /dev/null +++ b/nac3core/src/codegen/demo_fns.rs @@ -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(); + } +} \ No newline at end of file diff --git a/nac3core/src/codegen/mod.rs b/nac3core/src/codegen/mod.rs index 06b24bff..59855651 100644 --- a/nac3core/src/codegen/mod.rs +++ b/nac3core/src/codegen/mod.rs @@ -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>, 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, static_value_store: Arc>, - /// 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( generators: Vec>, top_level_ctx: Arc, - llvm_options: &CodeGenLLVMOptions, + codegen_options: &CodeGenOptions, f: &Arc, ) -> (Arc, Vec>) { 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", 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", 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); diff --git a/nac3core/src/codegen/numpy.rs b/nac3core/src/codegen/numpy.rs index f22c721e..d676755d 100644 --- a/nac3core/src/codegen/numpy.rs +++ b/nac3core/src/codegen/numpy.rs @@ -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) }; diff --git a/nac3standalone/src/main.rs b/nac3standalone/src/main.rs index 6c4b4aba..b6f849e7 100644 --- a/nac3standalone/src/main.rs +++ b/nac3standalone/src/main.rs @@ -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, + + /// 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();