#include "CodeGen_Posix.h"

#include "LLVM_Headers.h"

namespace Halide {
namespace Internal {

using std::string;
using std::vector;

#if defined(WITH_POWERPC)

namespace {

/** A code generator that emits mips code from a given Halide stmt. */
class CodeGen_PowerPC : public CodeGen_Posix {
public:
    /** Create a powerpc code generator. Processor features can be
     * enabled using the appropriate flags in the target struct. */
    CodeGen_PowerPC(const Target &);

protected:
    void init_module() override;

    string mcpu() const override;
    string mattrs() const override;
    bool use_soft_float_abi() const override;
    int native_vector_bits() const override;

    using CodeGen_Posix::visit;

    /** Nodes for which we want to emit specific PowerPC intrinsics */
    // @{
    void visit(const Min *) override;
    void visit(const Max *) override;
    // @}
};

CodeGen_PowerPC::CodeGen_PowerPC(const Target &t)
    : CodeGen_Posix(t) {
}

const int max_intrinsic_args = 4;

struct PowerPCIntrinsic {
    const char *intrin_name;
    halide_type_t ret_type;
    const char *name;
    halide_type_t arg_types[max_intrinsic_args];
    Target::Feature feature = Target::FeatureEnd;
};

// clang-format off
const PowerPCIntrinsic intrinsic_defs[] = {
    {"llvm.ppc.altivec.vminsb", Int(8, 16), "min", {Int(8, 16), Int(8, 16)}},
    {"llvm.ppc.altivec.vminub", UInt(8, 16), "min", {UInt(8, 16), UInt(8, 16)}},
    {"llvm.ppc.altivec.vminsh", Int(16, 8), "min", {Int(16, 8), Int(16, 8)}},
    {"llvm.ppc.altivec.vminuh", UInt(16, 8), "min", {UInt(16, 8), UInt(16, 8)}},
    {"llvm.ppc.altivec.vminsw", Int(32, 4), "min", {Int(32, 4), Int(32, 4)}},
    {"llvm.ppc.altivec.vminuw", UInt(32, 4), "min", {UInt(32, 4), UInt(32, 4)}},
    {"llvm.ppc.altivec.vminfp", Float(32, 4), "min", {Float(32, 4), Float(32, 4)}},
    {"llvm.ppc.altivec.vminsd", Int(64, 2), "min", {Int(64, 2), Int(64, 2)}, Target::POWER_ARCH_2_07},
    {"llvm.ppc.altivec.vminud", UInt(64, 2), "min", {UInt(64, 2), UInt(64, 2)}, Target::POWER_ARCH_2_07},
    {"llvm.ppc.vsx.xvmindp", Float(64, 2), "min", {Float(64, 2), Float(64, 2)}, Target::VSX},

    {"llvm.ppc.altivec.vmaxsb", Int(8, 16), "max", {Int(8, 16), Int(8, 16)}},
    {"llvm.ppc.altivec.vmaxub", UInt(8, 16), "max", {UInt(8, 16), UInt(8, 16)}},
    {"llvm.ppc.altivec.vmaxsh", Int(16, 8), "max", {Int(16, 8), Int(16, 8)}},
    {"llvm.ppc.altivec.vmaxuh", UInt(16, 8), "max", {UInt(16, 8), UInt(16, 8)}},
    {"llvm.ppc.altivec.vmaxsw", Int(32, 4), "max", {Int(32, 4), Int(32, 4)}},
    {"llvm.ppc.altivec.vmaxuw", UInt(32, 4), "max", {UInt(32, 4), UInt(32, 4)}},
    {"llvm.ppc.altivec.vmaxfp", Float(32, 4), "max", {Float(32, 4), Float(32, 4)}},
    {"llvm.ppc.altivec.vmaxsd", Int(64, 2), "max", {Int(64, 2), Int(64, 2)}, Target::POWER_ARCH_2_07},
    {"llvm.ppc.altivec.vmaxud", UInt(64, 2), "max", {UInt(64, 2), UInt(64, 2)}, Target::POWER_ARCH_2_07},
    {"llvm.ppc.vsx.xvmaxdp", Float(64, 2), "max", {Float(64, 2), Float(64, 2)}, Target::VSX},

    {"llvm.ppc.altivec.vaddsbs", Int(8, 16), "saturating_add", {Int(8, 16), Int(8, 16)}},
    {"llvm.ppc.altivec.vaddubs", UInt(8, 16), "saturating_add", {UInt(8, 16), UInt(8, 16)}},
    {"llvm.ppc.altivec.vaddshs", Int(16, 8), "saturating_add", {Int(16, 8), Int(16, 8)}},
    {"llvm.ppc.altivec.vadduhs", UInt(16, 8), "saturating_add", {UInt(16, 8), UInt(16, 8)}},
    {"llvm.ppc.altivec.vaddsws", Int(32, 4), "saturating_add", {Int(32, 4), Int(32, 4)}},
    {"llvm.ppc.altivec.vadduws", UInt(32, 4), "saturating_add", {UInt(32, 4), UInt(32, 4)}},

    {"llvm.ppc.altivec.vsubsbs", Int(8, 16), "saturating_sub", {Int(8, 16), Int(8, 16)}},
    {"llvm.ppc.altivec.vsububs", UInt(8, 16), "saturating_sub", {UInt(8, 16), UInt(8, 16)}},
    {"llvm.ppc.altivec.vsubshs", Int(16, 8), "saturating_sub", {Int(16, 8), Int(16, 8)}},
    {"llvm.ppc.altivec.vsubuhs", UInt(16, 8), "saturating_sub", {UInt(16, 8), UInt(16, 8)}},
    {"llvm.ppc.altivec.vsubsws", Int(32, 4), "saturating_sub", {Int(32, 4), Int(32, 4)}},
    {"llvm.ppc.altivec.vsubuws", UInt(32, 4), "saturating_sub", {UInt(32, 4), UInt(32, 4)}},

    {"llvm.ppc.altivec.vavgsb", Int(8, 16), "rounding_halving_add", {Int(8, 16), Int(8, 16)}},
    {"llvm.ppc.altivec.vavgub", UInt(8, 16), "rounding_halving_add", {UInt(8, 16), UInt(8, 16)}},
    {"llvm.ppc.altivec.vavgsh", Int(16, 8), "rounding_halving_add", {Int(16, 8), Int(16, 8)}},
    {"llvm.ppc.altivec.vavguh", UInt(16, 8), "rounding_halving_add", {UInt(16, 8), UInt(16, 8)}},
    {"llvm.ppc.altivec.vavgsw", Int(32, 4), "rounding_halving_add", {Int(32, 4), Int(32, 4)}},
    {"llvm.ppc.altivec.vavguw", UInt(32, 4), "rounding_halving_add", {UInt(32, 4), UInt(32, 4)}},
};
// clang-format on

void CodeGen_PowerPC::init_module() {
    CodeGen_Posix::init_module();

    for (const PowerPCIntrinsic &i : intrinsic_defs) {
        if (i.feature != Target::FeatureEnd && !target.has_feature(i.feature)) {
            continue;
        }

        Type ret_type = i.ret_type;
        vector<Type> arg_types;
        arg_types.reserve(max_intrinsic_args);
        for (halide_type_t j : i.arg_types) {
            if (j.bits == 0) {
                break;
            }
            arg_types.emplace_back(j);
        }

        auto *fn = declare_intrin_overload(i.name, ret_type, i.intrin_name, std::move(arg_types));
        fn->addFnAttr(llvm::Attribute::ReadNone);
        fn->addFnAttr(llvm::Attribute::NoUnwind);
    }
}

void CodeGen_PowerPC::visit(const Min *op) {
    if (op->type.is_vector()) {
        value = call_overloaded_intrin(op->type, "min", {op->a, op->b});
        if (value) {
            return;
        }
    }
    return CodeGen_Posix::visit(op);
}

void CodeGen_PowerPC::visit(const Max *op) {
    if (op->type.is_vector()) {
        value = call_overloaded_intrin(op->type, "max", {op->a, op->b});
        if (value) {
            return;
        }
    }
    return CodeGen_Posix::visit(op);
}

string CodeGen_PowerPC::mcpu() const {
    if (target.bits == 32) {
        return "ppc32";
    } else {
        if (target.has_feature(Target::POWER_ARCH_2_07)) {
            return "pwr8";
        } else if (target.has_feature(Target::VSX)) {
            return "pwr7";
        } else {
            return "ppc64";
        }
    }
}

string CodeGen_PowerPC::mattrs() const {
    string features;
    string separator;
    string enable;

    features += "+altivec";
    separator = ",";

    enable = target.has_feature(Target::VSX) ? "+" : "-";
    features += separator + enable + "vsx";
    separator = ",";

    enable = target.has_feature(Target::POWER_ARCH_2_07) ? "+" : "-";
    features += separator + enable + "power8-altivec";
    separator = ",";

    // These move instructions are defined in POWER ISA 2.06 but we do
    // not check for 2.06 currently.  So disable this for anything
    // lower than ISA 2.07
    features += separator + enable + "direct-move";
    separator = ",";

    return features;
}

bool CodeGen_PowerPC::use_soft_float_abi() const {
    return false;
}

int CodeGen_PowerPC::native_vector_bits() const {
    return 128;
}

}  // namespace

std::unique_ptr<CodeGen_Posix> new_CodeGen_PowerPC(const Target &target) {
    return std::make_unique<CodeGen_PowerPC>(target);
}

#else  // WITH_POWERPC

std::unique_ptr<CodeGen_Posix> new_CodeGen_PowerPC(const Target &target) {
    user_error << "PowerPC not enabled for this build of Halide.\n";
    return nullptr;
}

#endif  // WITH_POWERPC

}  // namespace Internal
}  // namespace Halide
