core: Switch to LLVM New Pass Manager

This commit is contained in:
David Mak 2023-09-11 13:14:35 +08:00 committed by David Mak
parent 70cb0bd898
commit 5722f3397e
4 changed files with 514 additions and 342 deletions

View File

@ -8,7 +8,7 @@ use std::sync::Arc;
use inkwell::{ use inkwell::{
memory_buffer::MemoryBuffer, memory_buffer::MemoryBuffer,
module::{Linkage, Module}, module::{Linkage, Module},
passes::{PassManager, PassManagerBuilder}, passes::{PassBuilderOptions, PassManager, PassManagerBuilder},
targets::*, targets::*,
OptimizationLevel, OptimizationLevel,
}; };
@ -654,12 +654,27 @@ impl Nac3 {
global_option = global.get_next_global(); global_option = global.get_next_global();
} }
let builder = PassManagerBuilder::create(); let target_machine = self.llvm_options.target
builder.set_optimization_level(OptimizationLevel::Aggressive); .create_target_machine(self.llvm_options.opt_level)
let passes = PassManager::create(()); .expect("couldn't create target machine");
builder.set_inliner_with_threshold(255);
builder.populate_module_pass_manager(&passes); if self.llvm_options.legacy_pm {
passes.run_on(&main); 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<O3>", &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) link_fn(&main)
} }
@ -902,7 +917,8 @@ impl Nac3 {
deferred_eval_store: DeferredEvaluationStore::new(), deferred_eval_store: DeferredEvaluationStore::new(),
llvm_options: CodeGenLLVMOptions { llvm_options: CodeGenLLVMOptions {
opt_level: OptimizationLevel::Default, 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), target: Nac3::get_llvm_target_options(isa),
emit_llvm: false, emit_llvm: false,
} }

View File

