core: Add use_demo_lib option

Allows injecting calls to demo library functions for debugging.
This commit is contained in:
David Mak 2024-05-30 13:23:08 +08:00
parent 520e1adc56
commit 4d0d4e0eae
6 changed files with 377 additions and 37 deletions

View File

@ -14,7 +14,7 @@ use inkwell::{
OptimizationLevel, OptimizationLevel,
}; };
use itertools::Itertools; use itertools::Itertools;
use nac3core::codegen::{CodeGenLLVMOptions, CodeGenTargetMachineOptions, gen_func_impl}; use nac3core::codegen::{CodeGenLLVMOptions, CodeGenOptions, CodeGenTargetMachineOptions, gen_func_impl};
use nac3core::toplevel::builtins::get_exn_constructor; use nac3core::toplevel::builtins::get_exn_constructor;
use nac3core::typecheck::typedef::{TypeEnum, Unifier, VarMap}; use nac3core::typecheck::typedef::{TypeEnum, Unifier, VarMap};
use nac3parser::{ use nac3parser::{
@ -113,8 +113,8 @@ struct Nac3 {
string_store: Arc<RwLock<HashMap<String, i32>>>, string_store: Arc<RwLock<HashMap<String, i32>>>,
exception_ids: Arc<RwLock<HashMap<usize, usize>>>, exception_ids: Arc<RwLock<HashMap<usize, usize>>>,
deferred_eval_store: DeferredEvaluationStore, deferred_eval_store: DeferredEvaluationStore,
/// LLVM-related options for code generation. /// Options for code generation.
llvm_options: CodeGenLLVMOptions, codegen_options: CodeGenOptions,
} }
create_exception!(nac3artiq, CompileError, exceptions::PyException); create_exception!(nac3artiq, CompileError, exceptions::PyException);
@ -607,7 +607,7 @@ impl Nac3 {
let (registry, handles) = WorkerRegistry::create_workers( let (registry, handles) = WorkerRegistry::create_workers(
threads, threads,
top_level.clone(), top_level.clone(),
&self.llvm_options, &self.codegen_options,
&f &f
); );
registry.add_task(task); registry.add_task(task);
@ -674,13 +674,13 @@ impl Nac3 {
global_option = global.get_next_global(); global_option = global.get_next_global();
} }
let target_machine = self.llvm_options.target let target_machine = self.codegen_options.llvm.target
.create_target_machine(self.llvm_options.opt_level) .create_target_machine(self.codegen_options.llvm.opt_level)
.expect("couldn't create target machine"); .expect("couldn't create target machine");
let pass_options = PassBuilderOptions::create(); let pass_options = PassBuilderOptions::create();
pass_options.set_merge_functions(true); pass_options.set_merge_functions(true);
let passes = format!("default<O{}>", self.llvm_options.opt_level as u32); let passes = format!("default<O{}>", self.codegen_options.llvm.opt_level as u32);
let result = main.run_passes(passes.as_str(), &target_machine, pass_options); let result = main.run_passes(passes.as_str(), &target_machine, pass_options);
if let Err(err) = result { if let Err(err) = result {
panic!("Failed to run optimization for module `main`: {}", err.to_string()); panic!("Failed to run optimization for module `main`: {}", err.to_string());
@ -733,7 +733,7 @@ impl Nac3 {
/// target [isa]. /// target [isa].
fn get_llvm_target_machine(&self) -> TargetMachine { fn get_llvm_target_machine(&self) -> TargetMachine {
Nac3::get_llvm_target_options(self.isa) Nac3::get_llvm_target_options(self.isa)
.create_target_machine(self.llvm_options.opt_level) .create_target_machine(self.codegen_options.llvm.opt_level)
.expect("couldn't create target machine") .expect("couldn't create target machine")
} }
} }
@ -908,10 +908,13 @@ impl Nac3 {
string_store: Arc::default(), string_store: Arc::default(),
exception_ids: Arc::default(), exception_ids: Arc::default(),
deferred_eval_store: DeferredEvaluationStore::new(), deferred_eval_store: DeferredEvaluationStore::new(),
llvm_options: CodeGenLLVMOptions { codegen_options: CodeGenOptions {
opt_level: OptimizationLevel::Default, use_demo_lib: false,
target: Nac3::get_llvm_target_options(isa), llvm: CodeGenLLVMOptions {
} opt_level: OptimizationLevel::Default,
target: Nac3::get_llvm_target_options(isa),
},
},
}) })
} }

View File

