/*
 * Copyright (C) 2019 Intel Corporation. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 */

#include "simd_conversions.h"
#include "simd_common.h"
#include "../aot_emit_exception.h"
#include "../aot_emit_numberic.h"
#include "../../aot/aot_runtime.h"

static bool
simd_integer_narrow_x86(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
                        LLVMTypeRef in_vector_type, LLVMTypeRef out_vector_type,
                        const char *intrinsic)
{
    LLVMValueRef vector1, vector2, result;
    LLVMTypeRef param_types[2] = { in_vector_type, in_vector_type };

    if (!(vector2 = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                              in_vector_type, "vec2"))
        || !(vector1 = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                                 in_vector_type, "vec1"))) {
        return false;
    }

    if (!(result = aot_call_llvm_intrinsic(comp_ctx, func_ctx, intrinsic,
                                           out_vector_type, param_types, 2,
                                           vector1, vector2))) {
        HANDLE_FAILURE("LLVMBuildCall");
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

enum integer_sat_type {
    e_sat_i16x8 = 0,
    e_sat_i32x4,
    e_sat_i64x2,
    e_sat_i32x8,
};

static LLVMValueRef
simd_saturate(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
              enum integer_sat_type itype, LLVMValueRef vector,
              LLVMValueRef min, LLVMValueRef max, bool is_signed)
{
    LLVMValueRef result;
    LLVMTypeRef vector_type;

    LLVMTypeRef param_types[][2] = {
        { V128_i16x8_TYPE, V128_i16x8_TYPE },
        { V128_i32x4_TYPE, V128_i32x4_TYPE },
        { V128_i64x2_TYPE, V128_i64x2_TYPE },
        { 0 },
    };

    const char *smin_intrinsic[] = {
        "llvm.smin.v8i16",
        "llvm.smin.v4i32",
        "llvm.smin.v2i64",
        "llvm.smin.v8i32",
    };

    const char *umin_intrinsic[] = {
        "llvm.umin.v8i16",
        "llvm.umin.v4i32",
        "llvm.umin.v2i64",
        "llvm.umin.v8i32",
    };

    const char *smax_intrinsic[] = {
        "llvm.smax.v8i16",
        "llvm.smax.v4i32",
        "llvm.smax.v2i64",
        "llvm.smax.v8i32",
    };

    const char *umax_intrinsic[] = {
        "llvm.umax.v8i16",
        "llvm.umax.v4i32",
        "llvm.umax.v2i64",
        "llvm.umax.v8i32",
    };

    if (e_sat_i32x8 == itype) {
        if (!(vector_type = LLVMVectorType(I32_TYPE, 8))) {
            HANDLE_FAILURE("LLVMVectorType");
            return NULL;
        }

        param_types[itype][0] = vector_type;
        param_types[itype][1] = vector_type;
    }

    if (!(result = aot_call_llvm_intrinsic(
              comp_ctx, func_ctx,
              is_signed ? smin_intrinsic[itype] : umin_intrinsic[itype],
              param_types[itype][0], param_types[itype], 2, vector, max))
        || !(result = aot_call_llvm_intrinsic(
                 comp_ctx, func_ctx,
                 is_signed ? smax_intrinsic[itype] : umax_intrinsic[itype],
                 param_types[itype][0], param_types[itype], 2, result, min))) {
        return NULL;
    }

    return result;
}

static bool
simd_integer_narrow_common(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
                           enum integer_sat_type itype, bool is_signed)
{
    LLVMValueRef vec1, vec2, min, max, mask, result;
    LLVMTypeRef in_vector_type[] = { V128_i16x8_TYPE, V128_i32x4_TYPE,
                                     V128_i64x2_TYPE };
    LLVMTypeRef min_max_type[] = { INT16_TYPE, I32_TYPE, I64_TYPE };
    LLVMTypeRef trunc_type[3] = { 0 };
    uint8 length[] = { 8, 4, 2 };

    int64 smin[] = { 0xff80, 0xffFF8000, 0xffFFffFF80000000 };
    int64 umin[] = { 0x0, 0x0, 0x0 };
    int64 smax[] = { 0x007f, 0x00007fff, 0x000000007fFFffFF };
    int64 umax[] = { 0x00ff, 0x0000ffff, 0x00000000ffFFffFF };

    LLVMValueRef mask_element[] = {
        LLVM_CONST(i32_zero),     LLVM_CONST(i32_one),
        LLVM_CONST(i32_two),      LLVM_CONST(i32_three),
        LLVM_CONST(i32_four),     LLVM_CONST(i32_five),
        LLVM_CONST(i32_six),      LLVM_CONST(i32_seven),
        LLVM_CONST(i32_eight),    LLVM_CONST(i32_nine),
        LLVM_CONST(i32_ten),      LLVM_CONST(i32_eleven),
        LLVM_CONST(i32_twelve),   LLVM_CONST(i32_thirteen),
        LLVM_CONST(i32_fourteen), LLVM_CONST(i32_fifteen),
    };

    if (!(trunc_type[0] = LLVMVectorType(INT8_TYPE, 8))
        || !(trunc_type[1] = LLVMVectorType(INT16_TYPE, 4))
        || !(trunc_type[2] = LLVMVectorType(I32_TYPE, 2))) {
        HANDLE_FAILURE("LLVMVectorType");
        return false;
    }

    if (!(vec2 = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                           in_vector_type[itype], "vec2"))
        || !(vec1 = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                              in_vector_type[itype], "vec1"))) {
        return false;
    }

    if (!(max = simd_build_splat_const_integer_vector(
              comp_ctx, min_max_type[itype],
              is_signed ? smax[itype] : umax[itype], length[itype]))
        || !(min = simd_build_splat_const_integer_vector(
                 comp_ctx, min_max_type[itype],
                 is_signed ? smin[itype] : umin[itype], length[itype]))) {
        return false;
    }

    /* Refer to:
     * https://github.com/WebAssembly/spec/blob/main/proposals/simd/SIMD.md#integer-to-integer-narrowing
     * Regardless of the whether the operation is signed or unsigned, the input
     * lanes are interpreted as signed integers.
     */
    if (!(vec1 = simd_saturate(comp_ctx, func_ctx, e_sat_i16x8, vec1, min, max,
                               true))
        || !(vec2 = simd_saturate(comp_ctx, func_ctx, e_sat_i16x8, vec2, min,
                                  max, true))) {
        return false;
    }

    /* trunc */
    if (!(vec1 = LLVMBuildTrunc(comp_ctx->builder, vec1, trunc_type[itype],
                                "vec1_trunc"))
        || !(vec2 = LLVMBuildTrunc(comp_ctx->builder, vec2, trunc_type[itype],
                                   "vec2_trunc"))) {
        HANDLE_FAILURE("LLVMBuildTrunc");
        return false;
    }

    /* combine */
    if (!(mask = LLVMConstVector(mask_element, (length[itype] << 1)))) {
        HANDLE_FAILURE("LLVMConstInt");
        return false;
    }

    if (!(result = LLVMBuildShuffleVector(comp_ctx->builder, vec1, vec2, mask,
                                          "vec_shuffle"))) {
        HANDLE_FAILURE("LLVMBuildShuffleVector");
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

bool
aot_compile_simd_i8x16_narrow_i16x8(AOTCompContext *comp_ctx,
                                    AOTFuncContext *func_ctx, bool is_signed)
{
    if (is_target_x86(comp_ctx)) {
        return simd_integer_narrow_x86(
            comp_ctx, func_ctx, V128_i16x8_TYPE, V128_i8x16_TYPE,
            is_signed ? "llvm.x86.sse2.packsswb.128"
                      : "llvm.x86.sse2.packuswb.128");
    }
    else {
        return simd_integer_narrow_common(comp_ctx, func_ctx, e_sat_i16x8,
                                          is_signed);
    }
}

bool
aot_compile_simd_i16x8_narrow_i32x4(AOTCompContext *comp_ctx,
                                    AOTFuncContext *func_ctx, bool is_signed)
{
    if (is_target_x86(comp_ctx)) {
        return simd_integer_narrow_x86(comp_ctx, func_ctx, V128_i32x4_TYPE,
                                       V128_i16x8_TYPE,
                                       is_signed ? "llvm.x86.sse2.packssdw.128"
                                                 : "llvm.x86.sse41.packusdw");
    }
    else {
        return simd_integer_narrow_common(comp_ctx, func_ctx, e_sat_i32x4,
                                          is_signed);
    }
}

enum integer_extend_type {
    e_ext_i8x16,
    e_ext_i16x8,
    e_ext_i32x4,
};

static LLVMValueRef
simd_integer_extension(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
                       enum integer_extend_type itype, LLVMValueRef vector,
                       bool lower_half, bool is_signed)
{
    LLVMValueRef mask, sub_vector, result;
    LLVMValueRef bits[] = {
        LLVM_CONST(i32_zero),     LLVM_CONST(i32_one),
        LLVM_CONST(i32_two),      LLVM_CONST(i32_three),
        LLVM_CONST(i32_four),     LLVM_CONST(i32_five),
        LLVM_CONST(i32_six),      LLVM_CONST(i32_seven),
        LLVM_CONST(i32_eight),    LLVM_CONST(i32_nine),
        LLVM_CONST(i32_ten),      LLVM_CONST(i32_eleven),
        LLVM_CONST(i32_twelve),   LLVM_CONST(i32_thirteen),
        LLVM_CONST(i32_fourteen), LLVM_CONST(i32_fifteen),
    };
    LLVMTypeRef out_vector_type[] = { V128_i16x8_TYPE, V128_i32x4_TYPE,
                                      V128_i64x2_TYPE };
    LLVMValueRef undef[] = { LLVM_CONST(i8x16_undef), LLVM_CONST(i16x8_undef),
                             LLVM_CONST(i32x4_undef) };
    uint32 sub_vector_length[] = { 8, 4, 2 };

    if (!(mask = lower_half ? LLVMConstVector(bits, sub_vector_length[itype])
                            : LLVMConstVector(bits + sub_vector_length[itype],
                                              sub_vector_length[itype]))) {
        HANDLE_FAILURE("LLVMConstVector");
        return false;
    }

    /* retrieve the low or high half */
    if (!(sub_vector = LLVMBuildShuffleVector(comp_ctx->builder, vector,
                                              undef[itype], mask, "half"))) {
        HANDLE_FAILURE("LLVMBuildShuffleVector");
        return false;
    }

    if (is_signed) {
        if (!(result = LLVMBuildSExt(comp_ctx->builder, sub_vector,
                                     out_vector_type[itype], "sext"))) {
            HANDLE_FAILURE("LLVMBuildSExt");
            return false;
        }
    }
    else {
        if (!(result = LLVMBuildZExt(comp_ctx->builder, sub_vector,
                                     out_vector_type[itype], "zext"))) {
            HANDLE_FAILURE("LLVMBuildZExt");
            return false;
        }
    }

    return result;
}

static bool
simd_integer_extension_wrapper(AOTCompContext *comp_ctx,
                               AOTFuncContext *func_ctx,
                               enum integer_extend_type itype, bool lower_half,
                               bool is_signed)
{
    LLVMValueRef vector, result;

    LLVMTypeRef in_vector_type[] = { V128_i8x16_TYPE, V128_i16x8_TYPE,
                                     V128_i32x4_TYPE };

    if (!(vector = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                             in_vector_type[itype], "vec"))) {
        return false;
    }

    if (!(result = simd_integer_extension(comp_ctx, func_ctx, itype, vector,
                                          lower_half, is_signed))) {
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

bool
aot_compile_simd_i16x8_extend_i8x16(AOTCompContext *comp_ctx,
                                    AOTFuncContext *func_ctx, bool lower_half,
                                    bool is_signed)
{
    return simd_integer_extension_wrapper(comp_ctx, func_ctx, e_ext_i8x16,
                                          lower_half, is_signed);
}

bool
aot_compile_simd_i32x4_extend_i16x8(AOTCompContext *comp_ctx,
                                    AOTFuncContext *func_ctx, bool lower_half,
                                    bool is_signed)
{
    return simd_integer_extension_wrapper(comp_ctx, func_ctx, e_ext_i16x8,
                                          lower_half, is_signed);
}

bool
aot_compile_simd_i64x2_extend_i32x4(AOTCompContext *comp_ctx,
                                    AOTFuncContext *func_ctx, bool lower_half,
                                    bool is_signed)
{
    return simd_integer_extension_wrapper(comp_ctx, func_ctx, e_ext_i32x4,
                                          lower_half, is_signed);
}

static LLVMValueRef
simd_trunc_sat(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
               const char *intrinsics, LLVMTypeRef in_vector_type,
               LLVMTypeRef out_vector_type)
{
    LLVMValueRef vector, result;
    LLVMTypeRef param_types[] = { in_vector_type };

    if (!(vector = simd_pop_v128_and_bitcast(comp_ctx, func_ctx, in_vector_type,
                                             "vector"))) {
        return false;
    }

    if (!(result = aot_call_llvm_intrinsic(comp_ctx, func_ctx, intrinsics,
                                           out_vector_type, param_types, 1,
                                           vector))) {
        return false;
    }

    return result;
}

bool
aot_compile_simd_i32x4_trunc_sat_f32x4(AOTCompContext *comp_ctx,
                                       AOTFuncContext *func_ctx, bool is_signed)
{
    LLVMValueRef result;
    if (!(result = simd_trunc_sat(comp_ctx, func_ctx,
                                  is_signed ? "llvm.fptosi.sat.v4i32.v4f32"
                                            : "llvm.fptoui.sat.v4i32.v4f32",
                                  V128_f32x4_TYPE, V128_i32x4_TYPE))) {
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

bool
aot_compile_simd_i32x4_trunc_sat_f64x2(AOTCompContext *comp_ctx,
                                       AOTFuncContext *func_ctx, bool is_signed)
{
    LLVMValueRef result, zero, mask;
    LLVMTypeRef out_vector_type;
    LLVMValueRef lanes[] = {
        LLVM_CONST(i32_zero),
        LLVM_CONST(i32_one),
        LLVM_CONST(i32_two),
        LLVM_CONST(i32_three),
    };

    if (!(out_vector_type = LLVMVectorType(I32_TYPE, 2))) {
        HANDLE_FAILURE("LLVMVectorType");
        return false;
    }

    if (!(result = simd_trunc_sat(comp_ctx, func_ctx,
                                  is_signed ? "llvm.fptosi.sat.v2i32.v2f64"
                                            : "llvm.fptoui.sat.v2i32.v2f64",
                                  V128_f64x2_TYPE, out_vector_type))) {
        return false;
    }

    if (!(zero = LLVMConstNull(out_vector_type))) {
        HANDLE_FAILURE("LLVMConstNull");
        return false;
    }

    /* v2i32 -> v4i32 */
    if (!(mask = LLVMConstVector(lanes, 4))) {
        HANDLE_FAILURE("LLVMConstVector");
        return false;
    }

    if (!(result = LLVMBuildShuffleVector(comp_ctx->builder, result, zero, mask,
                                          "extend"))) {
        HANDLE_FAILURE("LLVMBuildShuffleVector");
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

static LLVMValueRef
simd_integer_convert(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
                     bool is_signed, LLVMValueRef vector,
                     LLVMTypeRef out_vector_type)

{
    LLVMValueRef result;
    result = is_signed ? LLVMBuildSIToFP(comp_ctx->builder, vector,
                                         out_vector_type, "converted")
                       : LLVMBuildUIToFP(comp_ctx->builder, vector,
                                         out_vector_type, "converted");
    if (!result) {
        HANDLE_FAILURE("LLVMBuildSIToFP/LLVMBuildUIToFP");
    }

    return result;
}

bool
aot_compile_simd_f32x4_convert_i32x4(AOTCompContext *comp_ctx,
                                     AOTFuncContext *func_ctx, bool is_signed)
{
    LLVMValueRef vector, result;

    if (!(vector = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                             V128_i32x4_TYPE, "vec"))) {
        return false;
    }

    if (!(result = simd_integer_convert(comp_ctx, func_ctx, is_signed, vector,
                                        V128_f32x4_TYPE))) {
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

bool
aot_compile_simd_f64x2_convert_i32x4(AOTCompContext *comp_ctx,
                                     AOTFuncContext *func_ctx, bool is_signed)
{
    LLVMValueRef vector, mask, result;
    LLVMValueRef lanes[] = {
        LLVM_CONST(i32_zero),
        LLVM_CONST(i32_one),
    };
    LLVMTypeRef out_vector_type;

    if (!(vector = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                             V128_i32x4_TYPE, "vec"))) {
        return false;
    }

    if (!(out_vector_type = LLVMVectorType(F64_TYPE, 4))) {
        HANDLE_FAILURE("LLVMVectorType");
        return false;
    }

    if (!(result = simd_integer_convert(comp_ctx, func_ctx, is_signed, vector,
                                        out_vector_type))) {
        return false;
    }

    /* v4f64 -> v2f64 */
    if (!(mask = LLVMConstVector(lanes, 2))) {
        HANDLE_FAILURE("LLVMConstVector");
        return false;
    }

    if (!(result = LLVMBuildShuffleVector(comp_ctx->builder, result, result,
                                          mask, "trunc"))) {
        HANDLE_FAILURE("LLVMBuildShuffleVector");
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

static bool
simd_extadd_pairwise(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
                     LLVMTypeRef in_vector_type, LLVMTypeRef out_vector_type,
                     bool is_signed)
{
    LLVMValueRef vector, even_mask, odd_mask, sub_vector_even, sub_vector_odd,
        result;

    LLVMValueRef even_element[] = {
        LLVM_CONST(i32_zero),   LLVM_CONST(i32_two),      LLVM_CONST(i32_four),
        LLVM_CONST(i32_six),    LLVM_CONST(i32_eight),    LLVM_CONST(i32_ten),
        LLVM_CONST(i32_twelve), LLVM_CONST(i32_fourteen),
    };

    LLVMValueRef odd_element[] = {
        LLVM_CONST(i32_one),      LLVM_CONST(i32_three),
        LLVM_CONST(i32_five),     LLVM_CONST(i32_seven),
        LLVM_CONST(i32_nine),     LLVM_CONST(i32_eleven),
        LLVM_CONST(i32_thirteen), LLVM_CONST(i32_fifteen),
    };

    /* assumption about i16x8 from i8x16 and i32x4 from i16x8 */
    uint8 mask_length = V128_i16x8_TYPE == out_vector_type ? 8 : 4;

    if (!(vector = simd_pop_v128_and_bitcast(comp_ctx, func_ctx, in_vector_type,
                                             "vector"))) {
        return false;
    }

    if (!(even_mask = LLVMConstVector(even_element, mask_length))
        || !(odd_mask = LLVMConstVector(odd_element, mask_length))) {
        HANDLE_FAILURE("LLVMConstVector");
        return false;
    }

    /* shuffle a <16xi8> vector to two <8xi8> vectors */
    if (!(sub_vector_even = LLVMBuildShuffleVector(
              comp_ctx->builder, vector, vector, even_mask, "pick_even"))
        || !(sub_vector_odd = LLVMBuildShuffleVector(
                 comp_ctx->builder, vector, vector, odd_mask, "pick_odd"))) {
        HANDLE_FAILURE("LLVMBuildShuffleVector");
        return false;
    }

    /* sext/zext <8xi8> to <8xi16> */
    if (is_signed) {
        if (!(sub_vector_even =
                  LLVMBuildSExt(comp_ctx->builder, sub_vector_even,
                                out_vector_type, "even_sext"))
            || !(sub_vector_odd =
                     LLVMBuildSExt(comp_ctx->builder, sub_vector_odd,
                                   out_vector_type, "odd_sext"))) {
            HANDLE_FAILURE("LLVMBuildSExt");
            return false;
        }
    }
    else {
        if (!(sub_vector_even =
                  LLVMBuildZExt(comp_ctx->builder, sub_vector_even,
                                out_vector_type, "even_zext"))
            || !(sub_vector_odd =
                     LLVMBuildZExt(comp_ctx->builder, sub_vector_odd,
                                   out_vector_type, "odd_zext"))) {
            HANDLE_FAILURE("LLVMBuildZExt");
            return false;
        }
    }

    if (!(result = LLVMBuildAdd(comp_ctx->builder, sub_vector_even,
                                sub_vector_odd, "sum"))) {
        HANDLE_FAILURE("LLVMBuildAdd");
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

bool
aot_compile_simd_i16x8_extadd_pairwise_i8x16(AOTCompContext *comp_ctx,
                                             AOTFuncContext *func_ctx,
                                             bool is_signed)
{
    return simd_extadd_pairwise(comp_ctx, func_ctx, V128_i8x16_TYPE,
                                V128_i16x8_TYPE, is_signed);
}

bool
aot_compile_simd_i32x4_extadd_pairwise_i16x8(AOTCompContext *comp_ctx,
                                             AOTFuncContext *func_ctx,
                                             bool is_signed)
{
    return simd_extadd_pairwise(comp_ctx, func_ctx, V128_i16x8_TYPE,
                                V128_i32x4_TYPE, is_signed);
}

bool
aot_compile_simd_i16x8_q15mulr_sat(AOTCompContext *comp_ctx,
                                   AOTFuncContext *func_ctx)
{
    LLVMValueRef lhs, rhs, pad, offset, min, max, result;
    LLVMTypeRef vector_ext_type;

    if (!(rhs = simd_pop_v128_and_bitcast(comp_ctx, func_ctx, V128_i16x8_TYPE,
                                          "rhs"))
        || !(lhs = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                             V128_i16x8_TYPE, "lhs"))) {
        return false;
    }

    if (!(vector_ext_type = LLVMVectorType(I32_TYPE, 8))) {
        HANDLE_FAILURE("LLVMVectorType");
        return false;
    }

    if (!(lhs = LLVMBuildSExt(comp_ctx->builder, lhs, vector_ext_type,
                              "lhs_v8i32"))
        || !(rhs = LLVMBuildSExt(comp_ctx->builder, rhs, vector_ext_type,
                                 "rhs_v8i32"))) {
        HANDLE_FAILURE("LLVMBuildSExt");
        return false;
    }

    /* 0x4000 and 15*/
    if (!(pad = simd_build_splat_const_integer_vector(comp_ctx, I32_TYPE,
                                                      0x4000, 8))
        || !(offset = simd_build_splat_const_integer_vector(comp_ctx, I32_TYPE,
                                                            15, 8))) {
        return false;
    }

    /* TODO: looking for x86 intrinsics about integer"fused multiply-and-add" */
    /* S.SignedSaturate((x * y + 0x4000) >> 15) */
    if (!(result = LLVMBuildMul(comp_ctx->builder, lhs, rhs, "mul"))) {
        HANDLE_FAILURE("LLVMBuildMul");
        return false;
    }

    if (!(result = LLVMBuildAdd(comp_ctx->builder, result, pad, "add"))) {
        HANDLE_FAILURE("LLVMBuildAdd");
        return false;
    }

    if (!(result = LLVMBuildAShr(comp_ctx->builder, result, offset, "ashr"))) {
        HANDLE_FAILURE("LLVMBuildAShr");
        return false;
    }

    if (!(min = simd_build_splat_const_integer_vector(comp_ctx, I32_TYPE,
                                                      0xffff8000, 8))
        || !(max = simd_build_splat_const_integer_vector(comp_ctx, I32_TYPE,
                                                         0x00007fff, 8))) {
        return false;
    }

    /* sat after trunc will let *sat* part be optimized */
    if (!(result = simd_saturate(comp_ctx, func_ctx, e_sat_i32x8, result, min,
                                 max, true))) {
        return false;
    }

    if (!(result = LLVMBuildTrunc(comp_ctx->builder, result, V128_i16x8_TYPE,
                                  "down_to_v8i16"))) {
        HANDLE_FAILURE("LLVMBuildTrunc");
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

enum integer_extmul_type {
    e_i16x8_extmul_i8x16,
    e_i32x4_extmul_i16x8,
    e_i64x2_extmul_i32x4,
};

static bool
simd_integer_extmul(AOTCompContext *comp_ctx, AOTFuncContext *func_ctx,
                    bool lower_half, bool is_signed,
                    enum integer_extmul_type itype)
{
    LLVMValueRef vec1, vec2, result;
    enum integer_extend_type ext_type[] = {
        e_ext_i8x16,
        e_ext_i16x8,
        e_ext_i32x4,
    };
    LLVMTypeRef in_vector_type[] = {
        V128_i8x16_TYPE,
        V128_i16x8_TYPE,
        V128_i32x4_TYPE,
    };

    if (!(vec1 = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                           in_vector_type[itype], "vec1"))
        || !(vec2 = simd_pop_v128_and_bitcast(comp_ctx, func_ctx,
                                              in_vector_type[itype], "vec2"))) {
        return false;
    }

    if (!(vec1 = simd_integer_extension(comp_ctx, func_ctx, ext_type[itype],
                                        vec1, lower_half, is_signed))
        || !(vec2 = simd_integer_extension(comp_ctx, func_ctx, ext_type[itype],
                                           vec2, lower_half, is_signed))) {
        return false;
    }

    if (!(result = LLVMBuildMul(comp_ctx->builder, vec1, vec2, "product"))) {
        return false;
    }

    return simd_bitcast_and_push_v128(comp_ctx, func_ctx, result, "result");
}

bool
aot_compile_simd_i16x8_extmul_i8x16(AOTCompContext *comp_ctx,
                                    AOTFuncContext *func_ctx, bool lower_half,
                                    bool is_signed)
{
    return simd_integer_extmul(comp_ctx, func_ctx, lower_half, is_signed,
                               e_i16x8_extmul_i8x16);
}

bool
aot_compile_simd_i32x4_extmul_i16x8(AOTCompContext *comp_ctx,
                                    AOTFuncContext *func_ctx, bool lower_half,
                                    bool is_signed)
{
    return simd_integer_extmul(comp_ctx, func_ctx, lower_half, is_signed,
                               e_i32x4_extmul_i16x8);
}

bool
aot_compile_simd_i64x2_extmul_i32x4(AOTCompContext *comp_ctx,
                                    AOTFuncContext *func_ctx, bool lower_half,
                                    bool is_signed)
{
    return simd_integer_extmul(comp_ctx, func_ctx, lower_half, is_signed,
                               e_i64x2_extmul_i32x4);
}
