diff --git a/Cargo.lock b/Cargo.lock index f8a1ec0..6da76f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,6 +462,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -621,6 +630,7 @@ dependencies = [ "inkwell", "insta", "itertools 0.12.1", + "itertools 0.13.0", "nac3parser", "parking_lot", "rayon", diff --git a/nac3artiq/src/lib.rs b/nac3artiq/src/lib.rs index f8f029f..d26a51b 100644 --- a/nac3artiq/src/lib.rs +++ b/nac3artiq/src/lib.rs @@ -28,8 +28,9 @@ use pyo3::create_exception; use parking_lot::{Mutex, RwLock}; use nac3core::{ - codegen::irrt::load_irrt, codegen::{concrete_type::ConcreteTypeStore, CodeGenTask, WithCall, WorkerRegistry}, + codegen::irrt::load_irrt, + codegen::tracert::TraceRuntimeConfig, symbol_resolver::SymbolResolver, toplevel::{ composer::{ComposerConfig, TopLevelComposer}, @@ -608,6 +609,7 @@ impl Nac3 { threads, top_level.clone(), &self.llvm_options, + &TraceRuntimeConfig::default(), &f ); registry.add_task(task); diff --git a/nac3core/Cargo.toml b/nac3core/Cargo.toml index dc81b33..ff3d645 100644 --- a/nac3core/Cargo.toml +++ b/nac3core/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" authors = ["M-Labs"] edition = "2021" +[features] +tracing = [] + [dependencies] itertools = "0.12" crossbeam = "0.8" @@ -23,4 +26,5 @@ indoc = "2.0" insta = "=1.11.0" [build-dependencies] +itertools = "0.13" regex = "1.10" diff --git a/nac3core/build.rs b/nac3core/build.rs index 91060f9..011f92a 100644 --- a/nac3core/build.rs +++ b/nac3core/build.rs @@ -1,4 +1,3 @@ -use regex::Regex; use std::{ env, fs::File, @@ -6,17 +5,29 @@ use std::{ path::Path, process::{Command, Stdio}, }; +use std::ffi::OsStr; -fn main() { - const FILE: &str = "src/codegen/irrt/irrt.c"; +use itertools::Itertools; +use regex::Regex; +/// Extracts the extension-less filename from a [`Path`]. +fn path_to_extless_filename(path: &Path) -> &str { + path.file_name() + .map(Path::new) + .and_then(Path::file_stem) + .and_then(OsStr::to_str) + .unwrap() +} + +/// Compiles a source C file into LLVM bitcode. +fn compile_file_to_ir(path: &Path, filename_without_ext: &str) { /* * HACK: Sadly, clang doesn't let us emit generic LLVM bitcode. * Compiling for WASM32 and filtering the output with regex is the closest we can get. */ let flags: &[&str] = &[ "--target=wasm32", - FILE, + path.to_str().unwrap(), "-fno-discard-value-names", match env::var("PROFILE").as_deref() { Ok("debug") => "-O0", @@ -31,7 +42,7 @@ fn main() { "-", ]; - println!("cargo:rerun-if-changed={FILE}"); + println!("cargo:rerun-if-changed={}", path.to_str().unwrap()); let out_dir = env::var("OUT_DIR").unwrap(); let out_path = Path::new(&out_dir); @@ -44,8 +55,7 @@ fn main() { }) .unwrap(); - // https://github.com/rust-lang/regex/issues/244 - let output = std::str::from_utf8(&output.stdout).unwrap().replace("\r\n", "\n"); + let output = std::str::from_utf8(&output.stdout).unwrap(); let mut filtered_output = String::with_capacity(output.len()); let regex_filter = Regex::new(r"(?ms:^define.*?\}$)|(?m:^declare.*?$)").unwrap(); @@ -61,18 +71,36 @@ fn main() { println!("cargo:rerun-if-env-changed=DEBUG_DUMP_IRRT"); if env::var("DEBUG_DUMP_IRRT").is_ok() { - let mut file = File::create(out_path.join("irrt.ll")).unwrap(); + let mut file = File::create(out_path.join(format!("{filename_without_ext}.ll"))).unwrap(); file.write_all(output.as_bytes()).unwrap(); - let mut file = File::create(out_path.join("irrt-filtered.ll")).unwrap(); + let mut file = File::create(out_path.join(format!("{filename_without_ext}-filtered.ll"))).unwrap(); file.write_all(filtered_output.as_bytes()).unwrap(); } let mut llvm_as = Command::new("llvm-as-irrt") .stdin(Stdio::piped()) .arg("-o") - .arg(out_path.join("irrt.bc")) + .arg(out_path.join(format!("{filename_without_ext}.bc"))) .spawn() .unwrap(); llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap(); assert!(llvm_as.wait().unwrap().success()); } + +fn main() { + const IRRT_SOURCE_PATHS: &[&str] = &[ + "src/codegen/irrt/irrt.c", + "src/codegen/tracert/tracert.c", + ]; + + assert!(IRRT_SOURCE_PATHS.iter() + .map(Path::new) + .map(path_to_extless_filename) + .all_unique()); + + for path in IRRT_SOURCE_PATHS { + let path = Path::new(path); + + compile_file_to_ir(path, path_to_extless_filename(path)) + } +} diff --git a/nac3core/src/codegen/mod.rs b/nac3core/src/codegen/mod.rs index 06b24bf..bb2e057 100644 --- a/nac3core/src/codegen/mod.rs +++ b/nac3core/src/codegen/mod.rs @@ -49,12 +49,17 @@ pub mod irrt; pub mod llvm_intrinsics; pub mod numpy; pub mod stmt; +pub mod tracert; #[cfg(test)] mod test; use concrete_type::{ConcreteType, ConcreteTypeEnum, ConcreteTypeStore}; pub use generator::{CodeGenerator, DefaultCodeGenerator}; +use tracert::TraceRuntimeConfig; + +#[cfg(feature = "tracing")] +use crate::codegen::tracert::TraceRuntimeState; #[derive(Default)] pub struct StaticValueStore { @@ -199,6 +204,9 @@ pub struct CodeGenContext<'ctx, 'a> { /// See [need_sret]. pub need_sret: bool, + #[cfg(feature = "tracing")] + pub tracert_state: Option, + /// The current source location. pub current_loc: Location, } @@ -247,6 +255,9 @@ pub struct WorkerRegistry { /// LLVM-related options for code generation. pub llvm_options: CodeGenLLVMOptions, + + #[cfg(feature = "tracing")] + tracert_config: TraceRuntimeConfig, } impl WorkerRegistry { @@ -257,6 +268,7 @@ impl WorkerRegistry { generators: Vec>, top_level_ctx: Arc, llvm_options: &CodeGenLLVMOptions, + _tracert_config: &TraceRuntimeConfig, f: &Arc, ) -> (Arc, Vec>) { let (sender, receiver) = unbounded(); @@ -278,6 +290,8 @@ impl WorkerRegistry { wait_condvar, top_level_ctx, llvm_options: llvm_options.clone(), + #[cfg(feature = "tracing")] + tracert_config: _tracert_config.clone(), }); let mut handles = Vec::new(); @@ -889,6 +903,12 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte unifier, static_value_store, need_sret: has_sret, + #[cfg(feature = "tracing")] + tracert_state: if registry.tracert_config.enabled_tags.is_empty() { + None + } else { + Some(TraceRuntimeState::create(registry.tracert_config.clone())) + }, current_loc: Location::default(), debug_info: (dibuilder, compile_unit, func_scope.as_debug_info_scope()), }; diff --git a/nac3core/src/codegen/test.rs b/nac3core/src/codegen/test.rs index 1810a18..1b93952 100644 --- a/nac3core/src/codegen/test.rs +++ b/nac3core/src/codegen/test.rs @@ -1,7 +1,8 @@ use crate::{ codegen::{ concrete_type::ConcreteTypeStore, CodeGenContext, CodeGenLLVMOptions, - CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry, + CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, tracert::TraceRuntimeConfig, + WithCall, WorkerRegistry, }, symbol_resolver::{SymbolResolver, ValueEnum}, toplevel::{ @@ -230,6 +231,7 @@ fn test_primitives() { threads, top_level, &llvm_options, + &TraceRuntimeConfig::default(), &f ); registry.add_task(task); @@ -420,6 +422,7 @@ fn test_simple_call() { threads, top_level, &llvm_options, + &TraceRuntimeConfig::default(), &f ); registry.add_task(task); diff --git a/nac3core/src/codegen/tracert/mod.rs b/nac3core/src/codegen/tracert/mod.rs new file mode 100644 index 0000000..ddc1656 --- /dev/null +++ b/nac3core/src/codegen/tracert/mod.rs @@ -0,0 +1,144 @@ +use std::iter::once; +use inkwell::AddressSpace; +use inkwell::attributes::{Attribute, AttributeLoc}; + +use inkwell::context::Context; +use inkwell::memory_buffer::MemoryBuffer; +use inkwell::module::Module; +use inkwell::values::{BasicValue, BasicValueEnum, CallSiteValue, IntValue, PointerValue}; +use itertools::{Either, Itertools}; +use crate::codegen::CodeGenContext; + +#[derive(Eq, Clone, PartialEq)] +pub struct TraceRuntimeConfig { + pub enabled_tags: Vec, +} + +impl Default for TraceRuntimeConfig { + fn default() -> Self { + TraceRuntimeConfig { + enabled_tags: Vec::new(), + } + } +} + +#[cfg(feature = "tracing")] +#[derive(Eq, Clone, PartialEq)] +pub struct TraceRuntimeState { + config: TraceRuntimeConfig, + indent: usize, +} + +#[cfg(feature = "tracing")] +impl TraceRuntimeState { + pub fn create(config: TraceRuntimeConfig) -> TraceRuntimeState { + TraceRuntimeState { + config, + indent: 0, + } + } +} + +#[must_use] +pub fn load_tracert<'ctx>(ctx: &'ctx Context, config: &TraceRuntimeConfig) -> Option> { + #[cfg(feature = "tracing")] + { + if !config.enabled_tags.is_empty() { + let bitcode_buf = MemoryBuffer::create_from_memory_range( + include_bytes!(concat!(env!("OUT_DIR"), "/tracert.bc")), + "tracert_bitcode_buffer", + ); + let module = Module::parse_bitcode_from_buffer(&bitcode_buf, ctx).unwrap(); + + return Some(module) + } + } + + None +} + +fn call_printf<'ctx>( + ctx: &CodeGenContext<'ctx, '_>, + format: PointerValue<'ctx>, + args: &[BasicValueEnum<'ctx>], +) -> IntValue<'ctx> { + const FN_NAME: &str = "printf"; + + let llvm_i8 = ctx.ctx.i8_type(); + let llvm_i32 = ctx.ctx.i32_type(); + let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default()); + + let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| { + let fn_type = llvm_i32.fn_type(&[llvm_pi8.into()], true); + let func = ctx.module.add_function(FN_NAME, fn_type, None); + for attr in ["nofree", "nounwind"] { + func.add_attribute( + AttributeLoc::Function, + ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0) + ); + } + + func + }); + + let fn_args = once(&format.as_basic_value_enum()).chain(args) + .cloned() + .map(BasicValueEnum::into) + .collect_vec(); + + ctx.builder + .build_call(extern_fn, fn_args.as_slice(), "") + .map(CallSiteValue::try_as_basic_value) + .map(|v| v.map_left(BasicValueEnum::into_int_value)) + .map(Either::unwrap_left) + .unwrap() +} + +// TODO: Might need to redesign how trace logging should be done + +pub fn trace_log<'ctx>( + ctx: &mut CodeGenContext<'ctx, '_>, + tag: &'static str, + format: &'static str, + args: &[BasicValueEnum<'ctx>], +) { + #[cfg(feature = "tracing")] + { + if let None = ctx.tracert_state { + return + } + + // TODO: Add indentation + let str = format!("[TRACING] {tag} - {format}\n\0"); + + let pformat = ctx.builder.build_global_string_ptr(&str, "") + .map(|v| v.as_basic_value_enum()) + .map(BasicValueEnum::into_pointer_value) + .unwrap(); + call_printf(ctx, pformat, args); + } +} + +pub fn trace_push_level<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>) { + #[cfg(feature = "tracing")] + { + if let Some(tracert_state) = &mut ctx.tracert_state { + debug_assert!(tracert_state.indent < usize::MAX); + if tracert_state.indent < usize::MAX { + tracert_state.indent += 1 + } + } + } +} + +pub fn trace_pop_level<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>) { + #[cfg(feature = "tracing")] + { + if let Some(tracert_state) = &mut ctx.tracert_state { + debug_assert!(tracert_state.indent > 0); + if tracert_state.indent > 0 { + tracert_state.indent -= 1 + } + } + } +} \ No newline at end of file diff --git a/nac3core/src/codegen/tracert/tracert.c b/nac3core/src/codegen/tracert/tracert.c new file mode 100644 index 0000000..dc1813d --- /dev/null +++ b/nac3core/src/codegen/tracert/tracert.c @@ -0,0 +1,4 @@ +// stdio.h +int printf(const char *format, ...); + + diff --git a/nac3standalone/Cargo.toml b/nac3standalone/Cargo.toml index a55a26b..c2ed0b1 100644 --- a/nac3standalone/Cargo.toml +++ b/nac3standalone/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" authors = ["M-Labs"] edition = "2021" +[features] +tracing = ["nac3core/tracing"] + [dependencies] parking_lot = "0.12" nac3parser = { path = "../nac3parser" } diff --git a/nac3standalone/src/main.rs b/nac3standalone/src/main.rs index 6c4b4ab..ee949c6 100644 --- a/nac3standalone/src/main.rs +++ b/nac3standalone/src/main.rs @@ -13,7 +13,8 @@ use std::collections::HashSet; use nac3core::{ codegen::{ concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions, - CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry, + CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, + tracert::{load_tracert, TraceRuntimeConfig}, WithCall, WorkerRegistry, }, symbol_resolver::SymbolResolver, toplevel::{ @@ -68,6 +69,9 @@ struct CommandLineArgs { /// Additional target features to enable/disable, specified using the `+`/`-` prefixes. #[arg(long)] target_features: Option, + + #[arg(long)] + trace: Vec, } fn handle_typevar_definition( @@ -258,6 +262,7 @@ fn main() { triple, mcpu, target_features, + trace, } = cli; Target::initialize_all(&InitializationConfig::default()); @@ -289,6 +294,9 @@ fn main() { // The default behavior for -O where n>3 defaults to O3 for both Clang and GCC _ => OptimizationLevel::Aggressive, }; + let tracert_config = TraceRuntimeConfig { + enabled_tags: trace, + }; let program = match fs::read_to_string(file_name.clone()) { Ok(program) => program, @@ -405,7 +413,13 @@ 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, + &llvm_options, + &tracert_config, + &f, + ); registry.add_task(task); registry.wait_tasks_complete(handles); @@ -436,6 +450,14 @@ fn main() { } main.link_in_module(irrt).unwrap(); + if let Some(tracert) = load_tracert(&context, &tracert_config) { + if emit_llvm { + tracert.write_bitcode_to_path(Path::new("tracert.bc")); + } + + main.link_in_module(tracert).unwrap(); + } + let mut function_iter = main.get_first_function(); while let Some(func) = function_iter { if func.count_basic_blocks() > 0 && func.get_name().to_str().unwrap() != "run" {