@ -738,7 +738,7 @@ pub fn call_numpy_min<'ctx, G: CodeGenerator + ?Sized>(
let n = NDArrayValue::from_ptr_val(n, llvm_usize, None); let n = NDArrayValue::from_ptr_val(n, llvm_usize, None);
let n_sz = irrt::call_ndarray_calc_size(generator, ctx, &n.dim_sizes()); let n_sz = irrt::call_ndarray_calc_size(generator, ctx, &n.dim_sizes());
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None { if ctx.registry.codegen_options.llvm.opt_level == OptimizationLevel::None {
let n_sz_eqz = ctx.builder let n_sz_eqz = ctx.builder
.build_int_compare( .build_int_compare(
IntPredicate::NE, IntPredicate::NE,
@ -956,7 +956,7 @@ pub fn call_numpy_max<'ctx, G: CodeGenerator + ?Sized>(
let n = NDArrayValue::from_ptr_val(n, llvm_usize, None); let n = NDArrayValue::from_ptr_val(n, llvm_usize, None);
let n_sz = irrt::call_ndarray_calc_size(generator, ctx, &n.dim_sizes()); let n_sz = irrt::call_ndarray_calc_size(generator, ctx, &n.dim_sizes());
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None { if ctx.registry.codegen_options.llvm.opt_level == OptimizationLevel::None {
let n_sz_eqz = ctx.builder let n_sz_eqz = ctx.builder
.build_int_compare( .build_int_compare(
IntPredicate::NE, IntPredicate::NE,

View File

@ -0,0 +1,316 @@
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue, PointerValue};
use itertools::Either;
use crate::codegen::{CodeGenContext, CodeGenerator};
/// Invokes `dbl_nan` in the demo library.
pub fn call_dbl_nan<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>) -> FloatValue<'ctx> {
const FN_NAME: &str = "dbl_nan";
assert!(ctx.registry.codegen_options.use_demo_lib);
let llvm_f64 = ctx.ctx.f64_type();
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"mustprogress",
"nofree",
"norecurse",
"nosync",
"nounwind",
"sspstrong",
"willreturn",
"readnone",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[], "")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Invokes `dbl_inf` in the demo library.
pub fn call_dbl_inf<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>) -> FloatValue<'ctx> {
const FN_NAME: &str = "dbl_inf";
assert!(ctx.registry.codegen_options.use_demo_lib);
let llvm_f64 = ctx.ctx.f64_type();
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_f64.fn_type(&[], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"mustprogress",
"nofree",
"norecurse",
"nosync",
"nounwind",
"sspstrong",
"willreturn",
"readnone",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[], "")
.map(CallSiteValue::try_as_basic_value)
.map(|v| v.map_left(BasicValueEnum::into_float_value))
.map(Either::unwrap_left)
.unwrap()
}
/// Invokes `output_bool` in the demo library.
pub fn call_output_bool<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_bool";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i1 = ctx.ctx.bool_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i1.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i1.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_int32` in the demo library.
pub fn call_output_int32<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_int32";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i32 = ctx.ctx.i32_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i32.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i32.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_int64` in the demo library.
pub fn call_output_int64<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_int64";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i64 = ctx.ctx.i64_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i64.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i64.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_uint32` in the demo library.
pub fn call_output_uint32<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_uint32";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i32 = ctx.ctx.i32_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i32.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i32.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_uint64` in the demo library.
pub fn call_output_uint64<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
const FN_NAME: &str = "output_uint64";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_i64 = ctx.ctx.i64_type();
debug_assert_eq!(value.get_type().get_bit_width(), llvm_i64.get_bit_width());
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_i64.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_float64` in the demo library.
pub fn call_output_float64<'ctx>(ctx: &mut CodeGenContext<'ctx, '_>, value: FloatValue<'ctx>) {
const FN_NAME: &str = "output_float64";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_f64 = ctx.ctx.f64_type();
debug_assert_eq!(value.get_type(), llvm_f64);
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_f64.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}
/// Invokes `output_str` in the demo library.
pub fn call_output_str<'ctx>(
generator: &mut dyn CodeGenerator,
ctx: &mut CodeGenContext<'ctx, '_>,
value: PointerValue<'ctx>,
) {
const FN_NAME: &str = "output_str";
if ctx.registry.codegen_options.use_demo_lib {
let llvm_void = ctx.ctx.void_type();
let llvm_str = ctx.get_llvm_type(generator, ctx.primitives.str).into_pointer_type();
debug_assert_eq!(value.get_type(), llvm_str);
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
let fn_type = llvm_void.fn_type(&[llvm_str.into()], false);
let func = ctx.module.add_function(FN_NAME, fn_type, None);
for attr in [
"nofree",
"nounwind",
"sspstrong",
] {
func.add_attribute(
AttributeLoc::Function,
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0)
);
}
func
});
ctx.builder
.build_call(extern_fn, &[value.into()], "")
.unwrap();
}
}

View File

@ -42,6 +42,7 @@ use std::thread;
pub mod builtin_fns; pub mod builtin_fns;
pub mod classes; pub mod classes;
pub mod concrete_type; pub mod concrete_type;
pub mod demo_fns;
pub mod expr; pub mod expr;
pub mod extern_fns; pub mod extern_fns;
mod generator; mod generator;
@ -64,6 +65,16 @@ pub struct StaticValueStore {
pub type VarValue<'ctx> = (PointerValue<'ctx>, Option<Arc<dyn StaticValue + Send + Sync>>, i64); pub type VarValue<'ctx> = (PointerValue<'ctx>, Option<Arc<dyn StaticValue + Send + Sync>>, i64);
/// Additional options for codegen.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CodeGenOptions {
/// Whether to use the demo library during codegen.
pub use_demo_lib: bool,
/// Options related to LLVM codegen.
pub llvm: CodeGenLLVMOptions,
}
/// Additional options for LLVM during codegen. /// Additional options for LLVM during codegen.
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
pub struct CodeGenLLVMOptions { pub struct CodeGenLLVMOptions {
@ -245,8 +256,8 @@ pub struct WorkerRegistry {
top_level_ctx: Arc<TopLevelContext>, top_level_ctx: Arc<TopLevelContext>,
static_value_store: Arc<Mutex<StaticValueStore>>, static_value_store: Arc<Mutex<StaticValueStore>>,
/// LLVM-related options for code generation. /// Code generation options.
pub llvm_options: CodeGenLLVMOptions, pub codegen_options: CodeGenOptions,
} }
impl WorkerRegistry { impl WorkerRegistry {
@ -256,7 +267,7 @@ impl WorkerRegistry {
pub fn create_workers<G: CodeGenerator + Send + 'static>( pub fn create_workers<G: CodeGenerator + Send + 'static>(
generators: Vec<Box<G>>, generators: Vec<Box<G>>,
top_level_ctx: Arc<TopLevelContext>, top_level_ctx: Arc<TopLevelContext>,
llvm_options: &CodeGenLLVMOptions, codegen_options: &CodeGenOptions,
f: &Arc<WithCall>, f: &Arc<WithCall>,
) -> (Arc<WorkerRegistry>, Vec<thread::JoinHandle<()>>) { ) -> (Arc<WorkerRegistry>, Vec<thread::JoinHandle<()>>) {
let (sender, receiver) = unbounded(); let (sender, receiver) = unbounded();
@ -277,7 +288,7 @@ impl WorkerRegistry {
task_count, task_count,
wait_condvar, wait_condvar,
top_level_ctx, top_level_ctx,
llvm_options: llvm_options.clone(), codegen_options: codegen_options.clone(),
}); });
let mut handles = Vec::new(); let mut handles = Vec::new();
@ -382,11 +393,12 @@ impl WorkerRegistry {
let pass_options = PassBuilderOptions::create(); let pass_options = PassBuilderOptions::create();
let target_machine = self let target_machine = self
.llvm_options .codegen_options
.llvm
.target .target
.create_target_machine(self.llvm_options.opt_level) .create_target_machine(self.codegen_options.llvm.opt_level)
.unwrap_or_else(|| panic!("could not create target machine from properties {:?}", self.llvm_options.target)); .unwrap_or_else(|| panic!("could not create target machine from properties {:?}", self.codegen_options.llvm.target));
let passes = format!("default<O{}>", self.llvm_options.opt_level as u32); let passes = format!("default<O{}>", self.codegen_options.llvm.opt_level as u32);
let result = module.run_passes(passes.as_str(), &target_machine, pass_options); let result = module.run_passes(passes.as_str(), &target_machine, pass_options);
if let Err(err) = result { if let Err(err) = result {
panic!("Failed to run optimization for module `{}`: {}", panic!("Failed to run optimization for module `{}`: {}",
@ -828,7 +840,7 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte
), ),
/* directory */ "", /* directory */ "",
/* producer */ "NAC3", /* producer */ "NAC3",
/* is_optimized */ registry.llvm_options.opt_level != OptimizationLevel::None, /* is_optimized */ registry.codegen_options.llvm.opt_level != OptimizationLevel::None,
/* compiler command line flags */ "", /* compiler command line flags */ "",
/* runtime_ver */ 0, /* runtime_ver */ 0,
/* split_name */ "", /* split_name */ "",
@ -863,7 +875,7 @@ pub fn gen_func_impl<'ctx, G: CodeGenerator, F: FnOnce(&mut G, &mut CodeGenConte
/* is_definition */ true, /* is_definition */ true,
/* scope_line */ row as u32, /* scope_line */ row as u32,
/* flags */ inkwell::debug_info::DIFlags::PUBLIC, /* flags */ inkwell::debug_info::DIFlags::PUBLIC,
/* is_optimized */ registry.llvm_options.opt_level != OptimizationLevel::None, /* is_optimized */ registry.codegen_options.llvm.opt_level != OptimizationLevel::None,
); );
fn_val.set_subprogram(func_scope); fn_val.set_subprogram(func_scope);

View File

@ -937,7 +937,7 @@ pub fn ndarray_matmul_2d<'ctx, G: CodeGenerator>(
} }
} }
if ctx.registry.llvm_options.opt_level == OptimizationLevel::None { if ctx.registry.codegen_options.llvm.opt_level == OptimizationLevel::None {
let lhs_dim1 = unsafe { let lhs_dim1 = unsafe {
lhs.dim_sizes().get_typed_unchecked(ctx, generator, &llvm_usize.const_int(1, false), None) lhs.dim_sizes().get_typed_unchecked(ctx, generator, &llvm_usize.const_int(1, false), None)
}; };

View File

@ -12,7 +12,7 @@ use std::collections::HashSet;
use nac3core::{ use nac3core::{
codegen::{ codegen::{
concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions, concrete_type::ConcreteTypeStore, irrt::load_irrt, CodeGenLLVMOptions, CodeGenOptions,
CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry, CodeGenTargetMachineOptions, CodeGenTask, DefaultCodeGenerator, WithCall, WorkerRegistry,
}, },
symbol_resolver::SymbolResolver, symbol_resolver::SymbolResolver,
@ -68,6 +68,11 @@ struct CommandLineArgs {
/// Additional target features to enable/disable, specified using the `+`/`-` prefixes. /// Additional target features to enable/disable, specified using the `+`/`-` prefixes.
#[arg(long)] #[arg(long)]
target_features: Option<String>, target_features: Option<String>,
/// Enables the demo library for use in compiled executables. This requires `demo.o` to be
/// present when linking the executable.
#[arg(long, default_value_t = false)]
fuse_demo_lib: bool,
} }
fn handle_typevar_definition( fn handle_typevar_definition(
@ -258,6 +263,7 @@ fn main() {
triple, triple,
mcpu, mcpu,
target_features, target_features,
fuse_demo_lib,
} = cli; } = cli;
Target::initialize_all(&InitializationConfig::default()); Target::initialize_all(&InitializationConfig::default());
@ -371,14 +377,17 @@ fn main() {
instance_to_stmt[""].clone() instance_to_stmt[""].clone()
}; };
let llvm_options = CodeGenLLVMOptions { let codegen_options = CodeGenOptions {
opt_level, use_demo_lib: fuse_demo_lib,
target: CodeGenTargetMachineOptions { llvm: CodeGenLLVMOptions {
triple, opt_level,
cpu: mcpu, target: CodeGenTargetMachineOptions {
features: target_features, triple,
reloc_mode: RelocMode::PIC, cpu: mcpu,
..host_target_machine features: target_features,
reloc_mode: RelocMode::PIC,
..host_target_machine
},
}, },
}; };
@ -405,7 +414,7 @@ fn main() {
let threads = (0..threads) let threads = (0..threads)
.map(|i| Box::new(DefaultCodeGenerator::new(format!("module{i}"), SIZE_T))) .map(|i| Box::new(DefaultCodeGenerator::new(format!("module{i}"), SIZE_T)))
.collect(); .collect();
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, &llvm_options, &f); let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, &codegen_options, &f);
registry.add_task(task); registry.add_task(task);
registry.wait_tasks_complete(handles); registry.wait_tasks_complete(handles);
@ -444,8 +453,8 @@ fn main() {
function_iter = func.get_next_function(); function_iter = func.get_next_function();
} }
let target_machine = llvm_options.target let target_machine = codegen_options.llvm.target
.create_target_machine(llvm_options.opt_level) .create_target_machine(codegen_options.llvm.opt_level)
.expect("couldn't create target machine"); .expect("couldn't create target machine");
let pass_options = PassBuilderOptions::create(); let pass_options = PassBuilderOptions::create();