@ -15,7 +15,7 @@ use inkwell::{
builder::Builder, builder::Builder,
context::Context, context::Context,
module::Module, module::Module,
passes::{PassManager, PassManagerBuilder}, passes::{PassBuilderOptions, PassManager, PassManagerBuilder},
targets::{CodeModel, RelocMode, Target, TargetMachine, TargetTriple}, targets::{CodeModel, RelocMode, Target, TargetMachine, TargetTriple},
types::{AnyType, BasicType, BasicTypeEnum}, types::{AnyType, BasicType, BasicTypeEnum},
values::{BasicValueEnum, FunctionValue, PhiValue, PointerValue}, values::{BasicValueEnum, FunctionValue, PhiValue, PointerValue},
@ -308,23 +308,33 @@ impl WorkerRegistry {
context.i32_type().const_int(4, false), 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 // HACK: This critical section is a work-around for issue
// https://git.m-labs.hk/M-Labs/nac3/issues/275 // https://git.m-labs.hk/M-Labs/nac3/issues/275
{ {
let _data = PASSES_INIT_LOCK.lock(); let _data = PASSES_INIT_LOCK.lock();
let pass_builder = PassManagerBuilder::create(); let pass_builder = PassManagerBuilder::create();
pass_builder.set_optimization_level(self.llvm_options.opt_level); pass_builder.set_optimization_level(self.llvm_options.opt_level);
pass_builder.populate_function_pass_manager(&passes); pass_builder.populate_function_pass_manager(&pm);
} }
Some(pm)
} else {
None
};
let mut errors = HashSet::new(); let mut errors = HashSet::new();
while let Some(task) = self.receiver.recv().unwrap() { while let Some(task) = self.receiver.recv().unwrap() {
match gen_func(&context, generator, self, builder, module, task) { match gen_func(&context, generator, self, builder, module, task) {
Ok(result) => { Ok(result) => {
builder = result.0; builder = result.0;
passes.run_on(&result.2);
if let Some(pm) = &passes {
pm.run_on(&result.2);
};
module = result.1; module = result.1;
} }
Err((old_builder, e)) => { Err((old_builder, e)) => {
@ -348,6 +358,21 @@ impl WorkerRegistry {
panic!() 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<O{}>", 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 { if self.llvm_options.emit_llvm {
println!("LLVM IR for {}", module.get_name().to_str().unwrap()); println!("LLVM IR for {}", module.get_name().to_str().unwrap());
println!("{}", module.to_string()); println!("{}", module.to_string());

View File

@ -13,7 +13,10 @@ use crate::{
}, },
}; };
use indoc::indoc; use indoc::indoc;
use inkwell::OptimizationLevel; use inkwell::{
targets::{InitializationConfig, Target},
OptimizationLevel,
};
use nac3parser::{ use nac3parser::{
ast::{fold::Fold, StrRef}, ast::{fold::Fold, StrRef},
parser::parse_program, parser::parse_program,
@ -21,6 +24,7 @@ use nac3parser::{
use parking_lot::RwLock; use parking_lot::RwLock;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
use test_case::test_case;
struct Resolver { struct Resolver {
id_to_type: HashMap<StrRef, Type>, id_to_type: HashMap<StrRef, Type>,
@ -77,350 +81,455 @@ impl SymbolResolver for Resolver {
} }
} }
#[test] struct CodeGenTestInput {
fn test_primitives() { expected: String,
let source = indoc! { " 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<CodeGenTestInput>) {
Target::initialize_all(&InitializationConfig::default());
for input in inputs {
let CodeGenTestInput { expected, legacy_pm } = input;
let source = indoc! { "
c = a + b c = a + b
d = a if c == 1 else 0 d = a if c == 1 else 0
return d return d
"}; "};
let statements = parse_program(source, Default::default()).unwrap(); let statements = parse_program(source, Default::default()).unwrap();
let composer: TopLevelComposer = Default::default(); let composer: TopLevelComposer = Default::default();
let mut unifier = composer.unifier.clone(); let mut unifier = composer.unifier.clone();
let primitives = composer.primitives_ty; let primitives = composer.primitives_ty;
let top_level = Arc::new(composer.make_top_level_context()); let top_level = Arc::new(composer.make_top_level_context());
unifier.top_level = Some(top_level.clone()); unifier.top_level = Some(top_level.clone());
let resolver = Arc::new(Resolver { let resolver = Arc::new(Resolver {
id_to_type: HashMap::new(), id_to_type: HashMap::new(),
id_to_def: RwLock::new(HashMap::new()), id_to_def: RwLock::new(HashMap::new()),
class_names: Default::default(), class_names: Default::default(),
}) as Arc<dyn SymbolResolver + Send + Sync>; }) as Arc<dyn SymbolResolver + Send + Sync>;
let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()]; let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()];
let signature = FunSignature { let signature = FunSignature {
args: vec![ args: vec![
FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }, FuncArg { name: "a".into(), ty: primitives.int32, default_value: None },
FuncArg { name: "b".into(), ty: primitives.int32, default_value: None }, FuncArg { name: "b".into(), ty: primitives.int32, default_value: None },
], ],
ret: primitives.int32, ret: primitives.int32,
vars: HashMap::new(), vars: HashMap::new(),
}; };
let mut store = ConcreteTypeStore::new(); let mut store = ConcreteTypeStore::new();
let mut cache = HashMap::new(); let mut cache = HashMap::new();
let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache); let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache);
let signature = store.add_cty(signature); let signature = store.add_cty(signature);
let mut function_data = FunctionData { let mut function_data = FunctionData {
resolver: resolver.clone(), resolver: resolver.clone(),
bound_variables: Vec::new(), bound_variables: Vec::new(),
return_type: Some(primitives.int32), return_type: Some(primitives.int32),
}; };
let mut virtual_checks = Vec::new(); let mut virtual_checks = Vec::new();
let mut calls = HashMap::new(); let mut calls = HashMap::new();
let mut identifiers: HashSet<_> = ["a".into(), "b".into()].iter().cloned().collect(); let mut identifiers: HashSet<_> = ["a".into(), "b".into()].iter().cloned().collect();
let mut inferencer = Inferencer { let mut inferencer = Inferencer {
top_level: &top_level, top_level: &top_level,
function_data: &mut function_data, function_data: &mut function_data,
unifier: &mut unifier, unifier: &mut unifier,
variable_mapping: Default::default(), variable_mapping: Default::default(),
primitives: &primitives, primitives: &primitives,
virtual_checks: &mut virtual_checks, virtual_checks: &mut virtual_checks,
calls: &mut calls, calls: &mut calls,
defined_identifiers: identifiers.clone(), defined_identifiers: identifiers.clone(),
in_handler: false, in_handler: false,
}; };
inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32); inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32);
inferencer.variable_mapping.insert("b".into(), inferencer.primitives.int32); inferencer.variable_mapping.insert("b".into(), inferencer.primitives.int32);
let statements = statements let statements = statements
.into_iter() .into_iter()
.map(|v| inferencer.fold_stmt(v)) .map(|v| inferencer.fold_stmt(v))
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.unwrap(); .unwrap();
inferencer.check_block(&statements, &mut identifiers).unwrap(); inferencer.check_block(&statements, &mut identifiers).unwrap();
let top_level = Arc::new(TopLevelContext { let top_level = Arc::new(TopLevelContext {
definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))), definitions: Arc::new(RwLock::new(std::mem::take(&mut *top_level.definitions.write()))),
unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])), unifiers: Arc::new(RwLock::new(vec![(unifier.get_shared_unifier(), primitives)])),
personality_symbol: None, personality_symbol: None,
}); });
let task = CodeGenTask { let task = CodeGenTask {
subst: Default::default(), subst: Default::default(),
symbol_name: "testing".into(), symbol_name: "testing".into(),
body: Arc::new(statements), body: Arc::new(statements),
unifier_index: 0, unifier_index: 0,
calls: Arc::new(calls), calls: Arc::new(calls),
resolver, resolver,
store, store,
signature, signature,
id: 0, 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 expected = indoc! {" let f = Arc::new(WithCall::new(Box::new(move |module| {
; ModuleID = 'test' // the following IR is equivalent to
source_filename = \"test\" // ```
// ; ModuleID = 'test.ll'
define i32 @testing(i32 %0, i32 %1) !dbg !4 { // source_filename = "test"
init: //
%add = add i32 %0, %1, !dbg !9 // ; Function Attrs: norecurse nounwind readnone
%cmp = icmp eq i32 %add, 1, !dbg !10 // define i32 @testing(i32 %0, i32 %1) local_unnamed_addr #0 {
br i1 %cmp, label %then, label %else, !dbg !10 // init:
// %add = add i32 %1, %0
then: ; preds = %init // %cmp = icmp eq i32 %add, 1
br label %cont, !dbg !11 // %ifexpr = select i1 %cmp, i32 %0, i32 0
// ret i32 %ifexpr
else: ; preds = %init // }
br label %cont, !dbg !12 //
// attributes #0 = { norecurse nounwind readnone }
cont: ; preds = %else, %then // ```
%if_exp_result.0 = phi i32 [ %0, %then ], [ 0, %else ], !dbg !13 // after O2 optimization
ret i32 %if_exp_result.0, !dbg !14 assert_eq!(expected, module.print_to_string().to_str().unwrap().trim());
} })));
!llvm.module.flags = !{!0, !1} let llvm_options = CodeGenLLVMOptions {
!llvm.dbg.cu = !{!2} opt_level: OptimizationLevel::Default,
legacy_pm,
!0 = !{i32 2, !\"Debug Info Version\", i32 3} target: CodeGenTargetMachineOptions::from_host_triple(),
!1 = !{i32 2, !\"Dwarf Version\", i32 4} emit_llvm: false,
!2 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug) };
!3 = !DIFile(filename: \"unknown\", directory: \"\") let (registry, handles) = WorkerRegistry::create_workers(
!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) threads,
!5 = !DISubroutineType(flags: DIFlagPublic, types: !6) top_level,
!6 = !{!7} &llvm_options,
!7 = !DIBasicType(name: \"_\", flags: DIFlagPublic) f
!8 = !{} );
!9 = !DILocation(line: 1, column: 9, scope: !4) registry.add_task(task);
!10 = !DILocation(line: 2, column: 15, scope: !4) registry.wait_tasks_complete(handles);
!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);
} }
#[test] #[test_case(
fn test_simple_call() { vec![
let source_1 = indoc! { " 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<CodeGenTestInput>) {
Target::initialize_all(&InitializationConfig::default());
for input in inputs {
let CodeGenTestInput { expected, legacy_pm } = input;
let source_1 = indoc! { "
a = foo(a) a = foo(a)
return a * 2 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 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 composer: TopLevelComposer = Default::default();
let mut unifier = composer.unifier.clone(); let mut unifier = composer.unifier.clone();
let primitives = composer.primitives_ty; let primitives = composer.primitives_ty;
let top_level = Arc::new(composer.make_top_level_context()); let top_level = Arc::new(composer.make_top_level_context());
unifier.top_level = Some(top_level.clone()); unifier.top_level = Some(top_level.clone());
let signature = FunSignature { let signature = FunSignature {
args: vec![FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }], args: vec![FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }],
ret: primitives.int32, ret: primitives.int32,
vars: HashMap::new(), vars: HashMap::new(),
}; };
let fun_ty = unifier.add_ty(TypeEnum::TFunc(signature.clone())); let fun_ty = unifier.add_ty(TypeEnum::TFunc(signature.clone()));
let mut store = ConcreteTypeStore::new(); let mut store = ConcreteTypeStore::new();
let mut cache = HashMap::new(); let mut cache = HashMap::new();
let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache); let signature = store.from_signature(&mut unifier, &primitives, &signature, &mut cache);
let signature = store.add_cty(signature); let signature = store.add_cty(signature);
let foo_id = top_level.definitions.read().len(); let foo_id = top_level.definitions.read().len();
top_level.definitions.write().push(Arc::new(RwLock::new(TopLevelDef::Function { top_level.definitions.write().push(Arc::new(RwLock::new(TopLevelDef::Function {
name: "foo".to_string(), name: "foo".to_string(),
simple_name: "foo".into(), simple_name: "foo".into(),
signature: fun_ty, signature: fun_ty,
var_id: vec![], var_id: vec![],
instance_to_stmt: HashMap::new(), instance_to_stmt: HashMap::new(),
instance_to_symbol: HashMap::new(), instance_to_symbol: HashMap::new(),
resolver: None, resolver: None,
codegen_callback: None, codegen_callback: None,
loc: None, loc: None,
}))); })));
let resolver = Resolver { let resolver = Resolver {
id_to_type: HashMap::new(), id_to_type: HashMap::new(),
id_to_def: RwLock::new(HashMap::new()), id_to_def: RwLock::new(HashMap::new()),
class_names: Default::default(), class_names: Default::default(),
}; };
resolver.add_id_def("foo".into(), DefinitionId(foo_id)); resolver.add_id_def("foo".into(), DefinitionId(foo_id));
let resolver = Arc::new(resolver) as Arc<dyn SymbolResolver + Send + Sync>; let resolver = Arc::new(resolver) as Arc<dyn SymbolResolver + Send + Sync>;
if let TopLevelDef::Function { resolver: r, .. } = if let TopLevelDef::Function { resolver: r, .. } =
&mut *top_level.definitions.read()[foo_id].write() &mut *top_level.definitions.read()[foo_id].write()
{ {
*r = Some(resolver.clone()); *r = Some(resolver.clone());
} else { } else {
unreachable!() unreachable!()
} }
let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()]; let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()];
let mut function_data = FunctionData { let mut function_data = FunctionData {
resolver: resolver.clone(), resolver: resolver.clone(),
bound_variables: Vec::new(), bound_variables: Vec::new(),
return_type: Some(primitives.int32), return_type: Some(primitives.int32),
}; };
let mut virtual_checks = Vec::new(); let mut virtual_checks = Vec::new();
let mut calls = HashMap::new(); let mut calls = HashMap::new();
let mut identifiers: HashSet<_> = ["a".into(), "foo".into()].iter().cloned().collect(); let mut identifiers: HashSet<_> = ["a".into(), "foo".into()].iter().cloned().collect();
let mut inferencer = Inferencer { let mut inferencer = Inferencer {
top_level: &top_level, top_level: &top_level,
function_data: &mut function_data, function_data: &mut function_data,
unifier: &mut unifier, unifier: &mut unifier,
variable_mapping: Default::default(), variable_mapping: Default::default(),
primitives: &primitives, primitives: &primitives,
virtual_checks: &mut virtual_checks, virtual_checks: &mut virtual_checks,
calls: &mut calls, calls: &mut calls,
defined_identifiers: identifiers.clone(), defined_identifiers: identifiers.clone(),
in_handler: false, in_handler: false,
}; };
inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32); inferencer.variable_mapping.insert("a".into(), inferencer.primitives.int32);
inferencer.variable_mapping.insert("foo".into(), fun_ty); inferencer.variable_mapping.insert("foo".into(), fun_ty);
let statements_1 = statements_1 let statements_1 = statements_1
.into_iter() .into_iter()
.map(|v| inferencer.fold_stmt(v)) .map(|v| inferencer.fold_stmt(v))
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.unwrap(); .unwrap();
let calls1 = inferencer.calls.clone(); let calls1 = inferencer.calls.clone();
inferencer.calls.clear(); inferencer.calls.clear();
let statements_2 = statements_2 let statements_2 = statements_2
.into_iter() .into_iter()
.map(|v| inferencer.fold_stmt(v)) .map(|v| inferencer.fold_stmt(v))
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.unwrap(); .unwrap();
if let TopLevelDef::Function { instance_to_stmt, .. } = if let TopLevelDef::Function { instance_to_stmt, .. } =
&mut *top_level.definitions.read()[foo_id].write() &mut *top_level.definitions.read()[foo_id].write()
{ {
instance_to_stmt.insert( instance_to_stmt.insert(
"".to_string(), "".to_string(),
FunInstance { FunInstance {
body: Arc::new(statements_2), body: Arc::new(statements_2),
calls: Arc::new(inferencer.calls.clone()), calls: Arc::new(inferencer.calls.clone()),
subst: Default::default(), subst: Default::default(),
unifier_id: 0, 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 { registry.add_task(task);
unreachable!() 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);
} }

View File

@ -1,7 +1,7 @@
use clap::Parser; use clap::Parser;
use inkwell::{ use inkwell::{
memory_buffer::MemoryBuffer, memory_buffer::MemoryBuffer,
passes::{PassManager, PassManagerBuilder}, passes::{PassBuilderOptions, PassManager, PassManagerBuilder},
targets::*, targets::*,
OptimizationLevel, OptimizationLevel,
}; };
@ -49,6 +49,10 @@ struct CommandLineArgs {
/// Whether to emit LLVM IR at the end of every module. /// Whether to emit LLVM IR at the end of every module.
#[arg(long, default_value_t = false)] #[arg(long, default_value_t = false)]
emit_llvm: bool, 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( fn handle_typevar_definition(
@ -177,7 +181,13 @@ fn handle_assignment_pattern(
fn main() { fn main() {
let cli = CommandLineArgs::parse(); 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 { let opt_level = match opt_level {
0 => OptimizationLevel::None, 0 => OptimizationLevel::None,
1 => OptimizationLevel::Less, 1 => OptimizationLevel::Less,
@ -272,7 +282,7 @@ fn main() {
let llvm_options = CodeGenLLVMOptions { let llvm_options = CodeGenLLVMOptions {
opt_level, opt_level,
legacy_pm: true, legacy_pm,
target: CodeGenTargetMachineOptions::from_host_triple(), target: CodeGenTargetMachineOptions::from_host_triple(),
emit_llvm, emit_llvm,
}; };
@ -326,16 +336,28 @@ fn main() {
function_iter = func.get_next_function(); 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 let target_machine = llvm_options.target
.create_target_machine(llvm_options.opt_level) .create_target_machine(llvm_options.opt_level)
.expect("couldn't create target machine"); .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<O3>", &target_machine, pass_options);
if let Err(err) = result {
println!("Failed to run optimization for module `main`");
println!("{}", err.to_string());
panic!();
}
}
target_machine target_machine
.write_to_file(&main, FileType::Object, Path::new("module.o")) .write_to_file(&main, FileType::Object, Path::new("module.o"))
.expect("couldn't write module to file"); .expect("couldn't write module to file");