diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c
index 808caf4289..b57f255b24 100644
--- a/target/hexagon/genptr.c
+++ b/target/hexagon/genptr.c
@@ -36,7 +36,7 @@
#ifndef CONFIG_USER_ONLY
#include "gen_tcg_sys.h"
#endif
-
+#include "hlx_gvec_tables.h"
#include "genptr.h"
static void gen_sabsdiff_i32(TCGv_i32 d, TCGv_i32 a, TCGv_i32 b)
diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h
index 766995fae3..471ef0903b 100644
--- a/target/hexagon/helper.h
+++ b/target/hexagon/helper.h
@@ -37,6 +37,13 @@ DEF_HELPER_5(hlx_store_vec, void, env, tl, ptr, i32, i32)
DEF_HELPER_1(hlx_commit_store, void, env)
DEF_HELPER_FLAGS_2(hlx_trace_op, TCG_CALL_NO_RWG, void, env, i32)
+/* GVec helpers for signed-to-unsigned MO_16 saturation. */
+DEF_HELPER_FLAGS_4(hlx_gvec_ushsat_add16, TCG_CALL_NO_RWG,
+ void, ptr, ptr, ptr, i32)
+DEF_HELPER_FLAGS_4(hlx_gvec_ushsat_sub16, TCG_CALL_NO_RWG,
+ void, ptr, ptr, ptr, i32)
+DEF_HELPER_FLAGS_3(hlx_gvec_abs_uh_sat16, TCG_CALL_NO_RWG, void, ptr, ptr, i32)
+
/* Floating point */
DEF_HELPER_3(conv_sf2df, f64, env, f32, i32)
DEF_HELPER_3(conv_df2sf, f32, env, f64, i32)
diff --git a/target/hexagon/hex_common.py b/target/hexagon/hex_common.py
index a6ac84d792..3f4d796a98 100755
--- a/target/hexagon/hex_common.py
+++ b/target/hexagon/hex_common.py
@@ -378,6 +378,16 @@ def skip_qemu_helper(tag):
"vece": "MO_16"},
"LX_abs_uhp": {"op": "gvec2_fn", "fn": "abs",
"vece": "MO_16", "size": "2 * sizeof(HLXVector)"},
+ "LX_add_uhh_sat": {"op": "gvec3", "table": "hlx_ushsat_add_op",
+ "vece": "MO_16"},
+ "LX_sub_uhh_sat": {"op": "gvec3", "table": "hlx_ushsat_sub_op",
+ "vece": "MO_16"},
+ "LX_add_uhp_h_sat": {"op": "gvec3", "table": "hlx_ushsat_add_op",
+ "vece": "MO_16", "size": "2 * sizeof(HLXVector)"},
+ "LX_sub_uhp_h_sat": {"op": "gvec3", "table": "hlx_ushsat_sub_op",
+ "vece": "MO_16", "size": "2 * sizeof(HLXVector)"},
+ "LX_abs_uh_sat": {"op": "gvec2", "table": "hlx_abs_uh_sat_op",
+ "vece": "MO_16"},
}
def is_hlx_inline_tag(tag):
diff --git a/target/hexagon/hlx_gvec_tables.c b/target/hexagon/hlx_gvec_tables.c
new file mode 100644
index 0000000000..dd5aa788e0
--- /dev/null
+++ b/target/hexagon/hlx_gvec_tables.c
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "tcg/tcg.h"
+#include "tcg/tcg-op-common.h"
+#include "tcg/tcg-op-gvec-common.h"
+#include "hlx_gvec_tables.h"
+#include "exec/helper-gen.h"
+
+/* Saturate signed halfword results to uint16_t. */
+
+static const TCGOpcode hlx_vecop_ushsat_add[] = {
+ INDEX_op_add_vec,
+ INDEX_op_ssadd_vec,
+ INDEX_op_smax_vec,
+ INDEX_op_cmp_vec,
+ 0
+};
+
+static const TCGOpcode hlx_vecop_ushsat_sub[] = {
+ INDEX_op_sub_vec,
+ INDEX_op_sssub_vec,
+ INDEX_op_smax_vec,
+ INDEX_op_cmp_vec,
+ 0
+};
+
+static const TCGOpcode hlx_vecop_abs_uh_sat[] = {
+ INDEX_op_abs_vec,
+ INDEX_op_umin_vec,
+ 0
+};
+
+static void hlx_fniv_ushsat_add(unsigned vece, TCGv_vec r,
+ TCGv_vec a, TCGv_vec b)
+{
+ TCGv_vec wrap = tcg_temp_new_vec_matching(r);
+ TCGv_vec ssat = tcg_temp_new_vec_matching(r);
+ TCGv_vec pos_ovf = tcg_temp_new_vec_matching(r);
+ TCGv_vec pa = tcg_temp_new_vec_matching(r);
+ TCGv_vec pb = tcg_temp_new_vec_matching(r);
+ TCGv_vec zero = tcg_constant_vec_matching(r, vece, 0);
+
+ tcg_gen_add_vec(vece, wrap, a, b);
+
+ tcg_gen_ssadd_vec(vece, ssat, a, b);
+ tcg_gen_smax_vec(vece, ssat, ssat, zero);
+
+ tcg_gen_cmp_vec(TCG_COND_GE, vece, pa, a, zero);
+ tcg_gen_cmp_vec(TCG_COND_GE, vece, pb, b, zero);
+ tcg_gen_cmp_vec(TCG_COND_LT, vece, pos_ovf, wrap, zero);
+ tcg_gen_and_vec(vece, pos_ovf, pos_ovf, pa);
+ tcg_gen_and_vec(vece, pos_ovf, pos_ovf, pb);
+
+ tcg_gen_bitsel_vec(vece, r, pos_ovf, wrap, ssat);
+}
+
+static void hlx_fniv_ushsat_sub(unsigned vece, TCGv_vec r,
+ TCGv_vec a, TCGv_vec b)
+{
+ TCGv_vec wrap = tcg_temp_new_vec_matching(r);
+ TCGv_vec ssat = tcg_temp_new_vec_matching(r);
+ TCGv_vec pos_ovf = tcg_temp_new_vec_matching(r);
+ TCGv_vec pa = tcg_temp_new_vec_matching(r);
+ TCGv_vec nb = tcg_temp_new_vec_matching(r);
+ TCGv_vec zero = tcg_constant_vec_matching(r, vece, 0);
+
+ tcg_gen_sub_vec(vece, wrap, a, b);
+
+ tcg_gen_sssub_vec(vece, ssat, a, b);
+ tcg_gen_smax_vec(vece, ssat, ssat, zero);
+
+ /* Positive subtraction overflow needs a >= 0 and b < 0. */
+ tcg_gen_cmp_vec(TCG_COND_GE, vece, pa, a, zero);
+ tcg_gen_cmp_vec(TCG_COND_LT, vece, nb, b, zero);
+ tcg_gen_cmp_vec(TCG_COND_LT, vece, pos_ovf, wrap, zero);
+ tcg_gen_and_vec(vece, pos_ovf, pos_ovf, pa);
+ tcg_gen_and_vec(vece, pos_ovf, pos_ovf, nb);
+
+ tcg_gen_bitsel_vec(vece, r, pos_ovf, wrap, ssat);
+}
+
+static void hlx_fniv_abs_uh_sat(unsigned vece, TCGv_vec r, TCGv_vec a)
+{
+ /* Clamp abs(INT16_MIN) to INT16_MAX. */
+ TCGv_vec imax = tcg_constant_vec_matching(r, vece, 0x7FFF);
+ tcg_gen_abs_vec(vece, r, a);
+ tcg_gen_umin_vec(vece, r, r, imax);
+}
+
+const GVecGen3 hlx_ushsat_add_op = {
+ .fniv = hlx_fniv_ushsat_add,
+ .opt_opc = hlx_vecop_ushsat_add,
+ .fno = gen_helper_hlx_gvec_ushsat_add16,
+ .vece = MO_16,
+};
+
+const GVecGen3 hlx_ushsat_sub_op = {
+ .fniv = hlx_fniv_ushsat_sub,
+ .opt_opc = hlx_vecop_ushsat_sub,
+ .fno = gen_helper_hlx_gvec_ushsat_sub16,
+ .vece = MO_16,
+};
+
+const GVecGen2 hlx_abs_uh_sat_op = {
+ .fniv = hlx_fniv_abs_uh_sat,
+ .opt_opc = hlx_vecop_abs_uh_sat,
+ .fno = gen_helper_hlx_gvec_abs_uh_sat16,
+ .vece = MO_16,
+};
diff --git a/target/hexagon/hlx_gvec_tables.h b/target/hexagon/hlx_gvec_tables.h
new file mode 100644
index 0000000000..55ab6bb63f
--- /dev/null
+++ b/target/hexagon/hlx_gvec_tables.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HLX_GVEC_TABLES_H
+#define HLX_GVEC_TABLES_H
+
+#include "tcg/tcg-op-gvec-common.h"
+
+extern const GVecGen3 hlx_ushsat_add_op;
+extern const GVecGen3 hlx_ushsat_sub_op;
+extern const GVecGen2 hlx_abs_uh_sat_op;
+
+#endif /* HLX_GVEC_TABLES_H */
diff --git a/target/hexagon/hlx_helper.c b/target/hexagon/hlx_helper.c
index 6525abfb2c..906f31ba17 100644
--- a/target/hexagon/hlx_helper.c
+++ b/target/hexagon/hlx_helper.c
@@ -93,3 +93,53 @@ void HELPER(hlx_commit_reg)(CPUHexagonState *env, uint32_t
rnum)
memcpy(&env->hlx_ctx->LRegs[rnum], &env->future_LRegs[rnum],
sizeof(HLXVector));
}
+
+/* HLX vectors exceed the gvec inline limit. */
+void HELPER(hlx_gvec_ushsat_add16)(void *d, void *a, void *b, uint32_t desc)
+{
+ intptr_t oprsz = simd_oprsz(desc);
+ intptr_t i;
+
+ for (i = 0; i < oprsz; i += sizeof(int16_t)) {
+ int32_t r = (int32_t)(*(int16_t *)((char *)a + i))
+ + (int32_t)(*(int16_t *)((char *)b + i));
+ if (r < 0) {
+ r = 0;
+ } else if (r > 0xFFFF) {
+ r = 0xFFFF;
+ }
+ *(uint16_t *)((char *)d + i) = (uint16_t)r;
+ }
+}
+
+void HELPER(hlx_gvec_ushsat_sub16)(void *d, void *a, void *b, uint32_t desc)
+{
+ intptr_t oprsz = simd_oprsz(desc);
+ intptr_t i;
+
+ for (i = 0; i < oprsz; i += sizeof(int16_t)) {
+ int32_t r = (int32_t)(*(int16_t *)((char *)a + i))
+ - (int32_t)(*(int16_t *)((char *)b + i));
+ if (r < 0) {
+ r = 0;
+ } else if (r > 0xFFFF) {
+ r = 0xFFFF;
+ }
+ *(uint16_t *)((char *)d + i) = (uint16_t)r;
+ }
+}
+
+void HELPER(hlx_gvec_abs_uh_sat16)(void *d, void *a, uint32_t desc)
+{
+ intptr_t oprsz = simd_oprsz(desc);
+ intptr_t i;
+
+ for (i = 0; i < oprsz; i += sizeof(int16_t)) {
+ int16_t v = *(int16_t *)((char *)a + i);
+ int32_t r = (v < 0) ? -(int32_t)v : (int32_t)v;
+ if (r > INT16_MAX) {
+ r = INT16_MAX;
+ }
+ *(int16_t *)((char *)d + i) = (int16_t)r;
+ }
+}
diff --git a/target/hexagon/meson.build b/target/hexagon/meson.build
index 1aa4df7ac7..fd4983343d 100644
--- a/target/hexagon/meson.build
+++ b/target/hexagon/meson.build
@@ -266,6 +266,7 @@ hexagon_ss.add(files(
'mmvec/hvx_ieee_fp.c',
'mmvec/hvx_qfloat.c',
'hlx_helper.c',
+ 'hlx_gvec_tables.c',
))
hexagon_softmmu_ss.add(files(