diff --git a/cli/src/repl/gen.rs b/cli/src/repl/gen.rs index 5a30b723fe..49d47e54a3 100644 --- a/cli/src/repl/gen.rs +++ b/cli/src/repl/gen.rs @@ -8,7 +8,7 @@ use roc_can::builtins::builtin_defs_map; use roc_collections::all::{MutMap, MutSet}; use roc_fmt::annotation::Formattable; use roc_fmt::annotation::{Newlines, Parens}; -use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel}; +use roc_gen::llvm::build::OptLevel; use roc_gen::llvm::externs::add_default_roc_externs; use roc_load::file::LoadingProblem; use roc_parse::parser::SyntaxError; @@ -68,7 +68,7 @@ pub fn gen_and_eval<'a>( use roc_load::file::MonomorphizedModule; let MonomorphizedModule { - mut procedures, + procedures, interns, exposed_to_host, mut subs, @@ -185,63 +185,10 @@ pub fn gen_and_eval<'a>( exposed_to_host: MutSet::default(), }; - let mut layout_ids = roc_mono::layout::LayoutIds::default(); - let mut headers = Vec::with_capacity(procedures.len()); - - // Add all the Proc headers to the module. - // We have to do this in a separate pass first, - // because their bodies may reference each other. - let mut scope = roc_gen::llvm::build::Scope::default(); - for ((symbol, layout), proc) in procedures.drain() { - let fn_val = build_proc_header(&env, &mut layout_ids, symbol, layout, &proc); - - if proc.args.is_empty() { - // this is a 0-argument thunk, i.e. a top-level constant definition - // it must be in-scope everywhere in the module! - scope.insert_top_level_thunk(symbol, arena.alloc(layout), fn_val); - } - - headers.push((proc, fn_val)); - } - - // Build each proc using its header info. - for (proc, fn_val) in headers { - let mut current_scope = scope.clone(); - - // only have top-level thunks for this proc's module in scope - // this retain is not needed for correctness, but will cause less confusion when debugging - let home = proc.name.module_id(); - current_scope.retain_top_level_thunks_for_module(home); - - build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val); - - // call finalize() before any code generation/verification - env.dibuilder.finalize(); - - if fn_val.verify(true) { - function_pass.run_on(&fn_val); - } else { - let mode = "NON-OPTIMIZED"; - - eprintln!( - "\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n", - fn_val.get_name().to_str().unwrap(), - mode, - ); - - fn_val.print_to_stderr(); - - panic!( - "The preceding code was from {:?}, which failed LLVM verification in {} build.", - fn_val.get_name().to_str().unwrap(), - mode, - ); - } - } - - let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function( + let (main_fn_name, main_fn) = roc_gen::llvm::build::build_procedures_return_main( &env, - &mut layout_ids, + opt_level, + procedures, main_fn_symbol, main_fn_layout, ); diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index afdfcbaf38..4d087664c0 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -3,9 +3,8 @@ use bumpalo::Bump; use inkwell::context::Context; use inkwell::targets::{CodeModel, FileType, RelocMode}; pub use roc_gen::llvm::build::FunctionIterator; -use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope}; +use roc_gen::llvm::build::{module_from_builtins, OptLevel}; use roc_load::file::MonomorphizedModule; -use roc_mono::layout::LayoutIds; use std::path::{Path, PathBuf}; use std::time::{Duration, SystemTime}; use target_lexicon::Triple; @@ -121,7 +120,7 @@ pub fn gen_from_mono_module( let builder = context.create_builder(); let (dibuilder, compile_unit) = roc_gen::llvm::build::Env::new_debug_info(module); - let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level); + let (mpm, _fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level); // Compile and add all the Procs before adding main let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; @@ -138,55 +137,7 @@ pub fn gen_from_mono_module( exposed_to_host: loaded.exposed_to_host.keys().copied().collect(), }; - // Populate Procs further and get the low-level Expr from the canonical Expr - let mut headers = Vec::with_capacity(loaded.procedures.len()); - - // Add all the Proc headers to the module. - // We have to do this in a separate pass first, - // because their bodies may reference each other. - let mut layout_ids = LayoutIds::default(); - - let mut scope = Scope::default(); - for ((symbol, layout), proc) in loaded.procedures { - let fn_val = build_proc_header(&env, &mut layout_ids, symbol, layout, &proc); - - if proc.args.is_empty() { - // this is a 0-argument thunk, i.e. a top-level constant definition - // it must be in-scope everywhere in the module! - scope.insert_top_level_thunk(symbol, arena.alloc(layout), fn_val); - } - - headers.push((proc, fn_val)); - } - - // Build each proc using its header info. - for (proc, fn_val) in headers { - // NOTE: This is here to be uncommented in case verification fails. - // (This approach means we don't have to defensively clone name here.) - // - // println!("\n\nBuilding and then verifying function {:?}\n\n", proc); - build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val); - - // call finalize() before any code generation/verification - env.dibuilder.finalize(); - - if fn_val.verify(true) { - fpm.run_on(&fn_val); - } else { - fn_val.print_to_stderr(); - - // write the ll code to a file, so we can modify it - env.module.print_to_file(&app_ll_file).unwrap(); - - // env.module.print_to_stderr(); - // NOTE: If this fails, uncomment the above println to debug. - panic!( - r"Non-main function {:?} failed LLVM verification. I wrote the full LLVM IR to {:?}", - fn_val.get_name(), - app_ll_file, - ); - } - } + roc_gen::llvm::build::build_procedures(&env, opt_level, loaded.procedures); env.dibuilder.finalize(); diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 921f0fa741..01e092a8d3 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -44,7 +44,7 @@ use inkwell::values::{ use inkwell::OptimizationLevel; use inkwell::{AddressSpace, IntPredicate}; use roc_builtins::bitcode; -use roc_collections::all::{ImMap, MutSet}; +use roc_collections::all::{ImMap, MutMap, MutSet}; use roc_module::ident::TagName; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -2985,7 +2985,113 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>( wrapper_function } -pub fn build_proc_header<'a, 'ctx, 'env>( +pub fn build_proc_headers<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), roc_mono::ir::Proc<'a>>, + scope: &mut Scope<'a, 'ctx>, + // alias_analysis_solutions: AliasAnalysisSolutions, +) -> std::vec::Vec<(roc_mono::ir::Proc<'a>, FunctionValue<'ctx>)> { + // Populate Procs further and get the low-level Expr from the canonical Expr + let mut headers = std::vec::Vec::with_capacity(procedures.len()); + for ((symbol, layout), proc) in procedures { + let fn_val = build_proc_header(env, layout_ids, symbol, layout, &proc); + + if proc.args.is_empty() { + // this is a 0-argument thunk, i.e. a top-level constant definition + // it must be in-scope everywhere in the module! + scope.insert_top_level_thunk(symbol, env.arena.alloc(layout), fn_val); + } + + headers.push((proc, fn_val)); + } + + headers +} + +pub fn build_procedures<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + opt_level: OptLevel, + procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), roc_mono::ir::Proc<'a>>, + // alias_analysis_solutions: AliasAnalysisSolutions, +) { + build_procedures_help(env, opt_level, procedures, None); +} + +pub fn build_procedures_return_main<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + opt_level: OptLevel, + procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), roc_mono::ir::Proc<'a>>, + // alias_analysis_solutions: AliasAnalysisSolutions, + main_fn_symbol: Symbol, + main_fn_layout: TopLevelFunctionLayout<'a>, +) -> (&'static str, FunctionValue<'ctx>) { + build_procedures_help( + env, + opt_level, + procedures, + Some((main_fn_symbol, main_fn_layout)), + ) + .unwrap() +} + +fn build_procedures_help<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + opt_level: OptLevel, + procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), roc_mono::ir::Proc<'a>>, + main_data: Option<(Symbol, TopLevelFunctionLayout<'a>)>, +) -> Option<(&'static str, FunctionValue<'ctx>)> { + let mut layout_ids = roc_mono::layout::LayoutIds::default(); + let mut scope = Scope::default(); + + // Add all the Proc headers to the module. + // We have to do this in a separate pass first, + // because their bodies may reference each other. + let headers = build_proc_headers(env, &mut layout_ids, procedures, &mut scope); + + let (_, function_pass) = construct_optimization_passes(env.module, opt_level); + + for (proc, fn_val) in headers { + let mut current_scope = scope.clone(); + + // only have top-level thunks for this proc's module in scope + // this retain is not needed for correctness, but will cause less confusion when debugging + let home = proc.name.module_id(); + current_scope.retain_top_level_thunks_for_module(home); + + build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val); + + // call finalize() before any code generation/verification + env.dibuilder.finalize(); + + if fn_val.verify(true) { + function_pass.run_on(&fn_val); + } else { + let mode = "NON-OPTIMIZED"; + + eprintln!( + "\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n", + fn_val.get_name().to_str().unwrap(), + mode, + ); + + fn_val.print_to_stderr(); + // module.print_to_stderr(); + + panic!( + "The preceding code was from {:?}, which failed LLVM verification in {} build.", + fn_val.get_name().to_str().unwrap(), + mode, + ); + } + } + + main_data.map(|(main_fn_symbol, main_fn_layout)| { + promote_to_main_function(env, &mut layout_ids, main_fn_symbol, main_fn_layout) + }) +} + +fn build_proc_header<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, layout_ids: &mut LayoutIds<'a>, symbol: Symbol, diff --git a/compiler/test_gen/src/helpers/eval.rs b/compiler/test_gen/src/helpers/eval.rs index aa7e6b786b..14dffa71d5 100644 --- a/compiler/test_gen/src/helpers/eval.rs +++ b/compiler/test_gen/src/helpers/eval.rs @@ -36,7 +36,6 @@ pub fn helper<'a>( ignore_problems: bool, context: &'a inkwell::context::Context, ) -> (&'static str, String, Library) { - use roc_gen::llvm::build::{build_proc, build_proc_header, Scope}; use std::path::{Path, PathBuf}; let filename = PathBuf::from("Test.roc"); @@ -79,7 +78,7 @@ pub fn helper<'a>( use roc_load::file::MonomorphizedModule; let MonomorphizedModule { - mut procedures, + procedures, interns, exposed_to_host, .. @@ -236,63 +235,10 @@ pub fn helper<'a>( exposed_to_host: MutSet::default(), }; - let mut layout_ids = roc_mono::layout::LayoutIds::default(); - let mut headers = Vec::with_capacity(procedures.len()); - - // Add all the Proc headers to the module. - // We have to do this in a separate pass first, - // because their bodies may reference each other. - let mut scope = Scope::default(); - for ((symbol, layout), proc) in procedures.drain() { - let fn_val = build_proc_header(&env, &mut layout_ids, symbol, layout, &proc); - - if proc.args.is_empty() { - // this is a 0-argument thunk, i.e. a top-level constant definition - // it must be in-scope everywhere in the module! - scope.insert_top_level_thunk(symbol, arena.alloc(layout), fn_val); - } - - headers.push((proc, fn_val)); - } - - // Build each proc using its header info. - for (proc, fn_val) in headers { - let mut current_scope = scope.clone(); - - // only have top-level thunks for this proc's module in scope - // this retain is not needed for correctness, but will cause less confusion when debugging - let home = proc.name.module_id(); - current_scope.retain_top_level_thunks_for_module(home); - - build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val); - - // call finalize() before any code generation/verification - env.dibuilder.finalize(); - - if fn_val.verify(true) { - function_pass.run_on(&fn_val); - } else { - let mode = "NON-OPTIMIZED"; - - eprintln!( - "\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n", - fn_val.get_name().to_str().unwrap(), - mode, - ); - - fn_val.print_to_stderr(); - // module.print_to_stderr(); - - panic!( - "The preceding code was from {:?}, which failed LLVM verification in {} build.", - fn_val.get_name().to_str().unwrap(), - mode, - ); - } - } - let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function( + let (main_fn_name, main_fn) = roc_gen::llvm::build::build_procedures_return_main( &env, - &mut layout_ids, + opt_level, + procedures, main_fn_symbol, main_fn_layout, );