//! These tests simply ensure that we can parse all of the `.bc` files in LLVM 8's `test/Bitcode` directory without crashing.
//! Human-readable `.ll` versions of these files can be found in the LLVM repo at `test/Bitcode` on the git branch `release_80`.

use llvm_ir::Module;
use std::path::Path;

macro_rules! llvm_test {
    ($path:expr, $func:ident) => {
        #[test]
        #[allow(non_snake_case)]
        fn $func() {
            let _ = env_logger::builder().is_test(true).try_init(); // capture log messages with test harness
            let path = Path::new($path);
            let _ = Module::from_bc_path(&path).expect("Failed to parse module");
        }
    };
}

llvm_test!(
    "tests/llvm_bc/aggregateInstructions.3.2.ll.bc",
    aggregateInstructions
);
llvm_test!(
    "tests/llvm_bc/atomic-no-syncscope.ll.bc",
    atomic_no_syncscope
);
llvm_test!("tests/llvm_bc/attributes-3.3.ll.bc", attributes);
llvm_test!(
    "tests/llvm_bc/auto_upgrade_intrinsics.bc",
    auto_upgrade_intrinsics
);
llvm_test!(
    "tests/llvm_bc/avr-calling-conventions.ll.bc",
    avr_calling_conventions
);
llvm_test!(
    "tests/llvm_bc/binaryFloatInstructions.3.2.ll.bc",
    binaryFloatInstructions
);
llvm_test!(
    "tests/llvm_bc/binaryIntInstructions.3.2.ll.bc",
    binaryIntInstructions
);
llvm_test!(
    "tests/llvm_bc/bitwiseInstructions.3.2.ll.bc",
    bitwiseInstructions
);
llvm_test!(
    "tests/llvm_bc/calling-conventions.3.2.ll.bc",
    calling_conventions
);
llvm_test!("tests/llvm_bc/case-ranges-3.3.ll.bc", case_ranges);
llvm_test!("tests/llvm_bc/cmpxchg-upgrade.ll.bc", cmpxchg_upgrade);
llvm_test!("tests/llvm_bc/cmpxchg.3.6.ll.bc", cmpxchg);
llvm_test!("tests/llvm_bc/compatibility-3.6.ll.bc", compatibility_3_6);
llvm_test!("tests/llvm_bc/compatibility-3.7.ll.bc", compatibility_3_7);
llvm_test!("tests/llvm_bc/compatibility-3.8.ll.bc", compatibility_3_8);
llvm_test!("tests/llvm_bc/compatibility-3.9.ll.bc", compatibility_3_9);
llvm_test!("tests/llvm_bc/compatibility-4.0.ll.bc", compatibility_4_0);
llvm_test!("tests/llvm_bc/compatibility-5.0.ll.bc", compatibility_5_0);
llvm_test!("tests/llvm_bc/compatibility-6.0.ll.bc", compatibility_6_0);
llvm_test!("tests/llvm_bc/constantsTest.3.2.ll.bc", constantsTest);
llvm_test!(
    "tests/llvm_bc/conversionInstructions.3.2.ll.bc",
    conversionInstructions
);
llvm_test!(
    "tests/llvm_bc/DICompileUnit-no-DWOId.ll.bc",
    DICompileUnit_no_DWOId
);
llvm_test!("tests/llvm_bc/DIExpression-4.0.ll.bc", DIExpression_4_0);
llvm_test!(
    "tests/llvm_bc/DIExpression-aggresult.ll.bc",
    DIExpression_aggresult
);
llvm_test!("tests/llvm_bc/DIExpression-deref.ll.bc", DIExpression_deref);
llvm_test!(
    "tests/llvm_bc/DIExpression-minus-upgrade.ll.bc",
    DIExpression_minus_upgrade
);
llvm_test!(
    "tests/llvm_bc/diglobalvariable-3.8.ll.bc",
    diglobalvariable_3_8
);
llvm_test!(
    "tests/llvm_bc/DIGlobalVariableExpression.ll.bc",
    DIGlobalVariableExpression
);
llvm_test!(
    "tests/llvm_bc/DIGlobalVariableExpression2.ll.bc",
    DIGlobalVariableExpression2
);
llvm_test!(
    "tests/llvm_bc/dilocalvariable-3.9.ll.bc",
    dilocalvariable_3_9
);
llvm_test!(
    "tests/llvm_bc/DILocalVariable-explicit-tags.ll.bc",
    DILocalVariable_explicit_tags
);
llvm_test!(
    "tests/llvm_bc/DILocation-implicit-code.ll.bc",
    DILocation_implicit_code
);
llvm_test!("tests/llvm_bc/DINamespace.ll.bc", DINamespace);
llvm_test!(
    "tests/llvm_bc/DISubprogram-distinct-definitions.ll.bc",
    DISubprogram_distinct_definitions
);
llvm_test!("tests/llvm_bc/DISubprogram-v4.ll.bc", DISubprogram_v4);
llvm_test!("tests/llvm_bc/disubrange-v0.ll.bc", disubrange_v0);
llvm_test!("tests/llvm_bc/dityperefs-3.8.ll.bc", dityperefs);
llvm_test!("tests/llvm_bc/drop-debug-info.3.5.ll.bc", drop_debug_info);
llvm_test!(
    "tests/llvm_bc/function-local-metadata.3.5.ll.bc",
    function_local_metadata
);
llvm_test!("tests/llvm_bc/global-variables.3.2.ll.bc", global_variables);
llvm_test!(
    "tests/llvm_bc/highLevelStructure.3.2.ll.bc",
    highLevelStructure
);
// llvm_test!("tests/llvm_bc/invalid.ll.bc", invalid);  // we omit this .bc file because it is intentionally invalid
llvm_test!("tests/llvm_bc/linkage-types-3.2.ll.bc", linkage_types);
llvm_test!(
    "tests/llvm_bc/local-linkage-default-visibility.3.4.ll.bc",
    local_linkage_default_visibility
);
llvm_test!("tests/llvm_bc/memInstructions.3.2.ll.bc", memInstructions);
llvm_test!("tests/llvm_bc/metadata-source.ll.bc", metadata_source);
llvm_test!("tests/llvm_bc/metadata.3.5.ll.bc", metadata);
llvm_test!("tests/llvm_bc/miscInstructions.3.2.ll.bc", miscInstructions);
// llvm_test!("tests/llvm_bc/null-type.ll.bc", null_type);  // we omit this .bc file because it is intentionally invalid
llvm_test!("tests/llvm_bc/old-aliases.ll.bc", old_aliases);
// llvm_test!("tests/llvm_bc/pr18704.ll.bc", pr18704);  // we omit this .bc file because it is intentionally invalid
llvm_test!(
    "tests/llvm_bc/standardCIntrinsic.3.2.ll.bc",
    standardCIntrinsic
);
llvm_test!(
    "tests/llvm_bc/terminatorInstructions.3.2.ll.bc",
    terminatorInstructions
);
llvm_test!(
    "tests/llvm_bc/thinlto-summary-local-5.0.ll.bc",
    thinlto_summary_local
);
llvm_test!(
    "tests/llvm_bc/upgrade-dbg-checksum.ll.bc",
    upgrade_dbg_checksum
);
llvm_test!("tests/llvm_bc/upgrade-dbg-value.ll.bc", upgrade_dbg_value);
llvm_test!(
    "tests/llvm_bc/upgrade-debug-info-for-profiling.ll.bc",
    upgrade_debug_info_for_profiling
);
llvm_test!(
    "tests/llvm_bc/upgrade-global-ctors.ll.bc",
    upgrade_global_ctors
);
llvm_test!(
    "tests/llvm_bc/upgrade-importedentity.ll.bc",
    upgrade_importedentity
);
llvm_test!(
    "tests/llvm_bc/upgrade-loop-metadata.ll.bc",
    upgrade_loop_metadata
);
llvm_test!(
    "tests/llvm_bc/upgrade-objcretainrelease-asm.ll.bc",
    upgrade_objcretainrelease_asm
);
llvm_test!(
    "tests/llvm_bc/upgrade-objcretainrelease.ll.bc",
    upgrade_objcretainrelease
);
llvm_test!(
    "tests/llvm_bc/upgrade-pointer-address-space.ll.bc",
    upgrade_pointer_address_space
);
llvm_test!(
    "tests/llvm_bc/upgrade-subprogram-this.ll.bc",
    upgrade_subprogram_this
);
llvm_test!("tests/llvm_bc/upgrade-subprogram.ll.bc", upgrade_subprogram);
llvm_test!(
    "tests/llvm_bc/variableArgumentIntrinsic.3.2.ll.bc",
    variableArgumentIntrinsic
);
llvm_test!(
    "tests/llvm_bc/vectorInstructions.3.2.ll.bc",
    vectorInstructions
);
llvm_test!(
    "tests/llvm_bc/visibility-styles.3.2.ll.bc",
    visibility_styles
);
llvm_test!(
    "tests/llvm_bc/weak-cmpxchg-upgrade.ll.bc",
    weak_cmpxchg_upgrade
);
llvm_test!("tests/llvm_bc/weak-macho-3.5.ll.bc", weak_macho);

