diff --git a/nac3artiq/src/lib.rs b/nac3artiq/src/lib.rs index 3ff933e..db0d22b 100644 --- a/nac3artiq/src/lib.rs +++ b/nac3artiq/src/lib.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use inkwell::{ memory_buffer::MemoryBuffer, module::{Linkage, Module}, - passes::{PassManager, PassManagerBuilder}, + passes::{PassBuilderOptions, PassManager, PassManagerBuilder}, targets::*, OptimizationLevel, }; @@ -654,12 +654,27 @@ 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"); + + if self.llvm_options.legacy_pm { + 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); + } else { + let pass_options = PassBuilderOptions::create(); + pass_options.set_merge_functions(true); + let result = main.run_passes("default", &target_machine, pass_options); + if let Err(err) = result { + println!("Failed to run optimization for module `main`"); + println!("{}", err.to_string()); + panic!(); + } + } link_fn(&main) } @@ -902,7 +917,8 @@ impl Nac3 { deferred_eval_store: DeferredEvaluationStore::new(), llvm_options: CodeGenLLVMOptions { opt_level: OptimizationLevel::Default, - legacy_pm: true, + // FIXME(Derppening): Add a field to device_db.py for modifying this option + legacy_pm: false, target: Nac3::get_llvm_target_options(isa), emit_llvm: false, } diff --git a/nac3core/src/codegen/mod.rs b/nac3core/src/codegen/mod.rs index be43db8..63997de 100644 --- a/nac3core/src/codegen/mod.rs +++ b/nac3core/src/codegen/mod.rs @@ -15,7 +15,7 @@ use inkwell::{ builder::Builder, context::Context, module::Module, - passes::{PassManager, PassManagerBuilder}, + passes::{PassBuilderOptions, PassManager, PassManagerBuilder}, targets::{CodeModel, RelocMode, Target, TargetMachine, TargetTriple}, types::{AnyType, BasicType, BasicTypeEnum}, values::{BasicValueEnum, FunctionValue, PhiValue, PointerValue}, @@ -308,23 +308,33 @@ impl WorkerRegistry { context.i32_type().const_int(4, false), ); - let passes = PassManager::create(&module); + let passes = if self.llvm_options.legacy_pm { + let pm = 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); - } + // 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(&pm); + } + + Some(pm) + } else { + None + }; 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); + + if let Some(pm) = &passes { + pm.run_on(&result.2); + }; + module = result.1; } Err((old_builder, e)) => { @@ -348,6 +358,21 @@ impl WorkerRegistry { panic!() } + if !self.llvm_options.legacy_pm { + 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", self.llvm_options.opt_level as u32); + + let result = module.run_passes(passes.as_str(), &target_machine, pass_options); + if let Err(err) = result { + println!("Failed to run optimization for module `{}`", module.get_name().to_str().unwrap()); + println!("{}", err.to_string()); + panic!(); + } + } + if self.llvm_options.emit_llvm { println!("LLVM IR for {}", module.get_name().to_str().unwrap()); println!("{}", module.to_string()); diff --git a/nac3core/src/codegen/test.rs b/nac3core/src/codegen/test.rs index 7d8de17..b0adb41 100644 --- a/nac3core/src/codegen/test.rs +++ b/nac3core/src/codegen/test.rs @@ -13,7 +13,10 @@ use crate::{ }, }; use indoc::indoc; -use inkwell::OptimizationLevel; +use inkwell::{ + targets::{InitializationConfig, Target}, + OptimizationLevel, +}; use nac3parser::{ ast::{fold::Fold, StrRef}, parser::parse_program, @@ -21,6 +24,7 @@ use nac3parser::{ use parking_lot::RwLock; use std::collections::{HashMap, HashSet}; use std::sync::Arc; +use test_case::test_case; struct Resolver { id_to_type: HashMap, @@ -77,350 +81,455 @@ impl SymbolResolver for Resolver { } } -#[test] -fn test_primitives() { - let source = indoc! { " +struct CodeGenTestInput { + expected: String, + legacy_pm: bool +} + +#[test_case( + vec![ + CodeGenTestInput { + expected: indoc! {" + ; ModuleID = 'test' + source_filename = \"test\" + + ; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn + define i32 @testing(i32 %0, i32 %1) local_unnamed_addr #0 !dbg !4 { + init: + %add = add i32 %1, %0, !dbg !9 + %cmp = icmp eq i32 %add, 1, !dbg !10 + %. = select i1 %cmp, i32 %0, i32 0, !dbg !11 + ret i32 %., !dbg !12 + } + + attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn } + + !llvm.module.flags = !{!0, !1} + !llvm.dbg.cu = !{!2} + + !0 = !{i32 2, !\"Debug Info Version\", i32 3} + !1 = !{i32 2, !\"Dwarf Version\", i32 4} + !2 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) + !3 = !DIFile(filename: \"unknown\", directory: \"\") + !4 = distinct !DISubprogram(name: \"testing\", linkageName: \"testing\", scope: null, file: !3, line: 1, type: !5, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !8) + !5 = !DISubroutineType(flags: DIFlagPublic, types: !6) + !6 = !{!7} + !7 = !DIBasicType(name: \"_\", flags: DIFlagPublic) + !8 = !{} + !9 = !DILocation(line: 1, column: 9, scope: !4) + !10 = !DILocation(line: 2, column: 15, scope: !4) + !11 = !DILocation(line: 0, scope: !4) + !12 = !DILocation(line: 3, column: 8, scope: !4) + "}.trim().to_string(), + legacy_pm: false, + }, + CodeGenTestInput { + expected: indoc! {" + ; ModuleID = 'test' + source_filename = \"test\" + + define i32 @testing(i32 %0, i32 %1) !dbg !4 { + init: + %add = add i32 %0, %1, !dbg !9 + %cmp = icmp eq i32 %add, 1, !dbg !10 + br i1 %cmp, label %then, label %else, !dbg !10 + + then: ; preds = %init + br label %cont, !dbg !11 + + else: ; preds = %init + br label %cont, !dbg !12 + + cont: ; preds = %else, %then + %if_exp_result.0 = phi i32 [ %0, %then ], [ 0, %else ], !dbg !13 + ret i32 %if_exp_result.0, !dbg !14 + } + + !llvm.module.flags = !{!0, !1} + !llvm.dbg.cu = !{!2} + + !0 = !{i32 2, !\"Debug Info Version\", i32 3} + !1 = !{i32 2, !\"Dwarf Version\", i32 4} + !2 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) + !3 = !DIFile(filename: \"unknown\", directory: \"\") + !4 = distinct !DISubprogram(name: \"testing\", linkageName: \"testing\", scope: null, file: !3, line: 1, type: !5, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !8) + !5 = !DISubroutineType(flags: DIFlagPublic, types: !6) + !6 = !{!7} + !7 = !DIBasicType(name: \"_\", flags: DIFlagPublic) + !8 = !{} + !9 = !DILocation(line: 1, column: 9, scope: !4) + !10 = !DILocation(line: 2, column: 15, scope: !4) + !11 = !DILocation(line: 2, column: 5, scope: !4) + !12 = !DILocation(line: 2, column: 22, scope: !4) + !13 = !DILocation(line: 0, scope: !4) + !14 = !DILocation(line: 3, column: 8, scope: !4) + "}.trim().to_string(), + legacy_pm: true, + } + ]; + "primitives codegen" +)] +fn test_primitives(inputs: Vec) { + Target::initialize_all(&InitializationConfig::default()); + + for input in inputs { + let CodeGenTestInput { expected, legacy_pm } = input; + + let source = indoc! { " c = a + b d = a if c == 1 else 0 return d "}; - let statements = parse_program(source, Default::default()).unwrap(); + let statements = parse_program(source, Default::default()).unwrap(); - let composer: TopLevelComposer = Default::default(); - let mut unifier = composer.unifier.clone(); - let primitives = composer.primitives_ty; - let top_level = Arc::new(composer.make_top_level_context()); - unifier.top_level = Some(top_level.clone()); + let composer: TopLevelComposer = Default::default(); + let mut unifier = composer.unifier.clone(); + let primitives = composer.primitives_ty; + let top_level = Arc::new(composer.make_top_level_context()); + unifier.top_level = Some(top_level.clone()); - let resolver = Arc::new(Resolver { - id_to_type: HashMap::new(), - id_to_def: RwLock::new(HashMap::new()), - class_names: Default::default(), - }) as Arc; + let resolver = Arc::new(Resolver { + id_to_type: HashMap::new(), + id_to_def: RwLock::new(HashMap::new()), + class_names: Default::default(), + }) as Arc; - let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()]; - let signature = FunSignature { - args: vec![ - FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }, - FuncArg { name: "b".into(), ty: primitives.int32, default_value: None }, - ], - ret: primitives.int32, - vars: HashMap::new(), - }; + let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()]; + let signature = FunSignature { + args: vec![ + FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }, + FuncArg { name: "b".into(), ty: primitives.int32, default_value: None }, + ], + ret: primitives.int32, + vars: HashMap::new(), + }; - let mut store = ConcreteTypeStore::new(); - let mut cache = HashMap::new(); - let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache); - let signature = store.add_cty(signature); + let mut store = ConcreteTypeStore::new(); + let mut cache = HashMap::new(); + let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache); + let signature = store.add_cty(signature); - let mut function_data = FunctionData { - resolver: resolver.clone(), - bound_variables: Vec::new(), - return_type: Some(primitives.int32), - }; - let mut virtual_checks = Vec::new(); - let mut calls = HashMap::new(); - let mut identifiers: HashSet<_> = ["a".into(), "b".into()].iter().cloned().collect(); - let mut inferencer = Inferencer { - top_level: &top_level, - function_data: &mut function_data, - unifier: &mut unifier, - variable_mapping: Default::default(), - primitives: &primitives, - virtual_checks: &mut virtual_checks, - calls: &mut calls, - defined_identifiers: identifiers.clone(), - in_handler: false, - }; - inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32); - inferencer.variable_mapping.insert("b".into(), inferencer.primitives.int32); + let mut function_data = FunctionData { + resolver: resolver.clone(), + bound_variables: Vec::new(), + return_type: Some(primitives.int32), + }; + let mut virtual_checks = Vec::new(); + let mut calls = HashMap::new(); + let mut identifiers: HashSet<_> = ["a".into(), "b".into()].iter().cloned().collect(); + let mut inferencer = Inferencer { + top_level: &top_level, + function_data: &mut function_data, + unifier: &mut unifier, + variable_mapping: Default::default(), + primitives: &primitives, + virtual_checks: &mut virtual_checks, + calls: &mut calls, + defined_identifiers: identifiers.clone(), + in_handler: false, + }; + inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32); + inferencer.variable_mapping.insert("b".into(), inferencer.primitives.int32); - let statements = statements - .into_iter() - .map(|v| inferencer.fold_stmt(v)) - .collect::, _>>() - .unwrap(); + let statements = statements + .into_iter() + .map(|v| inferencer.fold_stmt(v)) + .collect::, _>>() + .unwrap(); - inferencer.check_block(&statements, &mut identifiers).unwrap(); - let top_level = Arc::new(TopLevelContext { - definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))), - unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])), - personality_symbol: None, - }); + inferencer.check_block(&statements, &mut identifiers).unwrap(); + let top_level = Arc::new(TopLevelContext { + definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))), + unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])), + personality_symbol: None, + }); - let task = CodeGenTask { - subst: Default::default(), - symbol_name: "testing".into(), - body: Arc::new(statements), - unifier_index: 0, - calls: Arc::new(calls), - resolver, - store, - signature, - id: 0, - }; - let f = Arc::new(WithCall::new(Box::new(|module| { - // the following IR is equivalent to - // ``` - // ; ModuleID = 'test.ll' - // source_filename = "test" - // - // ; Function Attrs: norecurse nounwind readnone - // define i32 @testing(i32 %0, i32 %1) local_unnamed_addr #0 { - // init: - // %add = add i32 %1, %0 - // %cmp = icmp eq i32 %add, 1 - // %ifexpr = select i1 %cmp, i32 %0, i32 0 - // ret i32 %ifexpr - // } - // - // attributes #0 = { norecurse nounwind readnone } - // ``` - // after O2 optimization + let task = CodeGenTask { + subst: Default::default(), + symbol_name: "testing".into(), + body: Arc::new(statements), + unifier_index: 0, + calls: Arc::new(calls), + resolver, + store, + signature, + id: 0, + }; - let expected = indoc! {" - ; ModuleID = 'test' - source_filename = \"test\" - - define i32 @testing(i32 %0, i32 %1) !dbg !4 { - init: - %add = add i32 %0, %1, !dbg !9 - %cmp = icmp eq i32 %add, 1, !dbg !10 - br i1 %cmp, label %then, label %else, !dbg !10 - - then: ; preds = %init - br label %cont, !dbg !11 - - else: ; preds = %init - br label %cont, !dbg !12 - - cont: ; preds = %else, %then - %if_exp_result.0 = phi i32 [ %0, %then ], [ 0, %else ], !dbg !13 - ret i32 %if_exp_result.0, !dbg !14 - } + let f = Arc::new(WithCall::new(Box::new(move |module| { + // the following IR is equivalent to + // ``` + // ; ModuleID = 'test.ll' + // source_filename = "test" + // + // ; Function Attrs: norecurse nounwind readnone + // define i32 @testing(i32 %0, i32 %1) local_unnamed_addr #0 { + // init: + // %add = add i32 %1, %0 + // %cmp = icmp eq i32 %add, 1 + // %ifexpr = select i1 %cmp, i32 %0, i32 0 + // ret i32 %ifexpr + // } + // + // attributes #0 = { norecurse nounwind readnone } + // ``` + // after O2 optimization + assert_eq!(expected, module.print_to_string().to_str().unwrap().trim()); + }))); - !llvm.module.flags = !{!0, !1} - !llvm.dbg.cu = !{!2} - - !0 = !{i32 2, !\"Debug Info Version\", i32 3} - !1 = !{i32 2, !\"Dwarf Version\", i32 4} - !2 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) - !3 = !DIFile(filename: \"unknown\", directory: \"\") - !4 = distinct !DISubprogram(name: \"testing\", linkageName: \"testing\", scope: null, file: !3, line: 1, type: !5, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !8) - !5 = !DISubroutineType(flags: DIFlagPublic, types: !6) - !6 = !{!7} - !7 = !DIBasicType(name: \"_\", flags: DIFlagPublic) - !8 = !{} - !9 = !DILocation(line: 1, column: 9, scope: !4) - !10 = !DILocation(line: 2, column: 15, scope: !4) - !11 = !DILocation(line: 2, column: 5, scope: !4) - !12 = !DILocation(line: 2, column: 22, scope: !4) - !13 = !DILocation(line: 0, scope: !4) - !14 = !DILocation(line: 3, column: 8, scope: !4) - "} - .trim(); - assert_eq!(expected, module.print_to_string().to_str().unwrap().trim()); - }))); - - let llvm_options = CodeGenLLVMOptions { - opt_level: OptimizationLevel::Default, - legacy_pm: true, - target: CodeGenTargetMachineOptions::from_host_triple(), - emit_llvm: false, - }; - let (registry, handles) = WorkerRegistry::create_workers( - threads, - top_level, - &llvm_options, - f - ); - registry.add_task(task); - registry.wait_tasks_complete(handles); + let llvm_options = CodeGenLLVMOptions { + opt_level: OptimizationLevel::Default, + legacy_pm, + target: CodeGenTargetMachineOptions::from_host_triple(), + emit_llvm: false, + }; + let (registry, handles) = WorkerRegistry::create_workers( + threads, + top_level, + &llvm_options, + f + ); + registry.add_task(task); + registry.wait_tasks_complete(handles); + } } -#[test] -fn test_simple_call() { - let source_1 = indoc! { " +#[test_case( + vec![ + CodeGenTestInput { + expected: indoc! {" + ; ModuleID = 'test' + source_filename = \"test\" + + ; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn + define i32 @testing(i32 %0) local_unnamed_addr #0 !dbg !5 { + init: + %add.i = shl i32 %0, 1, !dbg !10 + %mul = add i32 %add.i, 2, !dbg !10 + ret i32 %mul, !dbg !10 + } + + ; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn + define i32 @foo.0(i32 %0) local_unnamed_addr #0 !dbg !11 { + init: + %add = add i32 %0, 1, !dbg !12 + ret i32 %add, !dbg !12 + } + + attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn } + + !llvm.module.flags = !{!0, !1} + !llvm.dbg.cu = !{!2, !4} + + !0 = !{i32 2, !\"Debug Info Version\", i32 3} + !1 = !{i32 2, !\"Dwarf Version\", i32 4} + !2 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) + !3 = !DIFile(filename: \"unknown\", directory: \"\") + !4 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) + !5 = distinct !DISubprogram(name: \"testing\", linkageName: \"testing\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !9) + !6 = !DISubroutineType(flags: DIFlagPublic, types: !7) + !7 = !{!8} + !8 = !DIBasicType(name: \"_\", flags: DIFlagPublic) + !9 = !{} + !10 = !DILocation(line: 2, column: 12, scope: !5) + !11 = distinct !DISubprogram(name: \"foo.0\", linkageName: \"foo.0\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !4, retainedNodes: !9) + !12 = !DILocation(line: 1, column: 12, scope: !11) + "}.trim().to_string(), + legacy_pm: false, + }, + CodeGenTestInput { + expected: indoc! {" + ; ModuleID = 'test' + source_filename = \"test\" + + define i32 @testing(i32 %0) !dbg !5 { + init: + %call = call i32 @foo.0(i32 %0), !dbg !10 + %mul = mul i32 %call, 2, !dbg !11 + ret i32 %mul, !dbg !11 + } + + define i32 @foo.0(i32 %0) !dbg !12 { + init: + %add = add i32 %0, 1, !dbg !13 + ret i32 %add, !dbg !13 + } + + !llvm.module.flags = !{!0, !1} + !llvm.dbg.cu = !{!2, !4} + + !0 = !{i32 2, !\"Debug Info Version\", i32 3} + !1 = !{i32 2, !\"Dwarf Version\", i32 4} + !2 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) + !3 = !DIFile(filename: \"unknown\", directory: \"\") + !4 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) + !5 = distinct !DISubprogram(name: \"testing\", linkageName: \"testing\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !9) + !6 = !DISubroutineType(flags: DIFlagPublic, types: !7) + !7 = !{!8} + !8 = !DIBasicType(name: \"_\", flags: DIFlagPublic) + !9 = !{} + !10 = !DILocation(line: 1, column: 9, scope: !5) + !11 = !DILocation(line: 2, column: 12, scope: !5) + !12 = distinct !DISubprogram(name: \"foo.0\", linkageName: \"foo.0\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !4, retainedNodes: !9) + !13 = !DILocation(line: 1, column: 12, scope: !12) + "}.trim().to_string(), + legacy_pm: true, + } + ]; + "primitives codegen" +)] +fn test_simple_call(inputs: Vec) { + Target::initialize_all(&InitializationConfig::default()); + + for input in inputs { + let CodeGenTestInput { expected, legacy_pm } = input; + let source_1 = indoc! { " a = foo(a) return a * 2 "}; - let statements_1 = parse_program(source_1, Default::default()).unwrap(); + let statements_1 = parse_program(source_1, Default::default()).unwrap(); - let source_2 = indoc! { " + let source_2 = indoc! { " return a + 1 "}; - let statements_2 = parse_program(source_2, Default::default()).unwrap(); + let statements_2 = parse_program(source_2, Default::default()).unwrap(); - let composer: TopLevelComposer = Default::default(); - let mut unifier = composer.unifier.clone(); - let primitives = composer.primitives_ty; - let top_level = Arc::new(composer.make_top_level_context()); - unifier.top_level = Some(top_level.clone()); + let composer: TopLevelComposer = Default::default(); + let mut unifier = composer.unifier.clone(); + let primitives = composer.primitives_ty; + let top_level = Arc::new(composer.make_top_level_context()); + unifier.top_level = Some(top_level.clone()); - let signature = FunSignature { - args: vec![FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }], - ret: primitives.int32, - vars: HashMap::new(), - }; - let fun_ty = unifier.add_ty(TypeEnum::TFunc(signature.clone())); - let mut store = ConcreteTypeStore::new(); - let mut cache = HashMap::new(); - let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache); - let signature = store.add_cty(signature); + let signature = FunSignature { + args: vec![FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }], + ret: primitives.int32, + vars: HashMap::new(), + }; + let fun_ty = unifier.add_ty(TypeEnum::TFunc(signature.clone())); + let mut store = ConcreteTypeStore::new(); + let mut cache = HashMap::new(); + let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache); + let signature = store.add_cty(signature); - let foo_id = top_level.definitions.read().len(); - top_level.definitions.write().push(Arc::new(RwLock::new(TopLevelDef::Function { - name: "foo".to_string(), - simple_name: "foo".into(), - signature: fun_ty, - var_id: vec![], - instance_to_stmt: HashMap::new(), - instance_to_symbol: HashMap::new(), - resolver: None, - codegen_callback: None, - loc: None, - }))); + let foo_id = top_level.definitions.read().len(); + top_level.definitions.write().push(Arc::new(RwLock::new(TopLevelDef::Function { + name: "foo".to_string(), + simple_name: "foo".into(), + signature: fun_ty, + var_id: vec![], + instance_to_stmt: HashMap::new(), + instance_to_symbol: HashMap::new(), + resolver: None, + codegen_callback: None, + loc: None, + }))); - let resolver = Resolver { - id_to_type: HashMap::new(), - id_to_def: RwLock::new(HashMap::new()), - class_names: Default::default(), - }; - resolver.add_id_def("foo".into(), DefinitionId(foo_id)); - let resolver = Arc::new(resolver) as Arc; + let resolver = Resolver { + id_to_type: HashMap::new(), + id_to_def: RwLock::new(HashMap::new()), + class_names: Default::default(), + }; + resolver.add_id_def("foo".into(), DefinitionId(foo_id)); + let resolver = Arc::new(resolver) as Arc; - if let TopLevelDef::Function { resolver: r, .. } = - &mut *top_level.definitions.read()[foo_id].write() - { - *r = Some(resolver.clone()); - } else { - unreachable!() - } + if let TopLevelDef::Function { resolver: r, .. } = + &mut *top_level.definitions.read()[foo_id].write() + { + *r = Some(resolver.clone()); + } else { + unreachable!() + } - let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()]; - let mut function_data = FunctionData { - resolver: resolver.clone(), - bound_variables: Vec::new(), - return_type: Some(primitives.int32), - }; - let mut virtual_checks = Vec::new(); - let mut calls = HashMap::new(); - let mut identifiers: HashSet<_> = ["a".into(), "foo".into()].iter().cloned().collect(); - let mut inferencer = Inferencer { - top_level: &top_level, - function_data: &mut function_data, - unifier: &mut unifier, - variable_mapping: Default::default(), - primitives: &primitives, - virtual_checks: &mut virtual_checks, - calls: &mut calls, - defined_identifiers: identifiers.clone(), - in_handler: false, - }; - inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32); - inferencer.variable_mapping.insert("foo".into(), fun_ty); + let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()]; + let mut function_data = FunctionData { + resolver: resolver.clone(), + bound_variables: Vec::new(), + return_type: Some(primitives.int32), + }; + let mut virtual_checks = Vec::new(); + let mut calls = HashMap::new(); + let mut identifiers: HashSet<_> = ["a".into(), "foo".into()].iter().cloned().collect(); + let mut inferencer = Inferencer { + top_level: &top_level, + function_data: &mut function_data, + unifier: &mut unifier, + variable_mapping: Default::default(), + primitives: &primitives, + virtual_checks: &mut virtual_checks, + calls: &mut calls, + defined_identifiers: identifiers.clone(), + in_handler: false, + }; + inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32); + inferencer.variable_mapping.insert("foo".into(), fun_ty); - let statements_1 = statements_1 - .into_iter() - .map(|v| inferencer.fold_stmt(v)) - .collect::, _>>() - .unwrap(); + let statements_1 = statements_1 + .into_iter() + .map(|v| inferencer.fold_stmt(v)) + .collect::, _>>() + .unwrap(); - let calls1 = inferencer.calls.clone(); - inferencer.calls.clear(); + let calls1 = inferencer.calls.clone(); + inferencer.calls.clear(); - let statements_2 = statements_2 - .into_iter() - .map(|v| inferencer.fold_stmt(v)) - .collect::, _>>() - .unwrap(); + let statements_2 = statements_2 + .into_iter() + .map(|v| inferencer.fold_stmt(v)) + .collect::, _>>() + .unwrap(); - if let TopLevelDef::Function { instance_to_stmt, .. } = - &mut *top_level.definitions.read()[foo_id].write() - { - instance_to_stmt.insert( - "".to_string(), - FunInstance { - body: Arc::new(statements_2), - calls: Arc::new(inferencer.calls.clone()), - subst: Default::default(), - unifier_id: 0, - }, + if let TopLevelDef::Function { instance_to_stmt, .. } = + &mut *top_level.definitions.read()[foo_id].write() + { + instance_to_stmt.insert( + "".to_string(), + FunInstance { + body: Arc::new(statements_2), + calls: Arc::new(inferencer.calls.clone()), + subst: Default::default(), + unifier_id: 0, + }, + ); + } else { + unreachable!() + } + + inferencer.check_block(&statements_1, &mut identifiers).unwrap(); + let top_level = Arc::new(TopLevelContext { + definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))), + unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])), + personality_symbol: None, + }); + + let task = CodeGenTask { + subst: Default::default(), + symbol_name: "testing".to_string(), + body: Arc::new(statements_1), + calls: Arc::new(calls1), + unifier_index: 0, + resolver, + signature, + store, + id: 0, + }; + + let f = Arc::new(WithCall::new(Box::new(move |module| { + assert_eq!(expected, module.print_to_string().to_str().unwrap().trim()); + }))); + + let llvm_options = CodeGenLLVMOptions { + opt_level: OptimizationLevel::Default, + legacy_pm, + target: CodeGenTargetMachineOptions::from_host_triple(), + emit_llvm: false, + }; + let (registry, handles) = WorkerRegistry::create_workers( + threads, + top_level, + &llvm_options, + f ); - } else { - unreachable!() + registry.add_task(task); + registry.wait_tasks_complete(handles); } - - inferencer.check_block(&statements_1, &mut identifiers).unwrap(); - let top_level = Arc::new(TopLevelContext { - definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))), - unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])), - personality_symbol: None, - }); - - let task = CodeGenTask { - subst: Default::default(), - symbol_name: "testing".to_string(), - body: Arc::new(statements_1), - calls: Arc::new(calls1), - unifier_index: 0, - resolver, - signature, - store, - id: 0, - }; - let f = Arc::new(WithCall::new(Box::new(|module| { - let expected = indoc! {" - ; ModuleID = 'test' - source_filename = \"test\" - - define i32 @testing(i32 %0) !dbg !5 { - init: - %call = call i32 @foo.0(i32 %0), !dbg !10 - %mul = mul i32 %call, 2, !dbg !11 - ret i32 %mul, !dbg !11 - } - - define i32 @foo.0(i32 %0) !dbg !12 { - init: - %add = add i32 %0, 1, !dbg !13 - ret i32 %add, !dbg !13 - } - - !llvm.module.flags = !{!0, !1} - !llvm.dbg.cu = !{!2, !4} - - !0 = !{i32 2, !\"Debug Info Version\", i32 3} - !1 = !{i32 2, !\"Dwarf Version\", i32 4} - !2 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) - !3 = !DIFile(filename: \"unknown\", directory: \"\") - !4 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) - !5 = distinct !DISubprogram(name: \"testing\", linkageName: \"testing\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !9) - !6 = !DISubroutineType(flags: DIFlagPublic, types: !7) - !7 = !{!8} - !8 = !DIBasicType(name: \"_\", flags: DIFlagPublic) - !9 = !{} - !10 = !DILocation(line: 1, column: 9, scope: !5) - !11 = !DILocation(line: 2, column: 12, scope: !5) - !12 = distinct !DISubprogram(name: \"foo.0\", linkageName: \"foo.0\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !4, retainedNodes: !9) - !13 = !DILocation(line: 1, column: 12, scope: !12) - "} - .trim(); - assert_eq!(expected, module.print_to_string().to_str().unwrap().trim()); - }))); - - let llvm_options = CodeGenLLVMOptions { - opt_level: OptimizationLevel::Default, - legacy_pm: true, - target: CodeGenTargetMachineOptions::from_host_triple(), - emit_llvm: false, - }; - let (registry, handles) = WorkerRegistry::create_workers( - threads, - top_level, - &llvm_options, - f - ); - registry.add_task(task); - registry.wait_tasks_complete(handles); } diff --git a/nac3standalone/src/main.rs b/nac3standalone/src/main.rs index cbb03f7..fa00906 100644 --- a/nac3standalone/src/main.rs +++ b/nac3standalone/src/main.rs @@ -1,7 +1,7 @@ use clap::Parser; use inkwell::{ memory_buffer::MemoryBuffer, - passes::{PassManager, PassManagerBuilder}, + passes::{PassBuilderOptions, PassManager, PassManagerBuilder}, targets::*, OptimizationLevel, }; @@ -49,6 +49,10 @@ struct CommandLineArgs { /// Whether to emit LLVM IR at the end of every module. #[arg(long, default_value_t = false)] emit_llvm: bool, + + /// Whether to use LLVM's Legacy Pass Manager for optimizations. + #[arg(long, default_value_t = false)] + legacy_pm: bool, } fn handle_typevar_definition( @@ -177,7 +181,13 @@ fn handle_assignment_pattern( fn main() { let cli = CommandLineArgs::parse(); - let CommandLineArgs { file_name, threads, opt_level, emit_llvm } = cli; + let CommandLineArgs { + file_name, + threads, + opt_level, + emit_llvm, + legacy_pm, + } = cli; let opt_level = match opt_level { 0 => OptimizationLevel::None, 1 => OptimizationLevel::Less, @@ -272,7 +282,7 @@ fn main() { let llvm_options = CodeGenLLVMOptions { opt_level, - legacy_pm: true, + legacy_pm, target: CodeGenTargetMachineOptions::from_host_triple(), emit_llvm, }; @@ -326,16 +336,28 @@ 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"); + + if legacy_pm { + 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); + } else { + let pass_options = PassBuilderOptions::create(); + pass_options.set_merge_functions(true); + let result = main.run_passes("default", &target_machine, pass_options); + if let Err(err) = result { + println!("Failed to run optimization for module `main`"); + println!("{}", err.to_string()); + panic!(); + } + } + target_machine .write_to_file(&main, FileType::Object, Path::new("module.o")) .expect("couldn't write module to file");