/* ACLE support for Arm MVE (function_base classes) Copyright (C) 2023 Free Software Foundation, Inc. This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ #ifndef GCC_ARM_MVE_BUILTINS_FUNCTIONS_H #define GCC_ARM_MVE_BUILTINS_FUNCTIONS_H namespace arm_mve { /* Wrap T, which is derived from function_base, and indicate that the function never has side effects. It is only necessary to use this wrapper on functions that might have floating-point suffixes, since otherwise we assume by default that the function has no side effects. */ template class quiet : public T { public: CONSTEXPR quiet () : T () {} unsigned int call_properties (const function_instance &) const override { return 0; } }; /* An incomplete function_base for functions that have an associated rtx_code for signed integers, unsigned integers and floating-point values for the non-predicated, non-suffixed intrinsic, and unspec codes, with separate codes for signed integers, unsigned integers and floating-point values. The class simply records information about the mapping for derived classes to use. */ class unspec_based_mve_function_base : public function_base { public: CONSTEXPR unspec_based_mve_function_base (rtx_code code_for_sint, rtx_code code_for_uint, rtx_code code_for_fp, int unspec_for_n_sint, int unspec_for_n_uint, int unspec_for_n_fp, int unspec_for_m_sint, int unspec_for_m_uint, int unspec_for_m_fp, int unspec_for_m_n_sint, int unspec_for_m_n_uint, int unspec_for_m_n_fp) : m_code_for_sint (code_for_sint), m_code_for_uint (code_for_uint), m_code_for_fp (code_for_fp), m_unspec_for_n_sint (unspec_for_n_sint), m_unspec_for_n_uint (unspec_for_n_uint), m_unspec_for_n_fp (unspec_for_n_fp), m_unspec_for_m_sint (unspec_for_m_sint), m_unspec_for_m_uint (unspec_for_m_uint), m_unspec_for_m_fp (unspec_for_m_fp), m_unspec_for_m_n_sint (unspec_for_m_n_sint), m_unspec_for_m_n_uint (unspec_for_m_n_uint), m_unspec_for_m_n_fp (unspec_for_m_n_fp) {} /* The rtx code to use for signed, unsigned integers and floating-point values respectively. */ rtx_code m_code_for_sint; rtx_code m_code_for_uint; rtx_code m_code_for_fp; /* The unspec code associated with signed-integer, unsigned-integer and floating-point operations respectively. It covers the cases with the _n suffix, and/or the _m predicate. */ int m_unspec_for_n_sint; int m_unspec_for_n_uint; int m_unspec_for_n_fp; int m_unspec_for_m_sint; int m_unspec_for_m_uint; int m_unspec_for_m_fp; int m_unspec_for_m_n_sint; int m_unspec_for_m_n_uint; int m_unspec_for_m_n_fp; }; /* Map the function directly to CODE (UNSPEC, M) where M is the vector mode associated with type suffix 0, except when there is no predicate and no _n suffix, in which case we use the appropriate rtx_code. This is useful when the basic operation is mapped to a standard RTX code and all other versions use different unspecs. */ class unspec_based_mve_function_exact_insn : public unspec_based_mve_function_base { public: CONSTEXPR unspec_based_mve_function_exact_insn (rtx_code code_for_sint, rtx_code code_for_uint, rtx_code code_for_fp, int unspec_for_n_sint, int unspec_for_n_uint, int unspec_for_n_fp, int unspec_for_m_sint, int unspec_for_m_uint, int unspec_for_m_fp, int unspec_for_m_n_sint, int unspec_for_m_n_uint, int unspec_for_m_n_fp) : unspec_based_mve_function_base (code_for_sint, code_for_uint, code_for_fp, unspec_for_n_sint, unspec_for_n_uint, unspec_for_n_fp, unspec_for_m_sint, unspec_for_m_uint, unspec_for_m_fp, unspec_for_m_n_sint, unspec_for_m_n_uint, unspec_for_m_n_fp) {} rtx expand (function_expander &e) const override { /* No suffix, no predicate, use the right RTX code. */ if ((e.mode_suffix_id != MODE_n) && (e.pred == PRED_none)) return e.map_to_rtx_codes (m_code_for_sint, m_code_for_uint, m_code_for_fp); insn_code code; switch (e.pred) { case PRED_none: if (e.mode_suffix_id == MODE_n) /* No predicate, _n suffix. */ { if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_n (m_unspec_for_n_uint, m_unspec_for_n_uint, e.vector_mode (0)); else code = code_for_mve_q_n (m_unspec_for_n_sint, m_unspec_for_n_sint, e.vector_mode (0)); else code = code_for_mve_q_n_f (m_unspec_for_n_fp, e.vector_mode (0)); return e.use_exact_insn (code); } gcc_unreachable (); break; case PRED_m: switch (e.mode_suffix_id) { case MODE_none: /* No suffix, "m" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m (m_unspec_for_m_uint, m_unspec_for_m_uint, e.vector_mode (0)); else code = code_for_mve_q_m (m_unspec_for_m_sint, m_unspec_for_m_sint, e.vector_mode (0)); else code = code_for_mve_q_m_f (m_unspec_for_m_fp, e.vector_mode (0)); break; case MODE_n: /* _n suffix, "m" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m_n (m_unspec_for_m_n_uint, m_unspec_for_m_n_uint, e.vector_mode (0)); else code = code_for_mve_q_m_n (m_unspec_for_m_n_sint, m_unspec_for_m_n_sint, e.vector_mode (0)); else code = code_for_mve_q_m_n_f (m_unspec_for_m_n_fp, e.vector_mode (0)); break; default: gcc_unreachable (); } return e.use_cond_insn (code, 0); case PRED_x: switch (e.mode_suffix_id) { case MODE_none: /* No suffix, "x" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m (m_unspec_for_m_uint, m_unspec_for_m_uint, e.vector_mode (0)); else code = code_for_mve_q_m (m_unspec_for_m_sint, m_unspec_for_m_sint, e.vector_mode (0)); else code = code_for_mve_q_m_f (m_unspec_for_m_fp, e.vector_mode (0)); break; case MODE_n: /* _n suffix, "x" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m_n (m_unspec_for_m_n_uint, m_unspec_for_m_n_uint, e.vector_mode (0)); else code = code_for_mve_q_m_n (m_unspec_for_m_n_sint, m_unspec_for_m_n_sint, e.vector_mode (0)); else code = code_for_mve_q_m_n_f (m_unspec_for_m_n_fp, e.vector_mode (0)); break; default: gcc_unreachable (); } return e.use_pred_x_insn (code); default: gcc_unreachable (); } gcc_unreachable (); } }; /* Map the function directly to CODE (UNSPEC, M) where M is the vector mode associated with type suffix 0. */ class unspec_mve_function_exact_insn : public function_base { public: CONSTEXPR unspec_mve_function_exact_insn (int unspec_for_sint, int unspec_for_uint, int unspec_for_fp, int unspec_for_n_sint, int unspec_for_n_uint, int unspec_for_n_fp, int unspec_for_m_sint, int unspec_for_m_uint, int unspec_for_m_fp, int unspec_for_m_n_sint, int unspec_for_m_n_uint, int unspec_for_m_n_fp) : m_unspec_for_sint (unspec_for_sint), m_unspec_for_uint (unspec_for_uint), m_unspec_for_fp (unspec_for_fp), m_unspec_for_n_sint (unspec_for_n_sint), m_unspec_for_n_uint (unspec_for_n_uint), m_unspec_for_n_fp (unspec_for_n_fp), m_unspec_for_m_sint (unspec_for_m_sint), m_unspec_for_m_uint (unspec_for_m_uint), m_unspec_for_m_fp (unspec_for_m_fp), m_unspec_for_m_n_sint (unspec_for_m_n_sint), m_unspec_for_m_n_uint (unspec_for_m_n_uint), m_unspec_for_m_n_fp (unspec_for_m_n_fp) {} /* The unspec code associated with signed-integer, unsigned-integer and floating-point operations respectively. It covers the cases with the _n suffix, and/or the _m predicate. */ int m_unspec_for_sint; int m_unspec_for_uint; int m_unspec_for_fp; int m_unspec_for_n_sint; int m_unspec_for_n_uint; int m_unspec_for_n_fp; int m_unspec_for_m_sint; int m_unspec_for_m_uint; int m_unspec_for_m_fp; int m_unspec_for_m_n_sint; int m_unspec_for_m_n_uint; int m_unspec_for_m_n_fp; rtx expand (function_expander &e) const override { insn_code code; switch (e.pred) { case PRED_none: switch (e.mode_suffix_id) { case MODE_none: /* No predicate, no suffix. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q (m_unspec_for_uint, m_unspec_for_uint, e.vector_mode (0)); else code = code_for_mve_q (m_unspec_for_sint, m_unspec_for_sint, e.vector_mode (0)); else code = code_for_mve_q_f (m_unspec_for_fp, e.vector_mode (0)); break; case MODE_n: /* No predicate, _n suffix. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_n (m_unspec_for_n_uint, m_unspec_for_n_uint, e.vector_mode (0)); else code = code_for_mve_q_n (m_unspec_for_n_sint, m_unspec_for_n_sint, e.vector_mode (0)); else code = code_for_mve_q_n_f (m_unspec_for_n_fp, e.vector_mode (0)); break; default: gcc_unreachable (); } return e.use_exact_insn (code); case PRED_m: switch (e.mode_suffix_id) { case MODE_none: /* No suffix, "m" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m (m_unspec_for_m_uint, m_unspec_for_m_uint, e.vector_mode (0)); else code = code_for_mve_q_m (m_unspec_for_m_sint, m_unspec_for_m_sint, e.vector_mode (0)); else code = code_for_mve_q_m_f (m_unspec_for_m_fp, e.vector_mode (0)); break; case MODE_n: /* _n suffix, "m" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m_n (m_unspec_for_m_n_uint, m_unspec_for_m_n_uint, e.vector_mode (0)); else code = code_for_mve_q_m_n (m_unspec_for_m_n_sint, m_unspec_for_m_n_sint, e.vector_mode (0)); else code = code_for_mve_q_m_n_f (m_unspec_for_m_n_fp, e.vector_mode (0)); break; default: gcc_unreachable (); } return e.use_cond_insn (code, 0); case PRED_x: switch (e.mode_suffix_id) { case MODE_none: /* No suffix, "x" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m (m_unspec_for_m_uint, m_unspec_for_m_uint, e.vector_mode (0)); else code = code_for_mve_q_m (m_unspec_for_m_sint, m_unspec_for_m_sint, e.vector_mode (0)); else code = code_for_mve_q_m_f (m_unspec_for_m_fp, e.vector_mode (0)); break; case MODE_n: /* _n suffix, "x" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m_n (m_unspec_for_m_n_uint, m_unspec_for_m_n_uint, e.vector_mode (0)); else code = code_for_mve_q_m_n (m_unspec_for_m_n_sint, m_unspec_for_m_n_sint, e.vector_mode (0)); else code = code_for_mve_q_m_n_f (m_unspec_for_m_n_fp, e.vector_mode (0)); break; default: gcc_unreachable (); } return e.use_pred_x_insn (code); default: gcc_unreachable (); } gcc_unreachable (); } }; /* Map the function directly to CODE (UNSPEC), when there is a non-predicated version and one with the "_p" predicate. */ class unspec_mve_function_exact_insn_pred_p : public function_base { public: CONSTEXPR unspec_mve_function_exact_insn_pred_p (int unspec_for_sint, int unspec_for_uint, int unspec_for_fp, int unspec_for_p_sint, int unspec_for_p_uint, int unspec_for_p_fp) : m_unspec_for_sint (unspec_for_sint), m_unspec_for_uint (unspec_for_uint), m_unspec_for_fp (unspec_for_fp), m_unspec_for_p_sint (unspec_for_p_sint), m_unspec_for_p_uint (unspec_for_p_uint), m_unspec_for_p_fp (unspec_for_p_fp) {} /* The unspec code associated with signed-integer and unsigned-integer operations, with no predicate, or with "_p" predicate. */ int m_unspec_for_sint; int m_unspec_for_uint; int m_unspec_for_fp; int m_unspec_for_p_sint; int m_unspec_for_p_uint; int m_unspec_for_p_fp; rtx expand (function_expander &e) const override { insn_code code; if (m_unspec_for_sint == VADDLVQ_S || m_unspec_for_sint == VADDLVAQ_S || m_unspec_for_sint == VRMLALDAVHAQ_S || m_unspec_for_sint == VRMLALDAVHAXQ_S || m_unspec_for_sint == VRMLALDAVHQ_S || m_unspec_for_sint == VRMLALDAVHXQ_S || m_unspec_for_sint == VRMLSLDAVHAQ_S || m_unspec_for_sint == VRMLSLDAVHAXQ_S || m_unspec_for_sint == VRMLSLDAVHQ_S || m_unspec_for_sint == VRMLSLDAVHXQ_S) { switch (e.pred) { case PRED_none: if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_v4si (m_unspec_for_uint, m_unspec_for_uint); else code = code_for_mve_q_v4si (m_unspec_for_sint, m_unspec_for_sint); return e.use_exact_insn (code); case PRED_p: if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_p_v4si (m_unspec_for_p_uint, m_unspec_for_p_uint); else code = code_for_mve_q_p_v4si (m_unspec_for_p_sint, m_unspec_for_p_sint); return e.use_exact_insn (code); default: gcc_unreachable (); } } else { switch (e.pred) { case PRED_none: if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q (m_unspec_for_uint, m_unspec_for_uint, e.vector_mode (0)); else code = code_for_mve_q (m_unspec_for_sint, m_unspec_for_sint, e.vector_mode (0)); else code = code_for_mve_q_f (m_unspec_for_fp, e.vector_mode (0)); return e.use_exact_insn (code); case PRED_p: if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_p (m_unspec_for_p_uint, m_unspec_for_p_uint, e.vector_mode (0)); else code = code_for_mve_q_p (m_unspec_for_p_sint, m_unspec_for_p_sint, e.vector_mode (0)); else code = code_for_mve_q_p_f (m_unspec_for_p_fp, e.vector_mode (0)); return e.use_exact_insn (code); default: gcc_unreachable (); } } gcc_unreachable (); } }; /* Map the function directly to CODE (UNSPEC, M) for vshl-like builtins. The difference with unspec_mve_function_exact_insn is that this function handles MODE_r and the related unspecs.. */ class unspec_mve_function_exact_insn_vshl : public function_base { public: CONSTEXPR unspec_mve_function_exact_insn_vshl (int unspec_for_sint, int unspec_for_uint, int unspec_for_n_sint, int unspec_for_n_uint, int unspec_for_m_sint, int unspec_for_m_uint, int unspec_for_m_n_sint, int unspec_for_m_n_uint, int unspec_for_m_r_sint, int unspec_for_m_r_uint, int unspec_for_r_sint, int unspec_for_r_uint) : m_unspec_for_sint (unspec_for_sint), m_unspec_for_uint (unspec_for_uint), m_unspec_for_n_sint (unspec_for_n_sint), m_unspec_for_n_uint (unspec_for_n_uint), m_unspec_for_m_sint (unspec_for_m_sint), m_unspec_for_m_uint (unspec_for_m_uint), m_unspec_for_m_n_sint (unspec_for_m_n_sint), m_unspec_for_m_n_uint (unspec_for_m_n_uint), m_unspec_for_m_r_sint (unspec_for_m_r_sint), m_unspec_for_m_r_uint (unspec_for_m_r_uint), m_unspec_for_r_sint (unspec_for_r_sint), m_unspec_for_r_uint (unspec_for_r_uint) {} /* The unspec code associated with signed-integer, unsigned-integer and floating-point operations respectively. It covers the cases with the _n suffix, and/or the _m predicate. */ int m_unspec_for_sint; int m_unspec_for_uint; int m_unspec_for_n_sint; int m_unspec_for_n_uint; int m_unspec_for_m_sint; int m_unspec_for_m_uint; int m_unspec_for_m_n_sint; int m_unspec_for_m_n_uint; int m_unspec_for_m_r_sint; int m_unspec_for_m_r_uint; int m_unspec_for_r_sint; int m_unspec_for_r_uint; rtx expand (function_expander &e) const override { insn_code code; switch (e.pred) { case PRED_none: switch (e.mode_suffix_id) { case MODE_none: /* No predicate, no suffix. */ if (e.type_suffix (0).unsigned_p) code = code_for_mve_q (m_unspec_for_uint, m_unspec_for_uint, e.vector_mode (0)); else code = code_for_mve_q (m_unspec_for_sint, m_unspec_for_sint, e.vector_mode (0)); break; case MODE_n: /* No predicate, _n suffix. */ if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_n (m_unspec_for_n_uint, m_unspec_for_n_uint, e.vector_mode (0)); else code = code_for_mve_q_n (m_unspec_for_n_sint, m_unspec_for_n_sint, e.vector_mode (0)); break; case MODE_r: /* No predicate, _r suffix. */ if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_r (m_unspec_for_r_uint, m_unspec_for_r_uint, e.vector_mode (0)); else code = code_for_mve_q_r (m_unspec_for_r_sint, m_unspec_for_r_sint, e.vector_mode (0)); break; default: gcc_unreachable (); } return e.use_exact_insn (code); case PRED_m: switch (e.mode_suffix_id) { case MODE_none: /* No suffix, "m" predicate. */ if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m (m_unspec_for_m_uint, m_unspec_for_m_uint, e.vector_mode (0)); else code = code_for_mve_q_m (m_unspec_for_m_sint, m_unspec_for_m_sint, e.vector_mode (0)); break; case MODE_n: /* _n suffix, "m" predicate. */ if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m_n (m_unspec_for_m_n_uint, m_unspec_for_m_n_uint, e.vector_mode (0)); else code = code_for_mve_q_m_n (m_unspec_for_m_n_sint, m_unspec_for_m_n_sint, e.vector_mode (0)); break; case MODE_r: /* _r suffix, "m" predicate. */ if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m_r (m_unspec_for_m_r_uint, m_unspec_for_m_r_uint, e.vector_mode (0)); else code = code_for_mve_q_m_r (m_unspec_for_m_r_sint, m_unspec_for_m_r_sint, e.vector_mode (0)); break; default: gcc_unreachable (); } return e.use_cond_insn (code, 0); case PRED_x: switch (e.mode_suffix_id) { case MODE_none: /* No suffix, "x" predicate. */ if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m (m_unspec_for_m_uint, m_unspec_for_m_uint, e.vector_mode (0)); else code = code_for_mve_q_m (m_unspec_for_m_sint, m_unspec_for_m_sint, e.vector_mode (0)); break; case MODE_n: /* _n suffix, "x" predicate. */ if (e.type_suffix (0).unsigned_p) code = code_for_mve_q_m_n (m_unspec_for_m_n_uint, m_unspec_for_m_n_uint, e.vector_mode (0)); else code = code_for_mve_q_m_n (m_unspec_for_m_n_sint, m_unspec_for_m_n_sint, e.vector_mode (0)); break; default: gcc_unreachable (); } return e.use_pred_x_insn (code); default: gcc_unreachable (); } gcc_unreachable (); } }; /* Map the comparison functions. */ class unspec_based_mve_function_exact_insn_vcmp : public unspec_based_mve_function_base { public: CONSTEXPR unspec_based_mve_function_exact_insn_vcmp (rtx_code code_for_sint, rtx_code code_for_uint, rtx_code code_for_fp, int unspec_for_m_sint, int unspec_for_m_uint, int unspec_for_m_fp, int unspec_for_m_n_sint, int unspec_for_m_n_uint, int unspec_for_m_n_fp) : unspec_based_mve_function_base (code_for_sint, code_for_uint, code_for_fp, -1, -1, -1, unspec_for_m_sint, unspec_for_m_uint, unspec_for_m_fp, unspec_for_m_n_sint, unspec_for_m_n_uint, unspec_for_m_n_fp) {} rtx expand (function_expander &e) const override { machine_mode mode = e.vector_mode (0); insn_code code; rtx target; /* No suffix, no predicate, use the right RTX code. */ if (e.pred == PRED_none) { switch (e.mode_suffix_id) { case MODE_none: if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_vcmpq (m_code_for_uint, mode); else code = code_for_mve_vcmpq (m_code_for_sint, mode); else code = code_for_mve_vcmpq_f (m_code_for_fp, mode); break; case MODE_n: if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_vcmpq_n (m_code_for_uint, mode); else code = code_for_mve_vcmpq_n (m_code_for_sint, mode); else code = code_for_mve_vcmpq_n_f (m_code_for_fp, mode); break; default: gcc_unreachable (); } target = e.use_exact_insn (code); } else { switch (e.pred) { case PRED_m: switch (e.mode_suffix_id) { case MODE_none: /* No suffix, "m" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_vcmpq_m (m_unspec_for_m_uint, m_unspec_for_m_uint, mode); else code = code_for_mve_vcmpq_m (m_unspec_for_m_sint, m_unspec_for_m_sint, mode); else code = code_for_mve_vcmpq_m_f (m_unspec_for_m_fp, mode); break; case MODE_n: /* _n suffix, "m" predicate. */ if (e.type_suffix (0).integer_p) if (e.type_suffix (0).unsigned_p) code = code_for_mve_vcmpq_m_n (m_unspec_for_m_n_uint, m_unspec_for_m_n_uint, mode); else code = code_for_mve_vcmpq_m_n (m_unspec_for_m_n_sint, m_unspec_for_m_n_sint, mode); else code = code_for_mve_vcmpq_m_n_f (m_unspec_for_m_n_fp, mode); break; default: gcc_unreachable (); } target = e.use_cond_insn (code, 0); break; default: gcc_unreachable (); } } rtx HItarget = gen_reg_rtx (HImode); emit_move_insn (HItarget, gen_lowpart (HImode, target)); return HItarget; } }; } /* end namespace arm_mve */ /* Declare the global function base NAME, creating it from an instance of class CLASS with constructor arguments ARGS. */ #define FUNCTION(NAME, CLASS, ARGS) \ namespace { static CONSTEXPR const CLASS NAME##_obj ARGS; } \ namespace functions { const function_base *const NAME = &NAME##_obj; } #endif