use either::Either;
use llvm_ir::instruction::{Atomicity, MemoryOrdering, SynchronizationScope};
#[cfg(feature = "llvm-14-or-lower")]
use llvm_ir::types::NamedStructDef;
use llvm_ir::*;
use std::convert::TryInto;

/// Additionally ensure that certain constructs were parsed correctly
/// (these constructs don't currently appear in any of the basic_tests)
#[test]
#[allow(non_snake_case)]
#[allow(clippy::cognitive_complexity)]
fn DILocation_implicit_code_extra_checks() {
    let _ = env_logger::builder().is_test(true).try_init(); // capture log messages with test harness
    let path = Path::new("tests/llvm_bc/DILocation-implicit-code.ll.bc");
    let module = Module::from_bc_path(&path).expect("Failed to parse module");
    let func = module
        .get_func_by_name("_Z5test1v")
        .expect("Failed to find function");

    let entry = func
        .get_bb_by_name(&Name::from("entry"))
        .expect("Failed to find entry bb");
    let invoke: &terminator::Invoke = &entry
        .term
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected an invoke, got {:?}", &entry.term));
    if let Either::Right(Operand::ConstantOperand(cref)) = &invoke.function {
        if let Constant::GlobalReference { name, .. } = cref.as_ref() {
            assert_eq!(name, &Name::from("_ZN1A3fooEi"));
        } else {
            panic!(
                "Expected invoke.function to be a GlobalReference; instead it was another kind of Constant: {:?}",
                cref
            );
        }
    } else {
        panic!(
            "Expected invoke.function to be a GlobalReference; instead it was {:?}",
            &invoke.function
        );
    }
    assert_eq!(invoke.arguments.len(), 2);
    if let Operand::LocalOperand { name, ty } = &invoke.arguments[0].0 {
        assert_eq!(name, &Name::from("a"));
        #[cfg(feature = "llvm-14-or-lower")]
        if let Type::PointerType { pointee_type, .. } = ty.as_ref() {
            if let Type::NamedStructType { name } = pointee_type.as_ref() {
                match module.types.named_struct_def(name) {
                    None => panic!("Failed to find {} in module.types", name),
                    Some(NamedStructDef::Opaque) => {
                        panic!("Didn't expect {} to be an opaque struct type", name)
                    },
                    Some(NamedStructDef::Defined(ty)) => {
                        assert_eq!(ty, &module.types.struct_of(vec![module.types.i8()], false));
                    },
                }
            } else {
                panic!("Expected invoke.arguments[0].0 to be a pointer to a Type::NamedStructType; instead it was a pointer to a {:?}", **pointee_type);
            }
        } else {
            panic!(
                "Exected invoke.arguments[0].0 to be of PointerType; instead it was {:?}",
                ty.as_ref()
            );
        }
        #[cfg(feature = "llvm-15-or-greater")]
        assert!(matches!(ty.as_ref(), Type::PointerType { .. }));
    } else {
        panic!(
            "Expected invoke.arguments[0].0 to be a local operand; instead it was {:?}",
            &invoke.arguments[0].0
        );
    }
    assert_eq!(
        invoke.arguments[1].0,
        Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 0 }))
    );
    assert_eq!(invoke.return_label, Name::from("invoke.cont"));
    assert_eq!(invoke.exception_label, Name::from("lpad"));
    #[cfg(feature = "llvm-14-or-lower")]
    let expected_fmt = "%0 = invoke @_ZN1A3fooEi(%struct.A* %a, i32 0) to label %invoke.cont unwind label %lpad (with debugloc)";
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "%0 = invoke @_ZN1A3fooEi(ptr %a, i32 0) to label %invoke.cont unwind label %lpad (with debugloc)";
    assert_eq!(&format!("{}", invoke), expected_fmt);

    // For the rest of the function, our numbered variables are one-off the
    // numbers in the .ll file in the LLVM repo.
    // For instance, the .ll file in the LLVM repo has %0 as the dest of the
    // first 'landingpad' instruction, while we number it %1.
    // The discrepancy is because we assign %0 to be the result of the invoke
    // terminator in the entry block. Although this particular .ll file doesn't
    // assign the result of the invoke, the LLVM 8/9/10 docs on 'invoke' -- and
    // in particular the examples -- are clear that 'invoke' does produce a
    // result.

    let lpad = func
        .get_bb_by_name(&Name::from("lpad"))
        .expect("Failed to find lpad bb");
    let landingpad: &instruction::LandingPad = &lpad.instrs[0]
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected a landingpad, got {:?}", &lpad.instrs[0]));
    let expected_landingpad_resultty = module.types.struct_of(
        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.i32(),
        ],
        false,
    );
    assert_eq!(landingpad.result_type, expected_landingpad_resultty);
    assert_eq!(landingpad.clauses.len(), 1);
    assert_eq!(landingpad.cleanup, false);
    assert_eq!(landingpad.dest, Name::Number(1));
    #[cfg(feature = "llvm-14-or-lower")]
    let expected_fmt = "%1 = landingpad { i8*, i32 } (with debugloc)";
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "%1 = landingpad { ptr, i32 } (with debugloc)";
    assert_eq!(&format!("{}", landingpad), expected_fmt);
    let eval: &instruction::ExtractValue = &lpad.instrs[1]
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected an extractvalue, got {:?}", &lpad.instrs[1]));
    assert_eq!(
        eval.aggregate,
        Operand::LocalOperand {
            name: Name::Number(1),
            ty: expected_landingpad_resultty.clone()
        }
    );
    assert_eq!(eval.indices.len(), 1);
    assert_eq!(eval.indices[0], 0);
    assert_eq!(eval.dest, Name::Number(2));
    #[cfg(feature = "llvm-14-or-lower")]
    let expected_fmt = "%2 = extractvalue { i8*, i32 } %1, 0 (with debugloc)";
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "%2 = extractvalue { ptr, i32 } %1, 0 (with debugloc)";
    assert_eq!(&format!("{}", eval), expected_fmt);

    // From this point on, our numbers are off by 2 instead of 1, due to
    // numbering the invoke terminator in the 'catch' block.
    // See notes above.

    let lpad1 = func
        .get_bb_by_name(&Name::from("lpad1"))
        .expect("Failed to find lpad1 bb");
    let landingpad: &instruction::LandingPad = &lpad1.instrs[0]
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected a landingpad, got {:?}", &lpad1.instrs[0]));
    assert_eq!(landingpad.result_type, expected_landingpad_resultty);
    assert_eq!(landingpad.clauses.len(), 0);
    assert_eq!(landingpad.cleanup, true);
    #[cfg(feature = "llvm-14-or-lower")]
    let expected_fmt = "%10 = landingpad { i8*, i32 } cleanup (with debugloc)";
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "%10 = landingpad { ptr, i32 } cleanup (with debugloc)";
    assert_eq!(&format!("{}", landingpad), expected_fmt);
    let eval: &instruction::ExtractValue = &lpad1.instrs[3]
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected an extractvalue, got {:?}", &lpad.instrs[3]));
    assert_eq!(
        eval.aggregate,
        Operand::LocalOperand {
            name: Name::Number(10),
            ty: expected_landingpad_resultty.clone()
        }
    );
    assert_eq!(eval.indices.len(), 1);
    assert_eq!(eval.indices[0], 1);
    assert_eq!(eval.dest, Name::Number(12));
    #[cfg(feature = "llvm-14-or-lower")]
    let expected_fmt = "%12 = extractvalue { i8*, i32 } %10, 1 (with debugloc)";
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "%12 = extractvalue { ptr, i32 } %10, 1 (with debugloc)";
    assert_eq!(&format!("{}", eval), expected_fmt);

    let trycont = func
        .get_bb_by_name(&Name::from("try.cont"))
        .expect("Failed to find trycont bb");
    let u: &terminator::Unreachable = &trycont
        .term
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected an unreachable, got {:?}", &trycont.term));
    assert_eq!(&format!("{}", u), "unreachable (with debugloc)");

    let ehresume = func
        .get_bb_by_name(&Name::from("eh.resume"))
        .expect("Failed to find ehresume bb");
    let ival: &instruction::InsertValue = &ehresume.instrs[2]
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected an insertvalue, got {:?}", &ehresume.instrs[2]));
    assert_eq!(
        ival.aggregate,
        Operand::ConstantOperand(ConstantRef::new(Constant::Undef(
            expected_landingpad_resultty.clone()
        )))
    );
    assert_eq!(
        ival.element,
        Operand::LocalOperand {
            name: Name::from("exn4"),
            #[cfg(feature = "llvm-14-or-lower")]
            ty: module.types.pointer_to(module.types.i8()),
            #[cfg(feature = "llvm-15-or-greater")]
            ty: module.types.pointer(),
        }
    );
    assert_eq!(ival.indices.len(), 1);
    assert_eq!(ival.indices[0], 0);
    assert_eq!(ival.dest, Name::from("lpad.val"));
    #[cfg(feature = "llvm-14-or-lower")]
    let expected_fmt = "%lpad.val = insertvalue { i8*, i32 } undef, i8* %exn4, 0 (with debugloc)";
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "%lpad.val = insertvalue { ptr, i32 } undef, ptr %exn4, 0 (with debugloc)";
    assert_eq!(&format!("{}", ival), expected_fmt);
    let ival2: &instruction::InsertValue = &ehresume.instrs[3]
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected an insertvalue, got {:?}", &ehresume.instrs[3]));
    assert_eq!(
        ival2.aggregate,
        Operand::LocalOperand {
            name: Name::from("lpad.val"),
            ty: expected_landingpad_resultty.clone()
        }
    );
    assert_eq!(
        ival2.element,
        Operand::LocalOperand {
            name: Name::from("sel5"),
            ty: module.types.i32()
        }
    );
    assert_eq!(ival2.indices.len(), 1);
    assert_eq!(ival2.indices[0], 1);
    assert_eq!(ival2.dest, Name::from("lpad.val6"));
    #[cfg(feature = "llvm-14-or-lower")]
    let expected_fmt = "%lpad.val6 = insertvalue { i8*, i32 } %lpad.val, i32 %sel5, 1 (with debugloc)";
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "%lpad.val6 = insertvalue { ptr, i32 } %lpad.val, i32 %sel5, 1 (with debugloc)";
    assert_eq!(&format!("{}", ival2), expected_fmt);
    let resume: &terminator::Resume = &ehresume
        .term
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected a resume, got {:?}", &ehresume.term));
    assert_eq!(
        resume.operand,
        Operand::LocalOperand {
            name: Name::from("lpad.val6"),
            ty: expected_landingpad_resultty.clone()
        }
    );
    #[cfg(feature = "llvm-14-or-lower")]
    let expected_fmt = "resume { i8*, i32 } %lpad.val6 (with debugloc)";
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "resume { ptr, i32 } %lpad.val6 (with debugloc)";
    assert_eq!(&format!("{}", resume), expected_fmt);
}

