use either::Either; use itertools::Itertools; use llvm_ir::function::{FunctionAttribute, ParameterAttribute}; use llvm_ir::instruction; use llvm_ir::module::{Alignment, Endianness, Mangling, PointerLayout}; use llvm_ir::terminator; use llvm_ir::types::{FPType, NamedStructDef, Typed}; use llvm_ir::HasDebugLoc; use llvm_ir::{ Constant, ConstantRef, Instruction, IntPredicate, Module, Name, Operand, Terminator, Type, }; #[cfg(feature = "llvm-16-or-greater")] use llvm_ir::function::MemoryEffect; use std::convert::TryInto; use std::path::{Path, PathBuf}; fn init_logging() { // capture log messages with test harness let _ = env_logger::builder().is_test(true).try_init(); } const BC_DIR: &str = "tests/basic_bc/"; // Test against bitcode compiled with the same version of LLVM fn llvm_bc_dir() -> PathBuf { if cfg!(feature = "llvm-9") { Path::new(BC_DIR).join("llvm9") } else if cfg!(feature = "llvm-10") { Path::new(BC_DIR).join("llvm10") } else if cfg!(feature = "llvm-11") { Path::new(BC_DIR).join("llvm11") } else if cfg!(feature = "llvm-12") { Path::new(BC_DIR).join("llvm12") } else if cfg!(feature = "llvm-13") { Path::new(BC_DIR).join("llvm13") } else if cfg!(feature = "llvm-14") { Path::new(BC_DIR).join("llvm14") } else if cfg!(feature = "llvm-15") { Path::new(BC_DIR).join("llvm15") } else if cfg!(feature = "llvm-16") { Path::new(BC_DIR).join("llvm16") } else if cfg!(feature = "llvm-17") { Path::new(BC_DIR).join("llvm17") } else if cfg!(feature = "llvm-18") { Path::new(BC_DIR).join("llvm18") } else if cfg!(feature = "llvm-19") { Path::new(BC_DIR).join("llvm19") } else { unimplemented!("new llvm version?") } } // Test against bitcode compiled with the same version of LLVM fn cxx_llvm_bc_dir() -> PathBuf { if cfg!(feature = "llvm-9") { Path::new(BC_DIR).join("cxx-llvm9") } else if cfg!(feature = "llvm-10") { Path::new(BC_DIR).join("cxx-llvm10") } else if cfg!(feature = "llvm-11") { Path::new(BC_DIR).join("cxx-llvm11") } else if cfg!(feature = "llvm-12") { Path::new(BC_DIR).join("cxx-llvm12") } else if cfg!(feature = "llvm-13") { Path::new(BC_DIR).join("cxx-llvm13") } else if cfg!(feature = "llvm-14") { Path::new(BC_DIR).join("cxx-llvm14") } else if cfg!(feature = "llvm-15") { Path::new(BC_DIR).join("cxx-llvm15") } else if cfg!(feature = "llvm-16") { Path::new(BC_DIR).join("cxx-llvm16") } else if cfg!(feature = "llvm-17") { Path::new(BC_DIR).join("cxx-llvm17") } else if cfg!(feature = "llvm-18") { Path::new(BC_DIR).join("cxx-llvm18") } else if cfg!(feature = "llvm-19") { Path::new(BC_DIR).join("cxx-llvm19") } else { unimplemented!("new llvm version?") } } fn rust_bc_dir() -> PathBuf { Path::new(BC_DIR).join("rust") } #[test] fn hellobc() { init_logging(); let path = llvm_bc_dir().join("hello.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); assert_eq!(&module.name, &path.to_str().unwrap()); assert_eq!(module.source_file_name, "hello.c"); #[cfg(feature = "llvm-10-or-lower")] assert_eq!( module.target_triple, Some("x86_64-apple-macosx10.16.0".into()) ); #[cfg(any(feature = "llvm-11", feature = "llvm-12", feature = "llvm-13"))] assert_eq!( module.target_triple, Some("x86_64-apple-macosx11.0.0".into()) ); #[cfg(feature = "llvm-14-or-greater")] assert_eq!( module.target_triple, Some("x86_64-apple-macosx12.0.0".into()) ); assert_eq!(module.functions.len(), 1); let func = &module.functions[0]; assert_eq!(func.name, "main"); assert_eq!(func.parameters.len(), 0); assert_eq!(func.is_var_arg, false); assert_eq!(func.return_type, module.types.int(32)); assert_eq!(func.basic_blocks.len(), 1); let bb = &func.basic_blocks[0]; assert_eq!(bb.name, Name::Number(0)); assert_eq!(bb.instrs.len(), 0); let ret: &terminator::Ret = &bb .term .clone() .try_into() .unwrap_or_else(|_| panic!("Terminator should be a Ret but is {:?}", &bb.term)); assert_eq!( ret.return_operand, Some(Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 0 }))) ); assert_eq!(&ret.to_string(), "ret i32 0"); // this file was compiled without debuginfo, so nothing should have a debugloc assert_eq!(func.debugloc, None); assert_eq!(ret.debugloc, None); } // this test relates to the version of the file compiled with debuginfo #[test] fn hellobcg() { init_logging(); let path = llvm_bc_dir().join("hello.bc-g"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); assert_eq!(&module.name, &path.to_str().unwrap()); assert_eq!(module.source_file_name, "hello.c"); let debug_filename = "hello.c"; let debug_directory_suffix = "/tests/basic_bc"; let func = &module.functions[0]; assert_eq!(func.name, "main"); let debugloc = func .get_debug_loc() .as_ref() .expect("Expected main() to have a debugloc"); assert_eq!(debugloc.line, 3); assert_eq!(debugloc.col, None); assert_eq!(debugloc.filename, debug_filename); assert!(debugloc.directory.as_ref().expect("directory should exist").ends_with(debug_directory_suffix)); let bb = &func.basic_blocks[0]; let ret: &terminator::Ret = &bb .term .clone() .try_into() .unwrap_or_else(|_| panic!("Terminator should be a Ret but is {:?}", &bb.term)); let debugloc = ret .get_debug_loc() .as_ref() .expect("expected the Ret to have a debugloc"); assert_eq!(debugloc.line, 4); assert_eq!(debugloc.col, Some(3)); assert_eq!(debugloc.filename, debug_filename); assert!(debugloc.directory.as_ref().expect("directory should exist").ends_with(debug_directory_suffix)); assert_eq!(&ret.to_string(), "ret i32 0 (with debugloc)"); } #[test] #[allow(clippy::cognitive_complexity)] fn loopbc() { init_logging(); let path = llvm_bc_dir().join("loop.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); // get function and check info on it assert_eq!(module.functions.len(), 1); let func = &module.functions[0]; assert_eq!(func.name, "loop"); assert_eq!(func.parameters.len(), 2); assert_eq!(func.is_var_arg, false); assert_eq!(func.return_type, module.types.void()); assert_eq!( module.type_of(func), module.types.func_type( module.types.void(), vec![module.types.i32(), module.types.i32()], false, ) ); assert_eq!(module.get_func_by_name("loop"), Some(func)); // get parameters and check info on them let param0 = &func.parameters[0]; let param1 = &func.parameters[1]; assert_eq!(param0.name, Name::Number(0)); assert_eq!(param1.name, Name::Number(1)); assert_eq!(param0.ty, module.types.i32()); assert_eq!(param1.ty, module.types.i32()); assert_eq!(module.type_of(param0), module.types.i32()); assert_eq!(module.type_of(param1), module.types.i32()); // get basic blocks and check their names // different LLVM versions end up with different numbers of basic blocks for this function #[cfg(feature = "llvm-9-or-lower")] let bbs = { assert_eq!(func.basic_blocks.len(), 6); let bb2 = &func.basic_blocks[0]; let bb7 = &func.basic_blocks[1]; let bb10 = &func.basic_blocks[2]; let bb14 = &func.basic_blocks[3]; let bb19 = &func.basic_blocks[4]; let bb22 = &func.basic_blocks[5]; assert_eq!(bb2.name, Name::Number(2)); assert_eq!(bb7.name, Name::Number(7)); assert_eq!(bb10.name, Name::Number(10)); assert_eq!(bb14.name, Name::Number(14)); assert_eq!(bb19.name, Name::Number(19)); assert_eq!(bb22.name, Name::Number(22)); assert_eq!(func.get_bb_by_name(&Name::Number(2)), Some(bb2)); assert_eq!(func.get_bb_by_name(&Name::Number(19)), Some(bb19)); vec![bb2, bb7, bb10, bb14, bb19, bb22] }; #[cfg(feature = "llvm-10")] let bbs = { assert_eq!(func.basic_blocks.len(), 4); let bb2 = &func.basic_blocks[0]; let bb7 = &func.basic_blocks[1]; let bb12 = &func.basic_blocks[2]; let bb21 = &func.basic_blocks[3]; assert_eq!(bb2.name, Name::Number(2)); assert_eq!(bb7.name, Name::Number(7)); assert_eq!(bb12.name, Name::Number(12)); assert_eq!(bb21.name, Name::Number(21)); assert_eq!(func.get_bb_by_name(&Name::Number(2)), Some(bb2)); assert_eq!(func.get_bb_by_name(&Name::Number(12)), Some(bb12)); vec![bb2, bb7, bb12, bb21] }; #[cfg(feature = "llvm-11")] let bbs = { assert_eq!(func.basic_blocks.len(), 4); let bb2 = &func.basic_blocks[0]; let bb7 = &func.basic_blocks[1]; let bb14 = &func.basic_blocks[2]; let bb24 = &func.basic_blocks[3]; assert_eq!(bb2.name, Name::Number(2)); assert_eq!(bb7.name, Name::Number(7)); assert_eq!(bb14.name, Name::Number(14)); assert_eq!(bb24.name, Name::Number(24)); assert_eq!(func.get_bb_by_name(&Name::Number(2)), Some(bb2)); assert_eq!(func.get_bb_by_name(&Name::Number(14)), Some(bb14)); vec![bb2, bb7, bb14, bb24] }; #[cfg(feature = "llvm-12")] let bbs = { assert_eq!(func.basic_blocks.len(), 8); // LLVM 12+ seems to do some unrolling in this example that previous LLVMs didn't let bb2 = &func.basic_blocks[0]; let bb7 = &func.basic_blocks[1]; let bb12 = &func.basic_blocks[2]; let bb17 = &func.basic_blocks[3]; let bb19 = &func.basic_blocks[4]; let bb47 = &func.basic_blocks[7]; // actually have 8 BBs, but we only use the first five and the last one assert_eq!(bb2.name, Name::Number(2)); assert_eq!(bb7.name, Name::Number(7)); assert_eq!(bb12.name, Name::Number(12)); assert_eq!(bb17.name, Name::Number(17)); assert_eq!(bb19.name, Name::Number(19)); assert_eq!(bb47.name, Name::Number(47)); vec![bb2, bb7, bb12, bb17, bb19, bb47] }; #[cfg(feature = "llvm-13")] let bbs = { assert_eq!(func.basic_blocks.len(), 9); let bb2 = &func.basic_blocks[0]; let bb6 = &func.basic_blocks[1]; let bb12 = &func.basic_blocks[3]; let bb17 = &func.basic_blocks[4]; let bb19 = &func.basic_blocks[5]; let bb47 = &func.basic_blocks[8]; // actually have 9 BBs, but we only use these selected 6 assert_eq!(bb2.name, Name::Number(2)); assert_eq!(bb6.name, Name::Number(6)); assert_eq!(bb12.name, Name::Number(12)); assert_eq!(bb17.name, Name::Number(17)); assert_eq!(bb19.name, Name::Number(19)); assert_eq!(bb47.name, Name::Number(47)); vec![bb2, bb6, bb12, bb17, bb19, bb47] }; #[cfg(feature = "llvm-14")] let bbs = { assert_eq!(func.basic_blocks.len(), 8); let bb2 = &func.basic_blocks[0]; let bb7 = &func.basic_blocks[1]; let bb11 = &func.basic_blocks[2]; let bb16 = &func.basic_blocks[3]; let bb18 = &func.basic_blocks[4]; let bb46 = &func.basic_blocks[7]; // actually have 8 BBs, but we only use the first five and the last one assert_eq!(bb2.name, Name::Number(2)); assert_eq!(bb7.name, Name::Number(7)); assert_eq!(bb11.name, Name::Number(11)); assert_eq!(bb16.name, Name::Number(16)); assert_eq!(bb18.name, Name::Number(18)); assert_eq!(bb46.name, Name::Number(46)); vec![bb2, bb7, bb11, bb16, bb18, bb46] }; #[cfg(all(feature = "llvm-15-or-greater", feature = "llvm-17-or-lower"))] let bbs = { assert_eq!(func.basic_blocks.len(), 8); let bb2 = &func.basic_blocks[0]; let bb7 = &func.basic_blocks[1]; let bb11 = &func.basic_blocks[2]; let bb16 = &func.basic_blocks[3]; let bb18 = &func.basic_blocks[4]; let bb46 = &func.basic_blocks[7]; // actually have 8 BBs, but we only use the first five and the last one assert_eq!(bb2.name, Name::Number(2)); assert_eq!(bb7.name, Name::Number(6)); assert_eq!(bb11.name, Name::Number(9)); assert_eq!(bb16.name, Name::Number(14)); assert_eq!(bb18.name, Name::Number(16)); assert_eq!(bb46.name, Name::Number(44)); vec![bb2, bb7, bb11, bb16, bb18, bb46] }; #[cfg(feature = "llvm-18-or-greater")] let bbs = { assert_eq!(func.basic_blocks.len(), 8); let bb2 = &func.basic_blocks[0]; let bb7 = &func.basic_blocks[1]; let bb11 = &func.basic_blocks[2]; let bb16 = &func.basic_blocks[3]; let bb18 = &func.basic_blocks[4]; let bb46 = &func.basic_blocks[7]; // Some extra optimization removes a few instructions in Clang 18, // c.f. Clang 17 assert_eq!(bb2.name, Name::Number(2)); assert_eq!(bb7.name, Name::Number(6)); assert_eq!(bb11.name, Name::Number(9)); assert_eq!(bb16.name, Name::Number(14)); assert_eq!(bb18.name, Name::Number(16)); assert_eq!(bb46.name, Name::Number(41)); vec![bb2, bb7, bb11, bb16, bb18, bb46] }; // check details about the instructions in basic block %2 let alloca: &instruction::Alloca = &bbs[0].instrs[0] .clone() .try_into() .expect("Should be an alloca"); assert_eq!(alloca.dest, Name::Number(3)); let allocated_type = module.types.array_of(module.types.i32(), 10); assert_eq!(alloca.allocated_type, allocated_type); assert_eq!( alloca.num_elements, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 1 })) // One element, which is an array of 10 elements. Not 10 elements, each of which are i32. ); assert_eq!(alloca.alignment, 16); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( module.type_of(alloca), module.types.pointer_to(allocated_type.clone()), ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(module.type_of(alloca), module.types.pointer()); assert_eq!(module.type_of(&alloca.num_elements), module.types.i32()); assert_eq!(&alloca.to_string(), "%3 = alloca [10 x i32], align 16"); #[cfg(feature = "llvm-14-or-lower")] // LLVM 15+ does not require bitcasts in this function { let bitcast: &instruction::BitCast = &bbs[0].instrs[1] .clone() .try_into() .expect("Should be a bitcast"); assert_eq!(bitcast.dest, Name::Number(4)); assert_eq!(bitcast.to_type, module.types.pointer_to(module.types.i8())); assert_eq!( bitcast.operand, Operand::LocalOperand { name: Name::Number(3), ty: module.types.pointer_to(allocated_type.clone()) } ); assert_eq!( module.type_of(bitcast), module.types.pointer_to(module.types.i8()) ); assert_eq!( module.type_of(&bitcast.operand), module.types.pointer_to(allocated_type.clone()) ); assert_eq!(&bitcast.to_string(), "%4 = bitcast [10 x i32]* %3 to i8*"); } #[cfg(feature = "llvm-14-or-lower")] let lifetimestart: &instruction::Call = &bbs[0].instrs[2] .clone() .try_into() .expect("Should be a call"); #[cfg(feature = "llvm-15-or-greater")] let lifetimestart: &instruction::Call = &bbs[0].instrs[1] .clone() .try_into() .expect("Should be a call"); if let Either::Right(Operand::ConstantOperand(cref)) = &lifetimestart.function { if let Constant::GlobalReference { ref name, ref ty } = cref.as_ref() { assert!(matches!( module.type_of(&lifetimestart.function).as_ref(), Type::PointerType { .. } )); // lifetimestart.function should be a constant function pointer #[cfg(feature = "llvm-14-or-lower")] assert_eq!(*name, Name::from("llvm.lifetime.start.p0i8")); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(*name, Name::from("llvm.lifetime.start.p0")); if let Type::FuncType { result_type, param_types, is_var_arg, } = ty.as_ref() { assert_eq!(result_type, &module.types.void()); assert_eq!( param_types, &vec![ module.types.i64(), #[cfg(feature = "llvm-14-or-lower")] module.types.pointer_to(module.types.i8()), #[cfg(feature = "llvm-15-or-greater")] module.types.pointer(), ] ); assert_eq!(*is_var_arg, false); } else { panic!("lifetimestart.function has unexpected type {:?}", ty); } } else { panic!( "lifetimestart.function not a GlobalReference as expected; it is actually another kind of Constant: {:?}", cref ); } } else { panic!( "lifetimestart.function not a GlobalReference as expected; it is actually {:?}", &lifetimestart.function ); } let arg0 = &lifetimestart .arguments .get(0) .expect("Expected an argument 0"); let arg1 = &lifetimestart .arguments .get(1) .expect("Expected an argument 1"); assert_eq!( arg0.0, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 40 })) ); #[cfg(feature = "llvm-14-or-lower")] let arg1_expected_name = Name::Number(4); #[cfg(feature = "llvm-15-or-greater")] let arg1_expected_name = Name::Number(3); #[cfg(feature = "llvm-14-or-lower")] let arg1_expected_ty = module.types.pointer_to(module.types.i8()); #[cfg(feature = "llvm-15-or-greater")] let arg1_expected_ty = module.types.pointer(); assert_eq!( arg1.0, Operand::LocalOperand { name: arg1_expected_name, ty: arg1_expected_ty, } ); assert_eq!(arg0.1, vec![]); // should have no parameter attributes assert_eq!(arg1.1.len(), 1); // should have one parameter attribute assert_eq!(lifetimestart.dest, None); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "call @llvm.lifetime.start.p0i8(i64 40, i8* %4)"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "call @llvm.lifetime.start.p0(i64 40, ptr %3)"; assert_eq!(&lifetimestart.to_string(), expected_fmt); #[cfg(feature = "llvm-14-or-lower")] let memset: &instruction::Call = &bbs[0].instrs[3] .clone() .try_into() .expect("Should be a call"); #[cfg(feature = "llvm-15-or-greater")] let memset: &instruction::Call = &bbs[0].instrs[2] .clone() .try_into() .expect("Should be a call"); if let Either::Right(Operand::ConstantOperand(cref)) = &memset.function { if let Constant::GlobalReference { ref name, ref ty } = cref.as_ref() { #[cfg(feature = "llvm-14-or-lower")] assert_eq!(*name, Name::from("llvm.memset.p0i8.i64")); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(*name, Name::from("llvm.memset.p0.i64")); if let Type::FuncType { result_type, param_types, is_var_arg, } = ty.as_ref() { assert_eq!(result_type, &module.types.void()); assert_eq!( param_types, &vec![ #[cfg(feature = "llvm-14-or-lower")] module.types.pointer_to(module.types.i8()), #[cfg(feature = "llvm-15-or-greater")] module.types.pointer(), module.types.i8(), module.types.i64(), module.types.bool() ] ); assert_eq!(*is_var_arg, false); } else { panic!("memset.function has unexpected type {:?}", ty); } } else { panic!( "memset.function not a GlobalReference as expected; it is actually another kind of Constant: {:?}", cref ); } } else { panic!( "memset.function not a GlobalReference as expected; it is actually {:?}", memset.function ); } assert_eq!(memset.arguments.len(), 4); #[cfg(feature = "llvm-14-or-lower")] let memset_arg0_expected_name = Name::Number(4); #[cfg(feature = "llvm-15-or-greater")] let memset_arg0_expected_name = Name::Number(3); #[cfg(feature = "llvm-14-or-lower")] let memset_arg0_expected_ty = module.types.pointer_to(module.types.i8()); #[cfg(feature = "llvm-15-or-greater")] let memset_arg0_expected_ty = module.types.pointer(); assert_eq!( memset.arguments[0].0, Operand::LocalOperand { name: memset_arg0_expected_name, ty: memset_arg0_expected_ty, } ); assert_eq!( memset.arguments[1].0, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 8, value: 0 })) ); assert_eq!( memset.arguments[2].0, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 40 })) ); assert_eq!( memset.arguments[3].0, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 1, value: 1 })) ); assert_eq!(memset.arguments[0].1.len(), 2); // should have two parameter attributes #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "call @llvm.memset.p0i8.i64(i8* %4, i8 0, i64 40, i1 true)"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "call @llvm.memset.p0.i64(ptr %3, i8 0, i64 40, i1 true)"; assert_eq!(&memset.to_string(), expected_fmt); #[cfg(feature = "llvm-12-or-lower")] { let add: &instruction::Add = &bbs[0].instrs[4] .clone() .try_into() .expect("Should be an add"); assert_eq!( add.operand0, Operand::LocalOperand { name: Name::Number(1), ty: module.types.i32() } ); assert_eq!( add.operand1, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 0x0000_0000_FFFF_FFFF })) ); assert_eq!(add.dest, Name::Number(5)); assert_eq!(module.type_of(add), module.types.i32()); assert_eq!(&add.to_string(), "%5 = add i32 %1, i32 -1"); } #[cfg(feature = "llvm-13")] { let add: &instruction::Add = &bbs[1].instrs[0] .clone() .try_into() .expect("Should be an add"); assert_eq!( add.operand0, Operand::LocalOperand { name: Name::Number(0), ty: module.types.i32() } ); assert_eq!( add.operand1, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 3 })) ); assert_eq!(add.dest, Name::Number(7)); assert_eq!(module.type_of(add), module.types.i32()); assert_eq!(&add.to_string(), "%7 = add i32 %0, i32 3"); } #[cfg(feature = "llvm-14-or-greater")] { let add: &instruction::Add = &bbs[1].instrs[0] .clone() .try_into() .expect("Should be an add"); assert_eq!( add.operand0, Operand::LocalOperand { name: Name::Number(0), ty: module.types.i32() } ); assert_eq!( add.operand1, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 3 })) ); #[cfg(feature = "llvm-14-or-lower")] assert_eq!(add.dest, Name::Number(8)); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(add.dest, Name::Number(7)); assert_eq!(module.type_of(add), module.types.i32()); #[cfg(feature = "llvm-17-or-greater")] assert_eq!(add.nuw, false); #[cfg(feature = "llvm-17-or-greater")] assert_eq!(add.nsw, true); #[cfg(feature = "llvm-14-or-lower")] assert_eq!(&add.to_string(), "%8 = add i32 %0, i32 3"); #[cfg(any(feature = "llvm-15", feature = "llvm-16"))] assert_eq!(&add.to_string(), "%7 = add i32 %0, i32 3"); #[cfg(feature = "llvm-17-or-greater")] assert_eq!(&add.to_string(), "%7 = add nsw i32 %0, i32 3"); } #[cfg(feature = "llvm-12-or-lower")] { let icmp: &instruction::ICmp = &bbs[0].instrs[5] .clone() .try_into() .expect("Should be an icmp"); assert_eq!(icmp.predicate, IntPredicate::ULT); assert_eq!( icmp.operand0, Operand::LocalOperand { name: Name::Number(5), ty: module.types.i32() } ); assert_eq!( icmp.operand1, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 10 })) ); assert_eq!(module.type_of(icmp), module.types.bool()); assert_eq!(&icmp.to_string(), "%6 = icmp ult i32 %5, i32 10"); } #[cfg(feature = "llvm-13")] { let icmp: &instruction::ICmp = &bbs[0].instrs[4] .clone() .try_into() .expect("Should be an icmp"); assert_eq!(icmp.predicate, IntPredicate::SLT); assert_eq!( icmp.operand0, Operand::LocalOperand { name: Name::Number(1), ty: module.types.i32() } ); assert_eq!( icmp.operand1, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 11 })) ); assert_eq!(module.type_of(icmp), module.types.bool()); assert_eq!(&icmp.to_string(), "%5 = icmp slt i32 %1, i32 11"); } #[cfg(feature = "llvm-14-or-greater")] { #[cfg(feature = "llvm-14-or-lower")] let icmp: &instruction::ICmp = &bbs[0].instrs[5] .clone() .try_into() .expect("Should be an icmp"); #[cfg(feature = "llvm-15-or-greater")] let icmp: &instruction::ICmp = &bbs[0].instrs[4] .clone() .try_into() .expect("Should be an icmp"); assert_eq!(icmp.predicate, IntPredicate::ULT); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( icmp.operand0, Operand::LocalOperand { name: Name::Number(5), ty: module.types.i32() } ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!( icmp.operand0, Operand::LocalOperand { name: Name::Number(4), ty: module.types.i32() } ); assert_eq!( icmp.operand1, Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 10 })) ); assert_eq!(module.type_of(icmp), module.types.bool()); #[cfg(feature = "llvm-14-or-lower")] assert_eq!(&icmp.to_string(), "%6 = icmp ult i32 %5, i32 10"); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(&icmp.to_string(), "%5 = icmp ult i32 %4, i32 10"); } let condbr: &terminator::CondBr = &bbs[0].term.clone().try_into().expect("Should be a condbr"); #[cfg(feature = "llvm-12-or-lower")] let expected_condition_op = Name::Number(6); #[cfg(feature = "llvm-13")] let expected_condition_op = Name::Number(5); #[cfg(feature = "llvm-14")] let expected_condition_op = Name::Number(6); #[cfg(feature = "llvm-15-or-greater")] let expected_condition_op = Name::Number(5); assert_eq!( condbr.condition, Operand::LocalOperand { name: expected_condition_op.clone(), ty: module.types.bool() } ); #[cfg(feature = "llvm-12-or-lower")] let expected_true_dest = Name::Number(7); #[cfg(feature = "llvm-13")] let expected_true_dest = Name::Number(6); #[cfg(feature = "llvm-14")] let expected_true_dest = Name::Number(7); #[cfg(feature = "llvm-15-or-greater")] let expected_true_dest = Name::Number(6); assert_eq!(condbr.true_dest, expected_true_dest); let expected_false_dest = if cfg!(feature = "llvm-9-or-lower") { Name::Number(22) } else if cfg!(feature = "llvm-10") { Name::Number(21) } else if cfg!(feature = "llvm-11") { Name::Number(24) } else if cfg!(feature = "llvm-12") || cfg!(feature = "llvm-13") { Name::Number(47) } else if cfg!(feature = "llvm-14") { Name::Number(46) } else if cfg!(all(feature = "llvm-15-or-greater", feature = "llvm-17-or-lower")) { Name::Number(44) } else { Name::Number(41) }; assert_eq!(condbr.false_dest, expected_false_dest); assert_eq!(module.type_of(condbr), module.types.void()); assert_eq!( &condbr.to_string(), &format!( "br i1 {}, label {}, label {}", expected_condition_op, expected_true_dest, expected_false_dest, ), ); // check details about certain instructions in basic block %7 // not sure why LLVM 10+ puts a ZExt here instead of SExt. Maybe it can prove it's equivalent? // in LLVM 12+, the ZExt is in a different block #[cfg(feature = "llvm-9-or-lower")] let ext: &instruction::SExt = &bbs[1].instrs[1] .clone() .try_into() .expect("Should be a SExt"); #[cfg(feature = "llvm-10")] let ext: &instruction::ZExt = &bbs[1].instrs[1] .clone() .try_into() .expect("Should be a ZExt"); #[cfg(feature = "llvm-11")] let ext: &instruction::ZExt = &bbs[1].instrs[3] .clone() .try_into() .expect("Should be a ZExt"); #[cfg(feature = "llvm-12-or-greater")] let ext: &instruction::ZExt = &bbs[2].instrs[0] .clone() .try_into() .expect("Should be a ZExt"); let ext_input = if cfg!(feature = "llvm-10-or-lower") { Name::Number(1) } else if cfg!(any(feature = "llvm-11", feature = "llvm-12")) { Name::Number(10) } else { Name::Number(1) }; let ext_dest = if cfg!(feature = "llvm-10-or-lower") { Name::Number(9) } else if cfg!(feature = "llvm-11") { Name::Number(11) } else if cfg!(feature = "llvm-12") || cfg!(feature = "llvm-13") { Name::Number(13) } else if cfg!(feature = "llvm-14") { Name::Number(12) } else { Name::Number(10) }; assert_eq!( ext.operand, Operand::LocalOperand { name: ext_input, ty: module.types.i32() } ); assert_eq!(ext.to_type, module.types.i64()); assert_eq!(ext.dest, ext_dest); assert_eq!(module.type_of(ext), module.types.i64()); #[cfg(feature = "llvm-9-or-lower")] assert_eq!(&ext.to_string(), "%9 = sext i32 %1 to i64"); #[cfg(feature = "llvm-10")] assert_eq!(&ext.to_string(), "%9 = zext i32 %1 to i64"); #[cfg(feature = "llvm-11")] assert_eq!(&ext.to_string(), "%11 = zext i32 %10 to i64"); #[cfg(feature = "llvm-12")] assert_eq!(&ext.to_string(), "%13 = zext i32 %10 to i64"); #[cfg(feature = "llvm-13")] assert_eq!(&ext.to_string(), "%13 = zext i32 %1 to i64"); #[cfg(feature = "llvm-14")] assert_eq!(&ext.to_string(), "%12 = zext i32 %1 to i64"); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(&ext.to_string(), "%10 = zext i32 %1 to i64"); #[cfg(feature = "llvm-9-or-lower")] { // LLVM 10 and 11 don't have a Br in this function let br: &terminator::Br = &bbs[1].term.clone().try_into().expect("Should be a Br"); assert_eq!(br.dest, Name::Number(10)); assert_eq!(&br.to_string(), "br label %10"); } #[cfg(feature = "llvm-12-or-greater")] { let br: &terminator::Br = &bbs[3].term.clone().try_into().expect("Should be a Br"); #[cfg(any(feature = "llvm-12", feature = "llvm-13"))] { assert_eq!(br.dest, Name::Number(19)); assert_eq!(&br.to_string(), "br label %19"); } #[cfg(feature = "llvm-14")] { assert_eq!(br.dest, Name::Number(18)); assert_eq!(&br.to_string(), "br label %18"); } #[cfg(feature = "llvm-15-or-greater")] { assert_eq!(br.dest, Name::Number(16)); assert_eq!(&br.to_string(), "br label %16"); } } // check details about certain instructions in basic block %10 (LLVM 9-) / %12 (LLVM 10) / %14 (LLVM 11) / %19 (LLVM 12+) #[cfg(feature = "llvm-11-or-lower")] let phi: &instruction::Phi = &bbs[2].instrs[0] .clone() .try_into() .expect("Should be a Phi"); #[cfg(feature = "llvm-12-or-greater")] let phi: &instruction::Phi = &bbs[4].instrs[0] .clone() .try_into() .expect("Should be a Phi"); let phi_dest = if cfg!(feature = "llvm-9-or-lower") { Name::Number(11) } else if cfg!(feature = "llvm-10") { Name::Number(13) } else if cfg!(feature = "llvm-11") { Name::Number(15) } else if cfg!(any(feature = "llvm-12", feature = "llvm-13")) { Name::Number(20) } else if cfg!(feature = "llvm-14") { Name::Number(19) } else { Name::Number(17) }; assert_eq!(phi.dest, phi_dest); assert_eq!(phi.to_type, module.types.i64()); #[cfg(feature = "llvm-9-or-lower")] assert_eq!( phi.incoming_values, vec![ ( Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 0 })), Name::Number(7) ), ( Operand::LocalOperand { name: Name::Number(20), ty: module.types.i64() }, Name::Number(19) ), ] ); #[cfg(feature = "llvm-10")] assert_eq!( phi.incoming_values, vec![ ( Operand::LocalOperand { name: Name::Number(19), ty: module.types.i64() }, Name::Number(12) ), ( Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 1 })), Name::Number(7) ), ] ); #[cfg(feature = "llvm-11")] assert_eq!( phi.incoming_values, vec![ ( Operand::LocalOperand { name: Name::Number(22), ty: module.types.i64() }, Name::Number(14) ), ( Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 1 })), Name::Number(7) ), ] ); #[cfg(any(feature = "llvm-12", feature = "llvm-13"))] assert_eq!( phi.incoming_values, vec![ ( Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 1 })), Name::Number(17) ), ( Operand::LocalOperand { name: Name::Number(34), ty: module.types.i64() }, Name::Number(19) ), ] ); #[cfg(feature = "llvm-14")] assert_eq!( phi.incoming_values, vec![ ( Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 1 })), Name::Number(16) ), ( Operand::LocalOperand { name: Name::Number(33), ty: module.types.i64() }, Name::Number(18) ), ] ); #[cfg(all(feature = "llvm-15-or-greater", feature = "llvm-17-or-lower"))] assert_eq!( phi.incoming_values, vec![ ( Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 1 })), Name::Number(14) ), ( Operand::LocalOperand { name: Name::Number(31), ty: module.types.i64() }, Name::Number(16) ), ] ); #[cfg(feature = "llvm-18-or-greater")] assert_eq!( phi.incoming_values, vec![ ( Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 1 })), Name::Number(14) ), ( Operand::LocalOperand { name: Name::Number(29), ty: module.types.i64() }, Name::Number(16) ), ] ); #[cfg(feature = "llvm-9-or-lower")] assert_eq!( &phi.to_string(), "%11 = phi i64 [ i64 0, %7 ], [ i64 %20, %19 ]" ); #[cfg(feature = "llvm-10")] assert_eq!( &phi.to_string(), "%13 = phi i64 [ i64 %19, %12 ], [ i64 1, %7 ]" ); #[cfg(feature = "llvm-11")] assert_eq!( &phi.to_string(), "%15 = phi i64 [ i64 %22, %14 ], [ i64 1, %7 ]" ); #[cfg(any(feature = "llvm-12", feature = "llvm-13"))] assert_eq!( &phi.to_string(), "%20 = phi i64 [ i64 1, %17 ], [ i64 %34, %19 ]" ); #[cfg(feature = "llvm-14")] assert_eq!( &phi.to_string(), "%19 = phi i64 [ i64 1, %16 ], [ i64 %33, %18 ]" ); #[cfg(all(feature = "llvm-15-or-greater", feature = "llvm-17-or-lower"))] assert_eq!( &phi.to_string(), "%17 = phi i64 [ i64 1, %14 ], [ i64 %31, %16 ]" ); #[cfg(feature = "llvm-18-or-greater")] assert_eq!( &phi.to_string(), "%17 = phi i64 [ i64 1, %14 ], [ i64 %29, %16 ]" ); #[cfg(feature = "llvm-11-or-lower")] let gep: &instruction::GetElementPtr = &bbs[2].instrs[1] .clone() .try_into() .expect("Should be a gep"); #[cfg(feature = "llvm-12-or-greater")] let gep: &instruction::GetElementPtr = &bbs[4].instrs[2] .clone() .try_into() .expect("Should be a gep"); #[cfg(feature = "llvm-14-or-lower")] let gep_addr_expected_ty = module.types.pointer_to(allocated_type.clone()); #[cfg(feature = "llvm-15-or-greater")] let gep_addr_expected_ty = module.types.pointer(); assert_eq!( gep.address, Operand::LocalOperand { name: Name::Number(3), ty: gep_addr_expected_ty, } ); let gep_dest = if cfg!(feature = "llvm-9-or-lower") { Name::Number(12) } else if cfg!(feature = "llvm-10") { Name::Number(14) } else if cfg!(feature = "llvm-11") { Name::Number(16) } else if cfg!(feature = "llvm-12") || cfg!(feature = "llvm-13") { Name::Number(22) } else if cfg!(feature = "llvm-14") { Name::Number(21) } else { Name::Number(19) }; assert_eq!(gep.dest, gep_dest); assert_eq!(gep.in_bounds, true); let index = if cfg!(feature = "llvm-9-or-lower") { Name::Number(11) } else if cfg!(feature = "llvm-10") { Name::Number(13) } else if cfg!(feature = "llvm-11") { Name::Number(15) } else if cfg!(feature = "llvm-12") || cfg!(feature = "llvm-13") { Name::Number(20) } else if cfg!(feature = "llvm-14") { Name::Number(19) } else { Name::Number(17) }; assert_eq!( gep.indices, vec![ Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 64, value: 0 })), Operand::LocalOperand { name: index, ty: module.types.i64() }, ] ); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( module.type_of(gep), module.types.pointer_to(module.types.i32()) ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(module.type_of(gep), module.types.pointer()); #[cfg(feature = "llvm-9-or-lower")] assert_eq!( &gep.to_string(), "%12 = getelementptr inbounds [10 x i32]* %3, i64 0, i64 %11" ); #[cfg(feature = "llvm-10")] assert_eq!( &gep.to_string(), "%14 = getelementptr inbounds [10 x i32]* %3, i64 0, i64 %13" ); #[cfg(feature = "llvm-11")] assert_eq!( &gep.to_string(), "%16 = getelementptr inbounds [10 x i32]* %3, i64 0, i64 %15" ); #[cfg(any(feature = "llvm-12", feature = "llvm-13"))] assert_eq!( &gep.to_string(), "%22 = getelementptr inbounds [10 x i32]* %3, i64 0, i64 %20" ); #[cfg(feature = "llvm-14")] assert_eq!( &gep.to_string(), "%21 = getelementptr inbounds [10 x i32]* %3, i64 0, i64 %19" ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!( &gep.to_string(), "%19 = getelementptr inbounds ptr %3, i64 0, i64 %17" ); #[cfg(feature = "llvm-11-or-lower")] let store_inst = &bbs[2].instrs[2]; #[cfg(feature = "llvm-12-or-greater")] let store_inst = &bbs[4].instrs[3]; let store: &instruction::Store = &store_inst.clone().try_into().expect("Should be a store"); let address = if cfg!(feature = "llvm-9-or-lower") { Name::Number(12) } else if cfg!(feature = "llvm-10") { Name::Number(14) } else if cfg!(feature = "llvm-11") { Name::Number(16) } else if cfg!(feature = "llvm-12") || cfg!(feature = "llvm-13") { Name::Number(22) } else if cfg!(feature = "llvm-14") { Name::Number(21) } else { Name::Number(19) }; #[cfg(feature = "llvm-14-or-lower")] let address_ty = module.types.pointer_to(module.types.i32()); #[cfg(feature = "llvm-15-or-greater")] let address_ty = module.types.pointer(); assert_eq!( store.address, Operand::LocalOperand { name: address, ty: address_ty, } ); #[cfg(feature = "llvm-12-or-lower")] assert_eq!( store.value, Operand::LocalOperand { name: Name::Number(8), ty: module.types.i32() } ); #[cfg(feature = "llvm-13")] assert_eq!( store.value, Operand::LocalOperand { name: Name::Number(7), ty: module.types.i32() } ); #[cfg(feature = "llvm-14")] assert_eq!( store.value, Operand::LocalOperand { name: Name::Number(8), ty: module.types.i32() } ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!( store.value, Operand::LocalOperand { name: Name::Number(7), ty: module.types.i32() } ); assert_eq!(store.volatile, true); assert_eq!(store.alignment, 4); assert_eq!(module.type_of(store), module.types.void()); assert_eq!(store_inst.is_atomic(), false); #[cfg(feature = "llvm-9-or-lower")] assert_eq!( &store.to_string(), "store volatile i32 %8, i32* %12, align 4" ); #[cfg(feature = "llvm-10")] assert_eq!( &store.to_string(), "store volatile i32 %8, i32* %14, align 4" ); #[cfg(feature = "llvm-11")] assert_eq!( &store.to_string(), "store volatile i32 %8, i32* %16, align 4" ); #[cfg(feature = "llvm-12")] assert_eq!( &store.to_string(), "store volatile i32 %8, i32* %22, align 4" ); #[cfg(feature = "llvm-13")] assert_eq!( &store.to_string(), "store volatile i32 %7, i32* %22, align 4" ); #[cfg(feature = "llvm-14")] assert_eq!( &store.to_string(), "store volatile i32 %8, i32* %21, align 4" ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!( &store.to_string(), "store volatile i32 %7, ptr %19, align 4" ); // and finally other instructions of types we haven't seen yet let load_inst: &Instruction = if cfg!(feature = "llvm-9-or-lower") { &bbs[3].instrs[2] } else if cfg!(feature = "llvm-10") { &bbs[2].instrs[5] } else if cfg!(feature = "llvm-11") { &bbs[2].instrs[6] } else if cfg!(all(feature = "llvm-12-or-greater", feature = "llvm-17-or-lower")) { &bbs[4].instrs[7] } else { // Clang 18 removes some instructions in the loop body c.f. Clang 17 &bbs[4].instrs[6] }; let load: &instruction::Load = &load_inst.clone().try_into().expect("Should be a load"); let load_addr = if cfg!(feature = "llvm-10-or-lower") { Name::Number(16) } else if cfg!(feature = "llvm-11") { Name::Number(19) } else if cfg!(feature = "llvm-12") || cfg!(feature = "llvm-13") { Name::Number(25) } else if cfg!(feature = "llvm-14") { Name::Number(24) } else if cfg!(all(feature = "llvm-14-or-greater", feature = "llvm-17-or-lower")) { Name::Number(22) } else { Name::Number(21) }; #[cfg(feature = "llvm-14-or-lower")] let load_addr_expected_ty = module.types.pointer_to(module.types.i32()); #[cfg(feature = "llvm-15-or-greater")] let load_addr_expected_ty = module.types.pointer(); assert_eq!( load.address, Operand::LocalOperand { name: load_addr, ty: load_addr_expected_ty, } ); #[cfg(feature = "llvm-10-or-lower")] assert_eq!(load.dest, Name::Number(17)); #[cfg(feature = "llvm-11")] assert_eq!(load.dest, Name::Number(20)); #[cfg(any(feature = "llvm-12", feature = "llvm-13"))] assert_eq!(load.dest, Name::Number(26)); #[cfg(feature = "llvm-14")] assert_eq!(load.dest, Name::Number(25)); #[cfg(all(feature = "llvm-15-or-greater", feature = "llvm-17-or-lower"))] assert_eq!(load.dest, Name::Number(23)); #[cfg(feature = "llvm-18-or-greater")] assert_eq!(load.dest, Name::Number(22)); assert_eq!(load.volatile, true); assert_eq!(load.alignment, 4); assert_eq!(module.type_of(load), module.types.i32()); assert_eq!(load_inst.is_atomic(), false); #[cfg(feature = "llvm-10-or-lower")] assert_eq!(&load.to_string(), "%17 = load volatile i32* %16, align 4"); #[cfg(feature = "llvm-11")] assert_eq!(&load.to_string(), "%20 = load volatile i32* %19, align 4"); #[cfg(any(feature = "llvm-12", feature = "llvm-13"))] assert_eq!(&load.to_string(), "%26 = load volatile i32* %25, align 4"); #[cfg(feature = "llvm-14")] assert_eq!(&load.to_string(), "%25 = load volatile i32* %24, align 4"); #[cfg(all(feature = "llvm-15-or-greater", feature = "llvm-17-or-lower"))] assert_eq!( &load.to_string(), "%23 = load volatile i32, ptr %22, align 4" ); #[cfg(feature = "llvm-18-or-greater")] assert_eq!( &load.to_string(), "%22 = load volatile i32, ptr %21, align 4" ); let ret: &Terminator = if cfg!(feature = "llvm-9-or-lower") { &bbs[5].term } else if cfg!(feature = "llvm-10") || cfg!(feature = "llvm-11") { &bbs[3].term } else { &bbs[5].term }; let ret: &terminator::Ret = &ret.clone().try_into().expect("Should be a ret"); assert_eq!(ret.return_operand, None); assert_eq!(module.type_of(ret), module.types.void()); assert_eq!(&ret.to_string(), "ret void"); } #[test] fn switchbc() { init_logging(); let path = llvm_bc_dir().join("switch.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); assert_eq!(module.functions.len(), 1); let func = &module.functions[0]; assert_eq!(func.name, "has_a_switch"); let bb = &func.basic_blocks[0]; let switch: &terminator::Switch = &bb.term.clone().try_into().expect("Should be a switch"); assert_eq!( switch.operand, Operand::LocalOperand { name: Name::Number(0), ty: module.types.i32() } ); assert_eq!(switch.dests.len(), 9); assert_eq!( switch.dests[0], ( ConstantRef::new(Constant::Int { bits: 32, value: 0 }), Name::Number(12) ) ); assert_eq!( switch.dests[1], ( ConstantRef::new(Constant::Int { bits: 32, value: 1 }), Name::Number(2) ) ); assert_eq!( switch.dests[2], ( ConstantRef::new(Constant::Int { bits: 32, value: 13 }), Name::Number(3) ) ); assert_eq!( switch.dests[3], ( ConstantRef::new(Constant::Int { bits: 32, value: 26 }), Name::Number(4) ) ); assert_eq!( switch.dests[4], ( ConstantRef::new(Constant::Int { bits: 32, value: 33 }), Name::Number(5) ) ); assert_eq!( switch.dests[5], ( ConstantRef::new(Constant::Int { bits: 32, value: 142 }), Name::Number(6) ) ); assert_eq!( switch.dests[6], ( ConstantRef::new(Constant::Int { bits: 32, value: 1678 }), Name::Number(7) ) ); assert_eq!( switch.dests[7], ( ConstantRef::new(Constant::Int { bits: 32, value: 88 }), Name::Number(8) ) ); assert_eq!( switch.dests[8], ( ConstantRef::new(Constant::Int { bits: 32, value: 101 }), Name::Number(9) ) ); assert_eq!(switch.default_dest, Name::Number(10)); assert_eq!( &switch.to_string(), "switch i32 %0, label %10 [ i32 0, label %12; i32 1, label %2; i32 13, label %3; i32 26, label %4; i32 33, label %5; i32 142, label %6; i32 1678, label %7; i32 88, label %8; i32 101, label %9; ]", ); let phibb = &func .get_bb_by_name(&Name::Number(12)) .expect("Failed to find bb %12"); let phi: &instruction::Phi = &phibb.instrs[0].clone().try_into().expect("Should be a phi"); assert_eq!(phi.incoming_values.len(), 10); assert_eq!( &phi.to_string(), "%13 = phi i32 [ i32 -1, %10 ], [ i32 -3, %9 ], [ i32 0, %8 ], [ i32 77, %7 ], [ i32 -33, %6 ], [ i32 1, %5 ], [ i32 -5, %4 ], [ i32 -7, %3 ], [ i32 5, %2 ], [ i32 3, %1 ]", ); assert_eq!( module.get_func_decl_by_name("has_a_switch"), None, "has_a_switch should be a defined function, not a decl" ); let decl = module .get_func_decl_by_name("puts") .expect("there should be a puts declaration"); assert_eq!(decl.name, "puts"); assert_eq!(decl.return_type, module.types.i32()); assert_eq!(decl.parameters.len(), 1); #[cfg(feature = "llvm-14-or-lower")] let param_0_expected_ty = module.types.pointer_to(module.types.i8()); #[cfg(feature = "llvm-15-or-greater")] let param_0_expected_ty = module.types.pointer(); assert_eq!(module.type_of(&decl.parameters[0]), param_0_expected_ty,); } #[test] fn variablesbc() { init_logging(); let path = llvm_bc_dir().join("variables.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); assert_eq!(module.global_vars.len(), 1); let var = &module.global_vars[0]; assert_eq!(var.name, Name::from("global")); assert_eq!(var.is_constant, false); #[cfg(feature = "llvm-14-or-lower")] assert_eq!(var.ty, module.types.pointer_to(module.types.i32())); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(var.ty, module.types.pointer()); assert_eq!( var.initializer, Some(ConstantRef::new(Constant::Int { bits: 32, value: 5 })) ); assert_eq!(var.alignment, 4); assert!(var.get_debug_loc().is_none()); // this file was compiled without debuginfo assert_eq!(module.functions.len(), 1); let func = &module.functions[0]; assert_eq!(func.name, "variables"); let bb = &func.basic_blocks[0]; let store: &instruction::Store = &bb.instrs[2].clone().try_into().expect("Should be a store"); #[cfg(feature = "llvm-14-or-lower")] let store_addr_expected_ty = module.types.pointer_to(module.types.i32()); #[cfg(feature = "llvm-15-or-greater")] let store_addr_expected_ty = module.types.pointer(); assert_eq!( store.address, Operand::LocalOperand { name: Name::Number(3), ty: store_addr_expected_ty, } ); assert_eq!(module.type_of(store), module.types.void()); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "store volatile i32 %0, i32* %3, align 4"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "store volatile i32 %0, ptr %3, align 4"; assert_eq!(&store.to_string(), expected_fmt); #[cfg(feature = "llvm-14-or-lower")] let load: &instruction::Load = &bb.instrs[8].clone().try_into().expect("Should be a load"); #[cfg(feature = "llvm-15-or-greater")] let load: &instruction::Load = &bb.instrs[6].clone().try_into().expect("Should be a load"); #[cfg(feature = "llvm-14-or-lower")] let load_addr_expected_ty = module.types.pointer_to(module.types.i32()); #[cfg(feature = "llvm-15-or-greater")] let load_addr_expected_ty = module.types.pointer(); assert_eq!( load.address, Operand::LocalOperand { name: Name::Number(4), ty: load_addr_expected_ty, } ); assert_eq!(module.type_of(load), module.types.i32()); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "%8 = load volatile i32* %4, align 4"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "%6 = load volatile i32, ptr %4, align 4"; assert_eq!(&load.to_string(), expected_fmt); #[cfg(feature = "llvm-14-or-lower")] let global_load: &instruction::Load = &bb.instrs[14].clone().try_into().expect("Should be a load"); #[cfg(feature = "llvm-15-or-greater")] let global_load: &instruction::Load = &bb.instrs[12].clone().try_into().expect("Should be a load"); assert_eq!( global_load.address, Operand::ConstantOperand(ConstantRef::new(Constant::GlobalReference { name: Name::from("global"), ty: module.types.i32() })) ); assert_eq!(module.type_of(global_load), module.types.i32()); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "%12 = load volatile i32* @global, align 4"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "%10 = load volatile i32, ptr @global, align 4"; assert_eq!(&global_load.to_string(), expected_fmt); #[cfg(feature = "llvm-14-or-lower")] let global_store: &instruction::Store = &bb.instrs[16].clone().try_into().expect("Should be a store"); #[cfg(feature = "llvm-15-or-greater")] let global_store: &instruction::Store = &bb.instrs[14].clone().try_into().expect("Should be a store"); assert_eq!( global_store.address, Operand::ConstantOperand(ConstantRef::new(Constant::GlobalReference { name: Name::from("global"), ty: module.types.i32() })) ); assert_eq!(module.type_of(global_store), module.types.void()); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "store volatile i32 %13, i32* @global, align 4"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "store volatile i32 %11, ptr @global, align 4"; assert_eq!(&global_store.to_string(), expected_fmt); assert_eq!( module.get_func_decl_by_name("variables"), None, "variables should be a defined function, not a decl" ); let decl = module .get_func_decl_by_name("malloc") .expect("there should be a malloc declaration"); assert_eq!(decl.name, "malloc"); #[cfg(feature = "llvm-14-or-lower")] assert_eq!(decl.return_type, module.types.pointer_to(module.types.i8())); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(decl.return_type, module.types.pointer()); assert!(decl .return_attributes .contains(&ParameterAttribute::NoAlias)); assert_eq!(decl.parameters.len(), 1); assert_eq!(module.type_of(&decl.parameters[0]), module.types.i64()); #[cfg(feature = "llvm-12-or-lower")] assert_eq!(decl.parameters[0].attributes, vec![]); #[cfg(feature = "llvm-13-or-greater")] assert_eq!( decl.parameters[0].attributes, vec![ParameterAttribute::NoUndef] ); } // this test relates to the version of the file compiled with debuginfo #[test] fn variablesbcg() { init_logging(); let path = llvm_bc_dir().join("variables.bc-g"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); let debug_filename = "variables.c"; let debug_directory_suffix = "/tests/basic_bc"; // really all we want to check is the debugloc of the global variable. // other debuginfo stuff is covered in other tests assert_eq!(module.global_vars.len(), 1); let var = &module.global_vars[0]; assert_eq!(var.name, Name::from("global")); let debugloc = var .get_debug_loc() .as_ref() .expect("expected the global to have a debugloc"); assert_eq!(debugloc.line, 5); assert_eq!(debugloc.col, None); // only `Instruction`s and `Terminator`s get column numbers assert_eq!(debugloc.filename, debug_filename); assert!(debugloc.directory.as_ref().expect("directory should exist").ends_with(debug_directory_suffix)); } /// this test checks for regression on issue #4 #[test] fn issue4() { init_logging(); let path = llvm_bc_dir().join("issue_4.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); assert_eq!(module.functions.len(), 1); let func = &module.functions[0]; // not part of issue 4 proper, but let's check that we have the correct number of function attributes let expected_num_function_attributes = if cfg!(feature = "llvm-9") { 22 } else if cfg!(feature = "llvm-10") || cfg!(feature = "llvm-11") { // LLVM 10+ seems to have combined the two attributes // "no-frame-pointer-elim=true" and "no-frame-pointer-elim-non-leaf" // into a single attribute, "frame-pointer=all" 21 } else if cfg!(feature = "llvm-12") { // LLVM 12+ adds "willreturn" 22 } else if cfg!(feature = "llvm-13") || cfg!(feature = "llvm-14") { // LLVM 13+ adds "mustprogress" and "nosync" // LLVM 13+ removes the following string attributes: // "disable-tail-calls=false" // "less-precise-fpmad=false" // "no-infs-fp-math=false" // "no-jump-tables=false" // "no-nans-fp-math=false" // "no-signed-zeroes-fp-math=false" // "unsafe-fp-math=false" // "use-soft-float=false" // for a net of -6 attributes 16 } else if cfg!(feature = "llvm-15") { // LLVM 15+ adds "argmemonly" 17 } else if cfg!(feature = "llvm-16-or-greater") { // LLVM 16+ merges "argmemonly", "inaccessiblememonly", etc. into a single memory attribute // See https://discourse.llvm.org/t/rfc-unify-memory-effect-attributes/65579/20 16 } else { panic!("Shouldn't reach this") }; assert_eq!( func.function_attributes.len(), expected_num_function_attributes, "Expected {} function attributes but have {}: {:?}", expected_num_function_attributes, func.function_attributes.len(), func.function_attributes ); // and that all but 6 of them are StringAttributes (7 for LLVM 12; 9 for LLVM 13/14; 10 for LLVM 15+) let expected_num_enum_attrs = if cfg!(feature = "llvm-11-or-lower") { 6 } else if cfg!(feature = "llvm-12") { 7 // adds "willreturn" } else if cfg!(feature = "llvm-13") || cfg!(feature = "llvm-14") { 9 // adds "mustprogress" and "nosync" } else if cfg!(feature = "llvm-15") { 10 // adds "argmemonly" } else if cfg!(feature = "llvm-16-or-greater") { 9 // new "memory" attribute combines "argmemonly" and related attributes } else { unreachable!("Shouldn't reach this") }; let string_attrs = func.function_attributes.iter().filter(|attr| { if let FunctionAttribute::StringAttribute { .. } = attr { true } else { false } }); assert_eq!( string_attrs.count(), expected_num_function_attributes - expected_num_enum_attrs ); // now check that the first parameter has 3 attributes (4 in LLVM 11/12/13, 5 in LLVM 14) and the second parameter has 0 assert_eq!(func.parameters.len(), 2); let first_param_attrs = &func.parameters[0].attributes; #[cfg(feature = "llvm-10-or-lower")] assert_eq!(first_param_attrs.len(), 3); #[cfg(any(feature = "llvm-11", feature = "llvm-12", feature = "llvm-13"))] assert_eq!(first_param_attrs.len(), 4); #[cfg(all(feature = "llvm-14-or-greater", feature = "llvm-17-or-lower"))] assert_eq!(first_param_attrs.len(), 5); #[cfg(feature = "llvm-18-or-greater")] assert_eq!(first_param_attrs.len(), 7); // Clang 18 adds dead_on_unwind and writable let second_param_attrs = &func.parameters[1].attributes; #[cfg(feature = "llvm-13-or-lower")] assert_eq!(second_param_attrs.len(), 0); #[cfg(feature = "llvm-14-or-greater")] assert_eq!(second_param_attrs.len(), 1); // LLVM 14+ adds 'noundef' to the second param // and that one of the parameter attributes is SRet #[cfg(feature = "llvm-11-or-lower")] let is_sret = |attr: &ParameterAttribute| match attr { ParameterAttribute::SRet => true, _ => false, }; #[cfg(feature = "llvm-12-or-greater")] let is_sret = |attr: &ParameterAttribute| match attr { ParameterAttribute::SRet(_) => true, _ => false, }; assert!(first_param_attrs.iter().any(is_sret)); } /// This test checks for regression on issue 42 #[cfg(feature = "llvm-14-or-greater")] #[test] fn issue42() { init_logging(); let path = Path::new(BC_DIR).join("issue-42.ll"); let _ = Module::from_ir_path(&path).expect("Failed to parse module"); // just check the module parses without errors } /// This test checks for regression on issue 57 #[cfg(feature = "llvm-16-or-greater")] // although the issue probably affects all of our supported versions (IFunc has existed since at least LLVM 8), the provided bitcode was produced with LLVM 16 #[test] fn issue57() { init_logging(); let path = Path::new(BC_DIR).join("ifunc_minimal.ll"); let module = Module::from_ir_path(&path).expect("Failed to parse module"); let ifunc = module.get_global_ifunc_by_name(&Name::from("__libc_strstr")).expect("failed to find global ifunc"); assert_eq!(ifunc.ty, module.types.pointer()); match ifunc.resolver_fn.as_ref() { Constant::GlobalReference { name, ty } => { assert_eq!(name, &Name::from("__libc_strstr_ifunc")); assert_eq!(ty, &module.types.func_type(module.types.pointer(), vec![], false)); } _ => panic!("expected a GlobalReference"), } let path = Path::new(BC_DIR).join("strstr.o.bc"); let _ = Module::from_bc_path(&path).expect("Failed to parse module"); // just check the module parses without errors } #[test] fn rustbc() { // This tests against the checked-in rust.bc, which was generated from the checked-in rust.rs with rustc 1.39.0 init_logging(); let path = rust_bc_dir().join("rust.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); let func = module .get_func_by_name("_ZN4rust9rust_loop17h3ed0672b8cf44eb1E") .expect("Failed to find function"); assert_eq!(func.parameters.len(), 3); assert_eq!(func.parameters[0].name, Name::from("a")); assert_eq!(func.parameters[1].name, Name::from("b")); assert_eq!(func.parameters[2].name, Name::from("v")); assert_eq!(func.parameters[0].ty, module.types.i64()); assert_eq!(func.parameters[1].ty, module.types.i64()); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( func.parameters[2].ty, module .types .pointer_to(module.types.named_struct("alloc::vec::Vec<isize>")) ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(func.parameters[2].ty, module.types.pointer()); let startbb = func .get_bb_by_name(&Name::from("start")) .expect("Failed to find bb 'start'"); let alloca_iter: &instruction::Alloca = &startbb.instrs[5] .clone() .try_into() .expect("Should be an alloca"); assert_eq!(alloca_iter.dest, Name::from("iter")); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "%iter = alloca { i64*, i64* }, align 8"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "%iter = alloca { ptr, ptr }, align 8"; assert_eq!(&alloca_iter.to_string(), expected_fmt); let alloca_sum: &instruction::Alloca = &startbb.instrs[6] .clone() .try_into() .expect("Should be an alloca"); assert_eq!(alloca_sum.dest, Name::from("sum")); assert_eq!(&alloca_sum.to_string(), "%sum = alloca i64, align 8"); let store: &instruction::Store = &startbb.instrs[7] .clone() .try_into() .expect("Should be a store"); #[cfg(feature = "llvm-14-or-lower")] let store_addr_expected_ty = module.types.pointer_to(module.types.i64()); #[cfg(feature = "llvm-15-or-greater")] let store_addr_expected_ty = module.types.pointer(); assert_eq!( store.address, Operand::LocalOperand { name: Name::from("sum"), ty: store_addr_expected_ty, } ); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "store i64 0, i64* %sum, align 8"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "store i64 0, ptr %sum, align 8"; assert_eq!(&store.to_string(), expected_fmt); let call: &instruction::Call = &startbb.instrs[8] .clone() .try_into() .expect("Should be a call"); #[cfg(feature = "llvm-14-or-lower")] let param_type = module .types .pointer_to(module.types.named_struct("alloc::vec::Vec<isize>")); #[cfg(feature = "llvm-15-or-greater")] let param_type = module.types.pointer(); let ret_type = module.types.struct_of( vec![ #[cfg(feature = "llvm-14-or-lower")] module .types .pointer_to(module.types.array_of(module.types.i64(), 0)), #[cfg(feature = "llvm-15-or-greater")] module.types.pointer(), module.types.i64(), ], false, ); if let Either::Right(Operand::ConstantOperand(cref)) = &call.function { if let Constant::GlobalReference { ref name, ref ty } = cref.as_ref() { assert_eq!(name, &Name::from("_ZN68_$LT$alloc..vec..Vec$LT$T$GT$$u20$as$u20$core..ops..deref..Deref$GT$5deref17h378128d7d9378466E")); match ty.as_ref() { Type::FuncType { result_type, param_types, is_var_arg, } => { assert_eq!(result_type, &ret_type); assert_eq!(¶m_types[0], ¶m_type); assert_eq!(*is_var_arg, false); }, _ => panic!("Expected called global to have FuncType, but got {:?}", ty), } assert_eq!(module.type_of(call), ret_type); } else { panic!( "call.function not a GlobalReference as expected; it is actually another kind of Constant: {:?}", cref ); } } else { panic!( "call.function not a GlobalReference as expected; it is actually {:?}", call.function ); } assert_eq!(call.arguments.len(), 1); assert_eq!( call.arguments[0].0, Operand::LocalOperand { name: Name::from("v"), ty: param_type, } ); assert_eq!(call.dest, Some(Name::Number(0))); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "%0 = call @_ZN68_$LT$alloc..vec..Vec$LT$T$GT$$u20$as$u20$core..ops..deref..Deref$GT$5deref17h378128d7d9378466E(%alloc::vec::Vec<isize>* %v)"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "%0 = call @_ZN68_$LT$alloc..vec..Vec$LT$T$GT$$u20$as$u20$core..ops..deref..Deref$GT$5deref17h378128d7d9378466E(ptr %v)"; assert_eq!(&call.to_string(), expected_fmt); // this file was compiled without debuginfo, so nothing should have a debugloc assert!(func.get_debug_loc().is_none()); assert!(alloca_iter.get_debug_loc().is_none()); assert!(alloca_sum.get_debug_loc().is_none()); assert!(store.get_debug_loc().is_none()); assert!(call.get_debug_loc().is_none()); } // this test relates to the version of the file compiled with debuginfo #[test] fn rustbcg() { init_logging(); let path = rust_bc_dir().join("rust.bc-g"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); let debug_filename = "rust.rs"; let debug_directory_suffix = "/tests/basic_bc"; let func = module .get_func_by_name("_ZN4rust9rust_loop17h3ed0672b8cf44eb1E") .expect("Failed to find function"); let debugloc = func .get_debug_loc() .as_ref() .expect("Expected function to have a debugloc"); assert_eq!(debugloc.line, 3); assert_eq!(debugloc.col, None); assert_eq!(debugloc.filename, debug_filename); assert!(debugloc.directory.as_ref().expect("directory should exist").ends_with(debug_directory_suffix)); let startbb = func .get_bb_by_name(&Name::from("start")) .expect("Failed to find bb 'start'"); // the first 17 instructions in the function should not have debuglocs - they are just setting up the stack frame for i in 0..17 { assert!(startbb.instrs[i].get_debug_loc().is_none()); } #[cfg(feature = "llvm-18-or-lower")] const EXPECTED_STORE_INSTRUCTION_INDEX : usize = 31; #[cfg(feature = "llvm-19-or-greater")] const EXPECTED_STORE_INSTRUCTION_INDEX : usize = 19; #[cfg(feature = "llvm-18-or-lower")] const EXPECTED_CALL_INSTRUCTION_INDEX : usize = 33; #[cfg(feature = "llvm-19-or-greater")] const EXPECTED_CALL_INSTRUCTION_INDEX : usize = 21; let store_debugloc = startbb.instrs[EXPECTED_STORE_INSTRUCTION_INDEX] .get_debug_loc() .as_ref() .expect("Expected this store to have a debugloc"); assert_eq!(store_debugloc.line, 4); assert_eq!(store_debugloc.col, Some(18)); assert_eq!(store_debugloc.filename, debug_filename); assert!(debugloc.directory.as_ref().expect("directory should exist").ends_with(debug_directory_suffix)); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "store i64 0, i64* %sum, align 8 (with debugloc)"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "store i64 0, ptr %sum, align 8 (with debugloc)"; assert_eq!(&startbb.instrs[EXPECTED_STORE_INSTRUCTION_INDEX].to_string(), expected_fmt); let call_debugloc = startbb.instrs[EXPECTED_CALL_INSTRUCTION_INDEX] .get_debug_loc() .as_ref() .expect("Expected this call to have a debugloc"); assert_eq!(call_debugloc.line, 5); assert_eq!(call_debugloc.col, Some(13)); assert_eq!(call_debugloc.filename, debug_filename); assert!(debugloc.directory.as_ref().expect("directory should exist").ends_with(debug_directory_suffix)); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "%4 = call @_ZN68_$LT$alloc..vec..Vec$LT$T$GT$$u20$as$u20$core..ops..deref..Deref$GT$5deref17h378128d7d9378466E(%alloc::vec::Vec<isize>* %3) (with debugloc)"; #[cfg(feature = "llvm-15-or-greater")] let expected_fmt = "%4 = call @_ZN68_$LT$alloc..vec..Vec$LT$T$GT$$u20$as$u20$core..ops..deref..Deref$GT$5deref17h378128d7d9378466E(ptr %3) (with debugloc)"; assert_eq!(&startbb.instrs[EXPECTED_CALL_INSTRUCTION_INDEX].to_string(), expected_fmt); } #[test] fn simple_linked_list() { init_logging(); let path = llvm_bc_dir().join("linkedlist.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); let struct_name: String = "struct.SimpleLinkedList".into(); let structty = module.types.named_struct(&struct_name); match structty.as_ref() { Type::NamedStructType { name } => { assert_eq!(name, &struct_name); }, ty => panic!( "Expected {} to be NamedStructType, but got {:?}", struct_name, ty ), } let structty_inner = match module.types.named_struct_def(&struct_name) { None => panic!( "Failed to find {} with module.types.named_struct_def(); have names {:?}", struct_name, module.types.all_struct_names().collect::<Vec<_>>() ), Some(NamedStructDef::Opaque) => panic!("{} should not be an opaque type", struct_name), Some(NamedStructDef::Defined(ty)) => ty, }; if let Type::StructType { element_types, .. } = structty_inner.as_ref() { assert_eq!(element_types.len(), 2); assert_eq!(element_types[0], module.types.i32()); #[cfg(feature = "llvm-14-or-lower")] if let Type::PointerType { pointee_type, .. } = element_types[1].as_ref() { if let Type::NamedStructType { name } = pointee_type.as_ref() { assert_eq!(name, &struct_name); } else { panic!( "Expected pointee type to be a NamedStructType, got {:?}", pointee_type ); } } else { panic!( "Expected inner type to be a PointerType, got {:?}", element_types[1] ); } #[cfg(feature = "llvm-15-or-greater")] assert!(matches!( element_types[1].as_ref(), Type::PointerType { .. } )); } else { panic!( "Expected {} to be a StructType, got {:?}", struct_name, structty ); } let func = module .get_func_by_name("simple_linked_list") .expect("Failed to find function"); let alloca: &instruction::Alloca = &func.basic_blocks[0].instrs[1] .clone() .try_into() .expect("Should be an alloca"); if let Type::NamedStructType { name } = alloca.allocated_type.as_ref() { assert_eq!(name, &struct_name); } else { panic!( "Expected alloca.allocated_type to be a NamedStructType, got {:?}", alloca.allocated_type ); } assert_eq!( &alloca.to_string(), "%3 = alloca %struct.SimpleLinkedList, align 8" ); // LLVM 15 has no need for the SomeOpaqueStruct due to opaque pointer types #[cfg(feature = "llvm-14-or-lower")] { let struct_name: String = "struct.SomeOpaqueStruct".into(); let structty = module.types.named_struct(&struct_name); match structty.as_ref() { Type::NamedStructType { name } => { assert_eq!(name, &struct_name); }, ty => panic!( "Expected {} to be a NamedStructType, but got {:?}", struct_name, ty ), } match module.types.named_struct_def(&struct_name) { None => panic!( "Failed to find {} with module.types.named_struct_def(); have names {:?}", struct_name, module.types.all_struct_names().collect::<Vec<_>>() ), Some(NamedStructDef::Opaque) => (), Some(NamedStructDef::Defined(def)) => panic!( "{} should be an opaque type; got def {:?}", struct_name, def ), } } let func = module .get_func_by_name("takes_opaque_struct") .expect("Failed to find function"); let paramty = &func.parameters[0].ty; #[cfg(feature = "llvm-14-or-lower")] match paramty.as_ref() { Type::PointerType { pointee_type, .. } => match pointee_type.as_ref() { Type::NamedStructType { name } => { assert_eq!(name, "struct.SomeOpaqueStruct"); }, ty => panic!( "Expected parameter type to be pointer to named struct, but got pointer to {:?}", ty ), }, _ => panic!( "Expected parameter type to be pointer type, but got {:?}", paramty ), }; #[cfg(feature = "llvm-15-or-greater")] assert!(matches!(paramty.as_ref(), Type::PointerType { .. })); } // this test relates to the version of the file compiled with debuginfo #[test] fn simple_linked_list_g() { init_logging(); let path = llvm_bc_dir().join("linkedlist.bc-g"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); let debug_filename = "linkedlist.c"; let debug_directory_suffix = "/tests/basic_bc"; let func = module .get_func_by_name("simple_linked_list") .expect("Failed to find function"); let debugloc = func .get_debug_loc() .as_ref() .expect("expected simple_linked_list to have a debugloc"); assert_eq!(debugloc.line, 8); assert_eq!(debugloc.col, None); assert_eq!(debugloc.filename, debug_filename); assert!(debugloc.directory.as_ref().expect("directory should exist").ends_with(debug_directory_suffix)); // the first seven instructions shouldn't have debuglocs - they are just setting up the stack frame for i in 0..7 { assert!(func.basic_blocks[0].instrs[i].get_debug_loc().is_none()); } // As of LLVM 19, debug info is now metadata instead of intrinsics #[cfg(feature = "llvm-18-or-lower")] { // the eighth instruction should have a debugloc let debugloc = func.basic_blocks[0].instrs[7] .get_debug_loc() .as_ref() .expect("expected this instruction to have a debugloc"); assert_eq!(debugloc.line, 8); assert_eq!(debugloc.col, Some(28)); assert_eq!(debugloc.filename, debug_filename); assert!(debugloc.directory.as_ref().expect("directory should exist").ends_with(debug_directory_suffix)); assert_eq!( &func.basic_blocks[0].instrs[7].to_string(), "call @llvm.dbg.declare(<metadata>, <metadata>, <metadata>) (with debugloc)", ); } // the tenth instruction should have a different debugloc let debugloc = func.basic_blocks[0].instrs[9] .get_debug_loc() .as_ref() .expect("expected this instruction to have a debugloc"); assert_eq!(debugloc.line, 9); assert_eq!(debugloc.col, Some(34)); assert_eq!(debugloc.filename, debug_filename); assert!(debugloc.directory.as_ref().expect("directory should exist").ends_with(debug_directory_suffix)); #[cfg(feature = "llvm-14-or-lower")] let expected_fmt = "%8 = getelementptr inbounds %struct.SimpleLinkedList* %3, i32 0, i32 0 (with debugloc)"; #[cfg(all(feature = "llvm-15-or-greater", feature = "llvm-18-or-lower"))] let expected_fmt = "%8 = getelementptr inbounds ptr %3, i32 0, i32 0 (with debugloc)"; #[cfg(feature = "llvm-19-or-greater")] let expected_fmt = "store i32 %9, ptr %8, align 8 (with debugloc)"; assert_eq!(&func.basic_blocks[0].instrs[9].to_string(), expected_fmt); } #[test] fn indirectly_recursive_type() { init_logging(); let path = llvm_bc_dir().join("linkedlist.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); let struct_name_a: String = "struct.NodeA".into(); let aty = module.types.named_struct(&struct_name_a); match aty.as_ref() { Type::NamedStructType { name } => { assert_eq!(name, &struct_name_a); }, ty => panic!( "Expected {} to be a NamedStructType, but got {:?}", struct_name_a, ty ), } let aty_inner = match module.types.named_struct_def(&struct_name_a) { None => panic!( "Failed to find {} with module.types.named_struct_def(); have names {:?}", struct_name_a, module.types.all_struct_names().collect::<Vec<_>>() ), Some(NamedStructDef::Opaque) => panic!("{} should not be an opaque type", &struct_name_a), Some(NamedStructDef::Defined(ty)) => ty, }; let struct_name_b: String = "struct.NodeB".into(); let bty = module.types.named_struct(&struct_name_b); match bty.as_ref() { Type::NamedStructType { name } => { assert_eq!(name, &struct_name_b); }, ty => panic!( "Expected {} to be a NamedStructType, but got {:?}", struct_name_b, ty ), } let bty_inner = match module.types.named_struct_def(&struct_name_b) { None => panic!( "Failed to find {} with module.types.named_struct_def(); have names {:?}", struct_name_b, module.types.all_struct_names().collect::<Vec<_>>() ), Some(NamedStructDef::Opaque) => panic!("{} should not be an opaque type", &struct_name_b), Some(NamedStructDef::Defined(ty)) => ty, }; if let Type::StructType { element_types, .. } = aty_inner.as_ref() { assert_eq!(element_types.len(), 2); assert_eq!(element_types[0], module.types.i32()); #[cfg(feature = "llvm-14-or-lower")] if let Type::PointerType { pointee_type, .. } = element_types[1].as_ref() { if let Type::NamedStructType { name } = pointee_type.as_ref() { assert_eq!(name, &struct_name_b); } else { panic!( "Expected pointee type to be a NamedStructType, got {:?}", pointee_type.as_ref() ); } } else { panic!( "Expected inner type to be a PointerType, got {:?}", element_types[1] ); } #[cfg(feature = "llvm-15-or-greater")] assert!(matches!( element_types[1].as_ref(), Type::PointerType { .. } )); } else { panic!( "Expected NodeA inner type to be a StructType, got {:?}", aty ); } if let Type::StructType { element_types, .. } = bty_inner.as_ref() { assert_eq!(element_types.len(), 2); assert_eq!(element_types[0], module.types.i32()); #[cfg(feature = "llvm-14-or-lower")] if let Type::PointerType { pointee_type, .. } = element_types[1].as_ref() { if let Type::NamedStructType { name } = pointee_type.as_ref() { assert_eq!(name, &struct_name_a); } else { panic!( "Expected pointee type to be a NamedStructType, got {:?}", pointee_type.as_ref() ); } } else { panic!( "Expected inner type to be a PointerType, got {:?}", element_types[1] ); } #[cfg(feature = "llvm-15-or-greater")] assert!(matches!( element_types[1].as_ref(), Type::PointerType { .. } )); } else { panic!( "Expected NodeB inner type to be a StructType, got {:?}", bty ); } let func = module .get_func_by_name("indirectly_recursive_type") .expect("Failed to find function"); let alloca_a: &instruction::Alloca = &func.basic_blocks[0].instrs[1] .clone() .try_into() .expect("Should be an alloca"); let alloca_b: &instruction::Alloca = &func.basic_blocks[0].instrs[2] .clone() .try_into() .expect("Should be an alloca"); if let Type::NamedStructType { name } = alloca_a.allocated_type.as_ref() { assert_eq!(name, &struct_name_a); } else { panic!( "Expected alloca_a.allocated_type to be a NamedStructType, got {:?}", alloca_a.allocated_type ); } if let Type::NamedStructType { name } = alloca_b.allocated_type.as_ref() { assert_eq!(name, &struct_name_b); } else { panic!( "Expected alloca_b.allocated_type to be a NamedStructType, got {:?}", alloca_b.allocated_type ); } assert_eq!(&alloca_a.to_string(), "%3 = alloca %struct.NodeA, align 8"); assert_eq!(&alloca_b.to_string(), "%4 = alloca %struct.NodeB, align 8"); } #[test] fn param_and_func_attributes() { let _ = env_logger::builder().is_test(true).try_init(); // capture log messages with test harness let path = llvm_bc_dir().join("param_and_func_attributes.ll.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); // Return attributes let zeroext_fn = module.get_func_by_name("f.zeroext").unwrap(); assert_eq!(zeroext_fn.return_attributes.len(), 1); assert_eq!(zeroext_fn.return_attributes[0], ParameterAttribute::ZeroExt); let signext_fn = module.get_func_by_name("f.signext").unwrap(); assert_eq!(signext_fn.return_attributes.len(), 1); assert_eq!(signext_fn.return_attributes[0], ParameterAttribute::SignExt); let inreg_fn = module.get_func_by_name("f.inreg").unwrap(); assert_eq!(inreg_fn.return_attributes.len(), 1); assert_eq!(inreg_fn.return_attributes[0], ParameterAttribute::InReg); let noalias_fn = module.get_func_by_name("f.noalias").unwrap(); assert_eq!(noalias_fn.return_attributes.len(), 1); assert_eq!(noalias_fn.return_attributes[0], ParameterAttribute::NoAlias); let nonnull_fn = module.get_func_by_name("f.nonnull").unwrap(); assert_eq!(nonnull_fn.return_attributes.len(), 1); assert_eq!(nonnull_fn.return_attributes[0], ParameterAttribute::NonNull); let deref4_fn = module.get_func_by_name("f.dereferenceable4").unwrap(); assert_eq!(deref4_fn.return_attributes.len(), 1); assert_eq!( deref4_fn.return_attributes[0], ParameterAttribute::Dereferenceable(4) ); let deref8_fn = module.get_func_by_name("f.dereferenceable8").unwrap(); assert_eq!(deref8_fn.return_attributes.len(), 1); assert_eq!( deref8_fn.return_attributes[0], ParameterAttribute::Dereferenceable(8) ); let deref4ornull_fn = module .get_func_by_name("f.dereferenceable4_or_null") .unwrap(); assert_eq!(deref4ornull_fn.return_attributes.len(), 1); assert_eq!( deref4ornull_fn.return_attributes[0], ParameterAttribute::DereferenceableOrNull(4) ); let deref8ornull_fn = module .get_func_by_name("f.dereferenceable8_or_null") .unwrap(); assert_eq!(deref8ornull_fn.return_attributes.len(), 1); assert_eq!( deref8ornull_fn.return_attributes[0], ParameterAttribute::DereferenceableOrNull(8) ); // Parameter attributes let f = module.get_func_by_name("f.param.zeroext").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!(param.attributes[0], ParameterAttribute::ZeroExt); let f = module.get_func_by_name("f.param.signext").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!(param.attributes[0], ParameterAttribute::SignExt); let f = module.get_func_by_name("f.param.inreg").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!(param.attributes[0], ParameterAttribute::InReg); let f = module.get_func_by_name("f.param.byval").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); #[cfg(feature = "llvm-11-or-lower")] assert_eq!(param.attributes[0], ParameterAttribute::UnknownAttribute); #[cfg(feature = "llvm-12-or-greater")] match ¶m.attributes[0] { ParameterAttribute::ByVal(ty) => match ty.as_ref() { Type::StructType { element_types, is_packed: false, } => { assert_eq!(element_types.len(), 2); }, ty => panic!("Expected a StructType with is_packed=false, got {:?}", ty), }, attr => panic!("Expected a ByVal, got {:?}", attr), } let f = module.get_func_by_name("f.param.inalloca").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); #[cfg(feature = "llvm-12-or-lower")] assert_eq!(param.attributes[0], ParameterAttribute::InAlloca); #[cfg(feature = "llvm-13-or-greater")] match ¶m.attributes[0] { ParameterAttribute::InAlloca(ty) => match ty.as_ref() { Type::IntegerType { bits: 8 } => {}, ty => panic!("Expected i8, got {:?}", ty), }, attr => panic!("Expected an InAlloca, got {:?}", attr), } let f = module.get_func_by_name("f.param.sret").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); #[cfg(feature = "llvm-11-or-lower")] assert_eq!(param.attributes[0], ParameterAttribute::SRet); #[cfg(feature = "llvm-12-or-greater")] match ¶m.attributes[0] { ParameterAttribute::SRet(ty) => match ty.as_ref() { Type::IntegerType { bits: 8 } => {}, ty => panic!("Expected i8, got {:?}", ty), }, attr => panic!("Expected an SRet, got {:?}", attr), } let f = module.get_func_by_name("f.param.noalias").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!(param.attributes[0], ParameterAttribute::NoAlias); let f = module.get_func_by_name("f.param.nocapture").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!(param.attributes[0], ParameterAttribute::NoCapture); let f = module.get_func_by_name("f.param.nest").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!(param.attributes[0], ParameterAttribute::Nest); let f = module.get_func_by_name("f.param.returned").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!(param.attributes[0], ParameterAttribute::Returned); let f = module.get_func_by_name("f.param.nonnull").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!(param.attributes[0], ParameterAttribute::NonNull); let f = module.get_func_by_name("f.param.dereferenceable").unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!(param.attributes[0], ParameterAttribute::Dereferenceable(4)); let f = module .get_func_by_name("f.param.dereferenceable_or_null") .unwrap(); assert_eq!(f.parameters.len(), 1); let param = &f.parameters[0]; assert_eq!(param.attributes.len(), 1); assert_eq!( param.attributes[0], ParameterAttribute::DereferenceableOrNull(4) ); // Function attributes let f = module.get_func_by_name("f.alignstack4").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::AlignStack(4)); let f = module.get_func_by_name("f.alignstack8").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::AlignStack(8)); let f = module.get_func_by_name("f.alwaysinline").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::AlwaysInline); let f = module.get_func_by_name("f.cold").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Cold); let f = module.get_func_by_name("f.convergent").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Convergent); let f = module.get_func_by_name("f.inlinehint").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::InlineHint); let f = module.get_func_by_name("f.jumptable").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::JumpTable); let f = module.get_func_by_name("f.minsize").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::MinimizeSize); let f = module.get_func_by_name("f.naked").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Naked); let f = module.get_func_by_name("f.nobuiltin").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::NoBuiltin); let f = module.get_func_by_name("f.noduplicate").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::NoDuplicate); let f = module.get_func_by_name("f.noimplicitfloat").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::NoImplicitFloat); let f = module.get_func_by_name("f.nonlazybind").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::NonLazyBind); let f = module.get_func_by_name("f.noredzone").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::NoRedZone); let f = module.get_func_by_name("f.noreturn").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::NoReturn); let f = module.get_func_by_name("f.nounwind").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::NoUnwind); let f = module.get_func_by_name("f.optnone").unwrap(); assert_eq!(f.function_attributes.len(), 2); assert_eq!(f.function_attributes[0], FunctionAttribute::NoInline); assert_eq!(f.function_attributes[1], FunctionAttribute::OptNone); let f = module.get_func_by_name("f.optsize").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::OptSize); let f = module.get_func_by_name("f.readnone").unwrap(); assert_eq!(f.function_attributes.len(), 1); // LLVM 16 no longer has the ReadNone attribute #[cfg(feature="llvm-15-or-lower")] assert_eq!(f.function_attributes[0], FunctionAttribute::ReadNone); let f = module.get_func_by_name("f.readonly").unwrap(); assert_eq!(f.function_attributes.len(), 1); // LLVM 16 no longer has the ReadOnly attribute #[cfg(feature="llvm-15-or-lower")] assert_eq!(f.function_attributes[0], FunctionAttribute::ReadOnly); let f = module.get_func_by_name("f.returns_twice").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::ReturnsTwice); let f = module.get_func_by_name("f.safestack").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::SafeStack); let f = module.get_func_by_name("f.sanitize_address").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::SanitizeAddress); let f = module.get_func_by_name("f.sanitize_memory").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::SanitizeMemory); let f = module.get_func_by_name("f.sanitize_thread").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::SanitizeThread); let f = module.get_func_by_name("f.ssp").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::StackProtect); let f = module.get_func_by_name("f.sspreq").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::StackProtectReq); let f = module.get_func_by_name("f.sspstrong").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!( f.function_attributes[0], FunctionAttribute::StackProtectStrong ); let f = module.get_func_by_name("f.thunk").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!( f.function_attributes[0], FunctionAttribute::StringAttribute { kind: "thunk".into(), value: "".into() } ); let f = module.get_func_by_name("f.uwtable").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::UWTable); let f = module.get_func_by_name("f.kvpair").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!( f.function_attributes[0], FunctionAttribute::StringAttribute { kind: "cpu".into(), value: "cortex-a8".into() } ); let f = module.get_func_by_name("f.norecurse").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::NoRecurse); let f = module.get_func_by_name("f.inaccessiblememonly").unwrap(); assert_eq!(f.function_attributes.len(), 1); // LLVM 16 no longer has InaccessibleMemOnly attribute #[cfg(feature="llvm-15-or-lower")] assert_eq!( f.function_attributes[0], FunctionAttribute::InaccessibleMemOnly ); let f = module .get_func_by_name("f.inaccessiblemem_or_argmemonly") .unwrap(); assert_eq!(f.function_attributes.len(), 1); // LLVM 16 no longer has InaccessibleMemOrArgMemOnly attribute #[cfg(feature="llvm-15-or-lower")] assert_eq!( f.function_attributes[0], FunctionAttribute::InaccessibleMemOrArgMemOnly ); let f = module.get_func_by_name("f.strictfp").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::StrictFP); // Test the memory(...) attribute that's in LLVM 16+ #[cfg(feature = "llvm-16-or-greater")] { let f = module.get_func_by_name("f.default_none").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Memory { default: MemoryEffect::None, argmem: MemoryEffect::None, inaccessible_mem: MemoryEffect::None }); let f = module.get_func_by_name("f.default_read").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Memory { default: MemoryEffect::Read, argmem: MemoryEffect::Read, inaccessible_mem: MemoryEffect::Read }); let f = module.get_func_by_name("f.default_write").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Memory { default: MemoryEffect::Write, argmem: MemoryEffect::Write, inaccessible_mem: MemoryEffect::Write }); let f = module.get_func_by_name("f.default_readwrite").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Memory { default: MemoryEffect::ReadWrite, argmem: MemoryEffect::ReadWrite, inaccessible_mem: MemoryEffect::ReadWrite }); let f = module.get_func_by_name("f.default_none_arg_readwrite").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Memory { default: MemoryEffect::None, argmem: MemoryEffect::ReadWrite, inaccessible_mem: MemoryEffect::None }); let f = module.get_func_by_name("f.default_readwrite_arg_none").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Memory { default: MemoryEffect::ReadWrite, argmem: MemoryEffect::None, inaccessible_mem: MemoryEffect::ReadWrite }); let f = module.get_func_by_name("f.arg_read").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Memory { default: MemoryEffect::None, argmem: MemoryEffect::Read, inaccessible_mem: MemoryEffect::None }); let f = module.get_func_by_name("f.inaccessiblemem_read").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Memory { default: MemoryEffect::None, argmem: MemoryEffect::None, inaccessible_mem: MemoryEffect::Read }); let f = module.get_func_by_name("f.default_read_inaccessiblemem_write_arg_none").unwrap(); assert_eq!(f.function_attributes.len(), 1); assert_eq!(f.function_attributes[0], FunctionAttribute::Memory { default: MemoryEffect::Read, argmem: MemoryEffect::None, inaccessible_mem: MemoryEffect::Write }); } } #[cfg(feature = "llvm-11-or-greater")] #[test] fn float_types() { init_logging(); let path = llvm_bc_dir().join("float_types.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); let f = module.get_func_by_name("takes_half").unwrap(); assert_eq!( module.type_of(f.parameters.get(0).unwrap()), module.types.fp(FPType::Half) ); let f = module.get_func_by_name("takes_bfloat").unwrap(); assert_eq!( module.type_of(f.parameters.get(0).unwrap()), module.types.fp(FPType::BFloat) ); let f = module.get_func_by_name("takes_float").unwrap(); assert_eq!( module.type_of(f.parameters.get(0).unwrap()), module.types.fp(FPType::Single) ); let f = module.get_func_by_name("takes_double").unwrap(); assert_eq!( module.type_of(f.parameters.get(0).unwrap()), module.types.fp(FPType::Double) ); let f = module.get_func_by_name("takes_fp128").unwrap(); assert_eq!( module.type_of(f.parameters.get(0).unwrap()), module.types.fp(FPType::FP128) ); let f = module.get_func_by_name("takes_x86_fp80").unwrap(); assert_eq!( module.type_of(f.parameters.get(0).unwrap()), module.types.fp(FPType::X86_FP80) ); let f = module.get_func_by_name("takes_ppc_fp128").unwrap(); assert_eq!( module.type_of(f.parameters.get(0).unwrap()), module.types.fp(FPType::PPC_FP128) ); let f = module.get_func_by_name("returns_half").unwrap(); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( f.return_type, module.types.pointer_to(module.types.fp(FPType::Half)) ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(f.return_type, module.types.pointer()); let f = module.get_func_by_name("returns_bfloat").unwrap(); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( f.return_type, module.types.pointer_to(module.types.fp(FPType::BFloat)) ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(f.return_type, module.types.pointer()); let f = module.get_func_by_name("returns_float").unwrap(); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( f.return_type, module.types.pointer_to(module.types.fp(FPType::Single)) ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(f.return_type, module.types.pointer()); let f = module.get_func_by_name("returns_double").unwrap(); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( f.return_type, module.types.pointer_to(module.types.fp(FPType::Double)) ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(f.return_type, module.types.pointer()); let f = module.get_func_by_name("returns_fp128").unwrap(); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( f.return_type, module.types.pointer_to(module.types.fp(FPType::FP128)) ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(f.return_type, module.types.pointer()); let f = module.get_func_by_name("returns_x86_fp80").unwrap(); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( f.return_type, module.types.pointer_to(module.types.fp(FPType::X86_FP80)) ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(f.return_type, module.types.pointer()); let f = module.get_func_by_name("returns_ppc_fp128").unwrap(); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( f.return_type, module.types.pointer_to(module.types.fp(FPType::PPC_FP128)) ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!(f.return_type, module.types.pointer()); } #[test] fn datalayouts() { init_logging(); let path = llvm_bc_dir().join("hello.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); let data_layout = &module.data_layout; // Data layout changed from Clang 17 to 18, even w/ an explicit --target=x86_64-apple-macosx12.0.0 #[cfg(feature = "llvm-18-or-greater")] { assert_eq!( &data_layout.layout_str, "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" ); assert_eq!(&data_layout.endianness, &Endianness::LittleEndian); assert_eq!(&data_layout.mangling, &Some(Mangling::MachO)); assert_eq!( data_layout.alignments.ptr_alignment(270), &PointerLayout { size: 32, alignment: Alignment { abi: 32, pref: 32 }, index_size: 32 } ); assert_eq!( data_layout.alignments.ptr_alignment(271), &PointerLayout { size: 32, alignment: Alignment { abi: 32, pref: 32 }, index_size: 32 } ); assert_eq!( data_layout.alignments.ptr_alignment(272), &PointerLayout { size: 64, alignment: Alignment { abi: 64, pref: 64 }, index_size: 64 } ); assert_eq!( data_layout.alignments.ptr_alignment(0), &PointerLayout { size: 64, alignment: Alignment { abi: 64, pref: 64 }, index_size: 64 } ); assert_eq!( data_layout.alignments.ptr_alignment(33), &PointerLayout { size: 64, alignment: Alignment { abi: 64, pref: 64 }, index_size: 64 } ); assert_eq!( data_layout.alignments.int_alignment(64), &Alignment { abi: 64, pref: 64 } ); assert_eq!( data_layout.alignments.int_alignment(7), &Alignment { abi: 8, pref: 8 } ); assert_eq!( data_layout.alignments.int_alignment(26), &Alignment { abi: 32, pref: 32 } ); assert_eq!( data_layout.alignments.int_alignment(123456), &Alignment { abi: 128, pref: 128 } ); assert_eq!( data_layout.alignments.fp_alignment(FPType::Double), &Alignment { abi: 64, pref: 64 } ); assert_eq!( data_layout.alignments.fp_alignment(FPType::X86_FP80), &Alignment { abi: 128, pref: 128 } ); assert_eq!( data_layout .native_int_widths .as_ref() .unwrap() .iter() .copied() .sorted() .collect::<Vec<_>>(), vec![8, 16, 32, 64] ); assert_eq!(data_layout.stack_alignment, Some(128)); } #[cfg(all(feature = "llvm-10-or-greater", feature = "llvm-17-or-lower"))] { assert_eq!( &data_layout.layout_str, "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" ); assert_eq!(&data_layout.endianness, &Endianness::LittleEndian); assert_eq!(&data_layout.mangling, &Some(Mangling::MachO)); assert_eq!( data_layout.alignments.ptr_alignment(270), &PointerLayout { size: 32, alignment: Alignment { abi: 32, pref: 32 }, index_size: 32 } ); assert_eq!( data_layout.alignments.ptr_alignment(271), &PointerLayout { size: 32, alignment: Alignment { abi: 32, pref: 32 }, index_size: 32 } ); assert_eq!( data_layout.alignments.ptr_alignment(272), &PointerLayout { size: 64, alignment: Alignment { abi: 64, pref: 64 }, index_size: 64 } ); assert_eq!( data_layout.alignments.ptr_alignment(0), &PointerLayout { size: 64, alignment: Alignment { abi: 64, pref: 64 }, index_size: 64 } ); assert_eq!( data_layout.alignments.ptr_alignment(33), &PointerLayout { size: 64, alignment: Alignment { abi: 64, pref: 64 }, index_size: 64 } ); assert_eq!( data_layout.alignments.int_alignment(64), &Alignment { abi: 64, pref: 64 } ); assert_eq!( data_layout.alignments.int_alignment(7), &Alignment { abi: 8, pref: 8 } ); assert_eq!( data_layout.alignments.int_alignment(26), &Alignment { abi: 32, pref: 32 } ); assert_eq!( data_layout.alignments.int_alignment(123456), &Alignment { abi: 64, pref: 64 } ); assert_eq!( data_layout.alignments.fp_alignment(FPType::Double), &Alignment { abi: 64, pref: 64 } ); assert_eq!( data_layout.alignments.fp_alignment(FPType::X86_FP80), &Alignment { abi: 128, pref: 128 } ); assert_eq!( data_layout .native_int_widths .as_ref() .unwrap() .iter() .copied() .sorted() .collect::<Vec<_>>(), vec![8, 16, 32, 64] ); assert_eq!(data_layout.stack_alignment, Some(128)); } #[cfg(feature = "llvm-9-or-lower")] { assert_eq!( &data_layout.layout_str, "e-m:o-i64:64-f80:128-n8:16:32:64-S128" ); assert_eq!(&data_layout.endianness, &Endianness::LittleEndian); assert_eq!(&data_layout.mangling, &Some(Mangling::MachO)); assert_eq!( data_layout.alignments.ptr_alignment(0), &PointerLayout { size: 64, alignment: Alignment { abi: 64, pref: 64 }, index_size: 64 } ); assert_eq!( data_layout.alignments.ptr_alignment(33), &PointerLayout { size: 64, alignment: Alignment { abi: 64, pref: 64 }, index_size: 64 } ); assert_eq!( data_layout.alignments.int_alignment(64), &Alignment { abi: 64, pref: 64 } ); assert_eq!( data_layout.alignments.int_alignment(7), &Alignment { abi: 8, pref: 8 } ); assert_eq!( data_layout.alignments.int_alignment(26), &Alignment { abi: 32, pref: 32 } ); assert_eq!( data_layout.alignments.int_alignment(123456), &Alignment { abi: 64, pref: 64 } ); assert_eq!( data_layout.alignments.fp_alignment(FPType::Double), &Alignment { abi: 64, pref: 64 } ); assert_eq!( data_layout.alignments.fp_alignment(FPType::X86_FP80), &Alignment { abi: 128, pref: 128 } ); assert_eq!( data_layout .native_int_widths .as_ref() .unwrap() .iter() .copied() .sorted() .collect::<Vec<_>>(), vec![8, 16, 32, 64] ); assert_eq!(data_layout.stack_alignment, Some(128)); } assert_eq!( data_layout.alignments.type_alignment(&module.types.int(26)), &Alignment { abi: 32, pref: 32 } ); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( data_layout .alignments .type_alignment(&module.types.pointer_in_addr_space(module.types.int(32), 2)), &Alignment { abi: 64, pref: 64 } ); #[cfg(feature = "llvm-15-or-greater")] assert_eq!( data_layout .alignments .type_alignment(&module.types.pointer_in_addr_space(2)), &Alignment { abi: 64, pref: 64 } ); #[cfg(feature = "llvm-14-or-lower")] assert_eq!( data_layout .alignments .type_alignment(&module.types.pointer_to(module.types.func_type( module.types.void(), vec![], false ))), &Alignment { abi: 64, pref: 64 } ); } #[test] fn throw() { let _ = env_logger::builder().is_test(true).try_init(); // capture log messages with test harness let path = cxx_llvm_bc_dir().join("throw.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); // See https://github.com/cdisselkoen/llvm-ir/pull/30 for func in module.functions { for block in func.basic_blocks { if let Terminator::Invoke(i) = block.term { i.get_type(&module.types); } } } } /* #[test] fn fences() { init_logging(); let path = llvm_bc_dir().join("fences.ll.bc"); let module = Module::from_bc_path(&path).expect("Failed to parse module"); let f = module.get_func_by_name("fences").unwrap(); let block = &f.basic_blocks[0]; let seq_cst: &instruction::Fence = &block.instrs[0].clone().try_into().expect("Should be a fence"); assert_eq!(seq_cst.atomicity.mem_ordering, MemoryOrdering::SequentiallyConsistent); let acq: &instruction::Fence = &block.instrs[1].clone().try_into().expect("Should be a fence"); assert_eq!(acq.atomicity.mem_ordering, MemoryOrdering::Acquire); let rel: &instruction::Fence = &block.instrs[2].clone().try_into().expect("Should be a fence"); assert_eq!(rel.atomicity.mem_ordering, MemoryOrdering::Release); let acq_rel: &instruction::Fence = &block.instrs[3].clone().try_into().expect("Should be a fence"); assert_eq!(acq_rel.atomicity.mem_ordering, MemoryOrdering::AcquireRelease); let syncscope: &instruction::Fence = &block.instrs[4].clone().try_into().expect("Should be a fence"); assert_eq!(syncscope.atomicity.mem_ordering, MemoryOrdering::SequentiallyConsistent); assert_eq!(syncscope.atomicity.synch_scope, SynchronizationScope::SingleThread); } */ #[test] fn parseir() -> Result<(), Box<dyn std::error::Error>> { let ir = "define void @f() { ret void }"; let module = Module::from_ir_str(ir)?; assert_eq!(module.functions.len(), 1); let func = &module.functions[0]; assert_eq!(func.name, "f"); assert_eq!(func.parameters.len(), 0); assert_eq!(func.is_var_arg, false); assert_eq!(func.return_type, module.types.void()); assert_eq!(func.basic_blocks.len(), 1); let bb = &func.basic_blocks[0]; assert_eq!(bb.name, Name::Number(0)); assert_eq!(bb.instrs.len(), 0); let ret: &terminator::Ret = &bb .term .clone() .try_into() .unwrap_or_else(|_| panic!("Terminator should be a Ret but is {:?}", &bb.term)); assert_eq!( ret.return_operand, None ); assert_eq!(&ret.to_string(), "ret void"); // this file was compiled without debuginfo, so nothing should have a debugloc assert_eq!(func.debugloc, None); assert_eq!(ret.debugloc, None); Ok(()) }