//! Emulate LLVM intrinsics

use crate::intrinsics::*;
use crate::prelude::*;

pub(crate) fn codegen_llvm_intrinsic_call<'tcx>(
    fx: &mut FunctionCx<'_, '_, 'tcx>,
    intrinsic: &str,
    args: &[Spanned<mir::Operand<'tcx>>],
    ret: CPlace<'tcx>,
    target: Option<BasicBlock>,
    span: Span,
) {
    if intrinsic.starts_with("llvm.aarch64") {
        return llvm_aarch64::codegen_aarch64_llvm_intrinsic_call(fx, intrinsic, args, ret, target);
    }
    if intrinsic.starts_with("llvm.x86") {
        return llvm_x86::codegen_x86_llvm_intrinsic_call(fx, intrinsic, args, ret, target, span);
    }

    match intrinsic {
        "llvm.prefetch" => {
            // Nothing to do. This is merely a perf hint.
        }

        _ if intrinsic.starts_with("llvm.ctlz.v") => {
            intrinsic_args!(fx, args => (a); intrinsic);

            simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| {
                fx.bcx.ins().clz(lane)
            });
        }

        _ if intrinsic.starts_with("llvm.ctpop.v") => {
            intrinsic_args!(fx, args => (a); intrinsic);

            simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| {
                fx.bcx.ins().popcnt(lane)
            });
        }

        _ if intrinsic.starts_with("llvm.fma.v") => {
            intrinsic_args!(fx, args => (x,y,z); intrinsic);

            simd_trio_for_each_lane(
                fx,
                x,
                y,
                z,
                ret,
                &|fx, _lane_ty, _res_lane_ty, lane_x, lane_y, lane_z| {
                    fx.bcx.ins().fma(lane_x, lane_y, lane_z)
                },
            );
        }

        "llvm.fptosi.sat.v4i32.v4f32" => {
            intrinsic_args!(fx, args => (a); intrinsic);

            simd_for_each_lane(fx, a, ret, &|fx, _lane_ty, _res_lane_ty, lane| {
                fx.bcx.ins().fcvt_to_sint_sat(types::I32, lane)
            });
        }

        _ if intrinsic.starts_with("llvm.roundeven.v") => {
            intrinsic_args!(fx, args => (v); intrinsic);

            simd_for_each_lane(fx, v, ret, &|fx, _lane_ty, _res_lane_ty, lane| {
                fx.bcx.ins().nearest(lane)
            });
        }

        _ => {
            fx.tcx
                .dcx()
                .warn(format!("unsupported llvm intrinsic {}; replacing with trap", intrinsic));
            let msg = format!(
                "{intrinsic} is not yet supported.\n\
                 See https://github.com/rust-lang/rustc_codegen_cranelift/issues/171\n\
                 Please open an issue at https://github.com/rust-lang/rustc_codegen_cranelift/issues"
            );
            crate::base::codegen_panic_nounwind(fx, &msg, span);
            return;
        }
    }

    let dest = target.expect("all llvm intrinsics used by stdlib should return");
    let ret_block = fx.get_block(dest);
    fx.bcx.ins().jump(ret_block, &[]);
}