#[test]
fn atomics() {
    let _ = env_logger::builder().is_test(true).try_init(); // capture log messages with test harness
    let path = Path::new("tests/llvm_bc/compatibility-6.0.ll.bc");
    let module = Module::from_bc_path(&path).expect("Failed to parse module");
    let func = module
        .get_func_by_name("atomics")
        .expect("Failed to find function");
    let bb = &func.basic_blocks[0];
    let cmpxchg: &instruction::CmpXchg = &bb.instrs[0]
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected a cmpxchg, got {:?}", &bb.instrs[0]));
    assert_eq!(
        cmpxchg.address,
        Operand::LocalOperand {
            name: Name::from("word"),
            #[cfg(feature = "llvm-14-or-lower")]
            ty: module.types.pointer_to(module.types.i32()),
            #[cfg(feature = "llvm-15-or-greater")]
            ty: module.types.pointer(),
        }
    );
    assert_eq!(
        cmpxchg.expected,
        Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 0 }))
    );
    assert_eq!(
        cmpxchg.replacement,
        Operand::ConstantOperand(ConstantRef::new(Constant::Int { bits: 32, value: 4 }))
    );
    assert_eq!(cmpxchg.dest, Name::from("cmpxchg.0"));
    assert_eq!(cmpxchg.volatile, false);
    assert_eq!(
        cmpxchg.atomicity,
        Atomicity {
            synch_scope: SynchronizationScope::System,
            mem_ordering: MemoryOrdering::Monotonic
        }
    );
    assert_eq!(cmpxchg.failure_memory_ordering, MemoryOrdering::Monotonic);
    #[cfg(feature = "llvm-14-or-lower")]
    let expected_fmt = "%cmpxchg.0 = cmpxchg i32* %word, i32 0, i32 4 monotonic monotonic";
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "%cmpxchg.0 = cmpxchg ptr %word, i32 0, i32 4 monotonic monotonic";
    assert_eq!(&format!("{}", cmpxchg), expected_fmt);
    let atomicrmw: &instruction::AtomicRMW = &bb.instrs[8]
        .clone()
        .try_into()
        .unwrap_or_else(|_| panic!("Expected an atomicrmw, got {:?}", &bb.instrs[8]));
    assert_eq!(
        atomicrmw.address,
        Operand::LocalOperand {
            name: Name::from("word"),
            #[cfg(feature = "llvm-14-or-lower")]
            ty: module.types.pointer_to(module.types.i32()),
            #[cfg(feature = "llvm-15-or-greater")]
            ty: module.types.pointer(),
        }
    );
    assert_eq!(
        atomicrmw.value,
        Operand::ConstantOperand(ConstantRef::new(Constant::Int {
            bits: 32,
            value: 12
        }))
    );
    assert_eq!(atomicrmw.dest, Name::from("atomicrmw.xchg"));
    assert_eq!(module.type_of(atomicrmw), module.types.i32());
    #[cfg(feature = "llvm-9-or-lower")]
    let expected_fmt = "%atomicrmw.xchg = atomicrmw i32* %word, i32 12 not_atomic";
    #[cfg(all(feature = "llvm-10-or-greater", feature = "llvm-14-or-lower"))]
    let expected_fmt = "%atomicrmw.xchg = atomicrmw xchg i32* %word, i32 12 monotonic"; // I'm not sure why it's not_atomic for LLVM 9 and lower, but monotonic for LLVM 10+
    #[cfg(feature = "llvm-15-or-greater")]
    let expected_fmt = "%atomicrmw.xchg = atomicrmw xchg ptr %word, i32 12 monotonic"; // I'm not sure why it's not_atomic for LLVM 9 and lower, but monotonic for LLVM 10+
    assert_eq!(&format!("{}", atomicrmw), expected_fmt);
}