/* Internal functions.
- Copyright (C) 2011-2020 Free Software Foundation, Inc.
+ Copyright (C) 2011-2021 Free Software Foundation, Inc.
This file is part of GCC.
#include "tree-phinodes.h"
#include "ssa-iterators.h"
#include "explow.h"
+#include "rtl-iter.h"
/* The names of each internal function, indexed by function number. */
const char *const internal_fn_name_array[] = {
{
#define DEF_INTERNAL_FN(CODE, FLAGS, FNSPEC) \
if (FNSPEC) internal_fn_fnspec_array[IFN_##CODE] = \
- build_string ((int) sizeof (FNSPEC), FNSPEC ? FNSPEC : "");
+ build_string ((int) sizeof (FNSPEC) - 1, FNSPEC ? FNSPEC : "");
#include "internal-fn.def"
internal_fn_fnspec_array[IFN_LAST] = 0;
}
#define load_lanes_direct { -1, -1, false }
#define mask_load_lanes_direct { -1, -1, false }
#define gather_load_direct { 3, 1, false }
+#define len_load_direct { -1, -1, false }
#define mask_store_direct { 3, 2, false }
#define store_lanes_direct { 0, 0, false }
#define mask_store_lanes_direct { 0, 0, false }
#define vec_condu_direct { 0, 0, false }
#define vec_condeq_direct { 0, 0, false }
#define scatter_store_direct { 3, 1, false }
+#define len_store_direct { 3, 3, false }
+#define vec_set_direct { 3, 3, false }
#define unary_direct { 0, 0, true }
#define binary_direct { 0, 0, true }
#define ternary_direct { 0, 0, true }
/* This should get expanded in the sanopt pass. */
+static void
+expand_HWASAN_CHECK (internal_fn, gcall *)
+{
+ gcc_unreachable ();
+}
+
+/* For hwasan stack tagging:
+ Clear tags on the dynamically allocated space.
+ For use after an object dynamically allocated on the stack goes out of
+ scope. */
+static void
+expand_HWASAN_ALLOCA_UNPOISON (internal_fn, gcall *gc)
+{
+ gcc_assert (Pmode == ptr_mode);
+ tree restored_position = gimple_call_arg (gc, 0);
+ rtx restored_rtx = expand_expr (restored_position, NULL_RTX, VOIDmode,
+ EXPAND_NORMAL);
+ rtx func = init_one_libfunc ("__hwasan_tag_memory");
+ rtx off = expand_simple_binop (Pmode, MINUS, restored_rtx,
+ stack_pointer_rtx, NULL_RTX, 0,
+ OPTAB_WIDEN);
+ emit_library_call_value (func, NULL_RTX, LCT_NORMAL, VOIDmode,
+ virtual_stack_dynamic_rtx, Pmode,
+ HWASAN_STACK_BACKGROUND, QImode,
+ off, Pmode);
+}
+
+/* For hwasan stack tagging:
+ Return a tag to be used for a dynamic allocation. */
+static void
+expand_HWASAN_CHOOSE_TAG (internal_fn, gcall *gc)
+{
+ tree tag = gimple_call_lhs (gc);
+ rtx target = expand_expr (tag, NULL_RTX, VOIDmode, EXPAND_NORMAL);
+ machine_mode mode = GET_MODE (target);
+ gcc_assert (mode == QImode);
+
+ rtx base_tag = targetm.memtag.extract_tag (hwasan_frame_base (), NULL_RTX);
+ gcc_assert (base_tag);
+ rtx tag_offset = gen_int_mode (hwasan_current_frame_tag (), QImode);
+ rtx chosen_tag = expand_simple_binop (QImode, PLUS, base_tag, tag_offset,
+ target, /* unsignedp = */1,
+ OPTAB_WIDEN);
+ chosen_tag = hwasan_truncate_to_tag_size (chosen_tag, target);
+
+ /* Really need to put the tag into the `target` RTX. */
+ if (chosen_tag != target)
+ {
+ rtx temp = chosen_tag;
+ gcc_assert (GET_MODE (chosen_tag) == mode);
+ emit_move_insn (target, temp);
+ }
+
+ hwasan_increment_frame_tag ();
+}
+
+/* For hwasan stack tagging:
+ Tag a region of space in the shadow stack according to the base pointer of
+ an object on the stack. N.b. the length provided in the internal call is
+ required to be aligned to HWASAN_TAG_GRANULE_SIZE. */
+static void
+expand_HWASAN_MARK (internal_fn, gcall *gc)
+{
+ gcc_assert (ptr_mode == Pmode);
+ HOST_WIDE_INT flag = tree_to_shwi (gimple_call_arg (gc, 0));
+ bool is_poison = ((asan_mark_flags)flag) == ASAN_MARK_POISON;
+
+ tree base = gimple_call_arg (gc, 1);
+ gcc_checking_assert (TREE_CODE (base) == ADDR_EXPR);
+ rtx base_rtx = expand_normal (base);
+
+ rtx tag = is_poison ? HWASAN_STACK_BACKGROUND
+ : targetm.memtag.extract_tag (base_rtx, NULL_RTX);
+ rtx address = targetm.memtag.untagged_pointer (base_rtx, NULL_RTX);
+
+ tree len = gimple_call_arg (gc, 2);
+ rtx r_len = expand_normal (len);
+
+ rtx func = init_one_libfunc ("__hwasan_tag_memory");
+ emit_library_call (func, LCT_NORMAL, VOIDmode, address, Pmode,
+ tag, QImode, r_len, Pmode);
+}
+
+/* For hwasan stack tagging:
+ Store a tag into a pointer. */
+static void
+expand_HWASAN_SET_TAG (internal_fn, gcall *gc)
+{
+ gcc_assert (ptr_mode == Pmode);
+ tree g_target = gimple_call_lhs (gc);
+ tree g_ptr = gimple_call_arg (gc, 0);
+ tree g_tag = gimple_call_arg (gc, 1);
+
+ rtx ptr = expand_normal (g_ptr);
+ rtx tag = expand_expr (g_tag, NULL_RTX, QImode, EXPAND_NORMAL);
+ rtx target = expand_normal (g_target);
+
+ rtx untagged = targetm.memtag.untagged_pointer (ptr, target);
+ rtx tagged_value = targetm.memtag.set_tag (untagged, tag, target);
+ if (tagged_value != target)
+ emit_move_insn (target, tagged_value);
+}
+
+/* This should get expanded in the sanopt pass. */
+
static void
expand_ASAN_CHECK (internal_fn, gcall *)
{
if (++cnt > 30)
return prec + (orig_sign != sign);
}
+ if (CONVERT_EXPR_P (arg)
+ && INTEGRAL_TYPE_P (TREE_TYPE (TREE_OPERAND (arg, 0)))
+ && TYPE_PRECISION (TREE_TYPE (TREE_OPERAND (arg, 0))) > prec)
+ {
+ /* We have e.g. (unsigned short) y_2 where int y_2 = (int) x_1(D);
+ If y_2's min precision is smaller than prec, return that. */
+ int oprec = get_min_precision (TREE_OPERAND (arg, 0), sign);
+ if (oprec < prec)
+ return oprec + (orig_sign != sign);
+ }
if (TREE_CODE (arg) != SSA_NAME)
return prec + (orig_sign != sign);
wide_int arg_min, arg_max;
/* Add sub/add overflow checking to the statement STMT.
CODE says whether the operation is +, or -. */
-static void
+void
expand_addsub_overflow (location_t loc, tree_code code, tree lhs,
tree arg0, tree arg1, bool unsr_p, bool uns0_p,
bool uns1_p, bool is_ubsan, tree *datap)
NULL, done_label, profile_probability::very_likely ());
goto do_error_label;
case 3:
+ if (get_min_precision (arg1, UNSIGNED)
+ + get_min_precision (arg0, SIGNED) <= GET_MODE_PRECISION (mode))
+ {
+ /* If the first operand is sign extended from narrower type, the
+ second operand is zero extended from narrower type and
+ the sum of the two precisions is smaller or equal to the
+ result precision: if the first argument is at runtime
+ non-negative, maximum result will be 0x7e81 or 0x7f..fe80..01
+ and there will be no overflow, if the first argument is
+ negative and the second argument zero, the result will be
+ 0 and there will be no overflow, if the first argument is
+ negative and the second argument positive, the result when
+ treated as signed will be negative (minimum -0x7f80 or
+ -0x7f..f80..0) there there will be always overflow. So, do
+ res = (U) (s1 * u2)
+ ovf = (S) res < 0 */
+ struct separate_ops ops;
+ ops.code = MULT_EXPR;
+ ops.type
+ = build_nonstandard_integer_type (GET_MODE_PRECISION (mode),
+ 1);
+ ops.op0 = make_tree (ops.type, op0);
+ ops.op1 = make_tree (ops.type, op1);
+ ops.op2 = NULL_TREE;
+ ops.location = loc;
+ res = expand_expr_real_2 (&ops, NULL_RTX, mode, EXPAND_NORMAL);
+ do_compare_rtx_and_jump (res, const0_rtx, GE, false,
+ mode, NULL_RTX, NULL, done_label,
+ profile_probability::very_likely ());
+ goto do_error_label;
+ }
rtx_code_label *do_main_label;
do_main_label = gen_label_rtx ();
do_compare_rtx_and_jump (op0, const0_rtx, GE, false, mode, NULL_RTX,
/* u1 * u2 -> sr */
if (uns0_p && uns1_p && !unsr_p)
{
- uns = true;
+ if ((pos_neg0 | pos_neg1) == 1)
+ {
+ /* If both arguments are zero extended from narrower types,
+ the MSB will be clear on both and so we can pretend it is
+ a normal s1 * s2 -> sr multiplication. */
+ uns0_p = false;
+ uns1_p = false;
+ }
+ else
+ uns = true;
/* Rest of handling of this case after res is computed. */
goto do_main;
}
profile_probability::very_likely ());
goto do_error_label;
}
+ if (get_min_precision (arg0, SIGNED)
+ + get_min_precision (arg1, SIGNED) <= GET_MODE_PRECISION (mode))
+ {
+ /* If both operands are sign extended from narrower types and
+ the sum of the two precisions is smaller or equal to the
+ result precision: if both arguments are at runtime
+ non-negative, maximum result will be 0x3f01 or 0x3f..f0..01
+ and there will be no overflow, if both arguments are negative,
+ maximum result will be 0x40..00 and there will be no overflow
+ either, if one argument is positive and the other argument
+ negative, the result when treated as signed will be negative
+ and there will be always overflow, and if one argument is
+ zero and the other negative the result will be zero and no
+ overflow. So, do
+ res = (U) (s1 * s2)
+ ovf = (S) res < 0 */
+ struct separate_ops ops;
+ ops.code = MULT_EXPR;
+ ops.type
+ = build_nonstandard_integer_type (GET_MODE_PRECISION (mode),
+ 1);
+ ops.op0 = make_tree (ops.type, op0);
+ ops.op1 = make_tree (ops.type, op1);
+ ops.op2 = NULL_TREE;
+ ops.location = loc;
+ res = expand_expr_real_2 (&ops, NULL_RTX, mode, EXPAND_NORMAL);
+ do_compare_rtx_and_jump (res, const0_rtx, GE, false,
+ mode, NULL_RTX, NULL, done_label,
+ profile_probability::very_likely ());
+ goto do_error_label;
+ }
/* The general case, do all the needed comparisons at runtime. */
rtx_code_label *do_main_label, *after_negate_label;
rtx rop0, rop1;
profile_probability::very_likely ());
else
{
+ /* RES is used more than once, place it in a pseudo. */
+ res = force_reg (mode, res);
+
rtx signbit = expand_shift (RSHIFT_EXPR, mode, res, prec - 1,
NULL_RTX, 0);
/* RES is low half of the double width result, HIPART
return fold_build2 (MEM_REF, type, addr, build_int_cst (alias_ptr_type, 0));
}
-/* Expand MASK_LOAD{,_LANES} call STMT using optab OPTAB. */
+/* Expand MASK_LOAD{,_LANES} or LEN_LOAD call STMT using optab OPTAB. */
static void
-expand_mask_load_optab_fn (internal_fn, gcall *stmt, convert_optab optab)
+expand_partial_load_optab_fn (internal_fn, gcall *stmt, convert_optab optab)
{
class expand_operand ops[3];
tree type, lhs, rhs, maskt;
if (optab == vec_mask_load_lanes_optab)
icode = get_multi_vector_move (type, optab);
+ else if (optab == len_load_optab)
+ icode = direct_optab_handler (optab, TYPE_MODE (type));
else
icode = convert_optab_handler (optab, TYPE_MODE (type),
TYPE_MODE (TREE_TYPE (maskt)));
target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE);
create_output_operand (&ops[0], target, TYPE_MODE (type));
create_fixed_operand (&ops[1], mem);
- create_input_operand (&ops[2], mask, TYPE_MODE (TREE_TYPE (maskt)));
+ if (optab == len_load_optab)
+ create_convert_operand_from (&ops[2], mask, TYPE_MODE (TREE_TYPE (maskt)),
+ TYPE_UNSIGNED (TREE_TYPE (maskt)));
+ else
+ create_input_operand (&ops[2], mask, TYPE_MODE (TREE_TYPE (maskt)));
expand_insn (icode, 3, ops);
if (!rtx_equal_p (target, ops[0].value))
emit_move_insn (target, ops[0].value);
}
+#define expand_mask_load_optab_fn expand_partial_load_optab_fn
#define expand_mask_load_lanes_optab_fn expand_mask_load_optab_fn
+#define expand_len_load_optab_fn expand_partial_load_optab_fn
-/* Expand MASK_STORE{,_LANES} call STMT using optab OPTAB. */
+/* Expand MASK_STORE{,_LANES} or LEN_STORE call STMT using optab OPTAB. */
static void
-expand_mask_store_optab_fn (internal_fn, gcall *stmt, convert_optab optab)
+expand_partial_store_optab_fn (internal_fn, gcall *stmt, convert_optab optab)
{
class expand_operand ops[3];
tree type, lhs, rhs, maskt;
if (optab == vec_mask_store_lanes_optab)
icode = get_multi_vector_move (type, optab);
+ else if (optab == len_store_optab)
+ icode = direct_optab_handler (optab, TYPE_MODE (type));
else
icode = convert_optab_handler (optab, TYPE_MODE (type),
TYPE_MODE (TREE_TYPE (maskt)));
reg = expand_normal (rhs);
create_fixed_operand (&ops[0], mem);
create_input_operand (&ops[1], reg, TYPE_MODE (type));
- create_input_operand (&ops[2], mask, TYPE_MODE (TREE_TYPE (maskt)));
+ if (optab == len_store_optab)
+ create_convert_operand_from (&ops[2], mask, TYPE_MODE (TREE_TYPE (maskt)),
+ TYPE_UNSIGNED (TREE_TYPE (maskt)));
+ else
+ create_input_operand (&ops[2], mask, TYPE_MODE (TREE_TYPE (maskt)));
expand_insn (icode, 3, ops);
}
+#define expand_mask_store_optab_fn expand_partial_store_optab_fn
#define expand_mask_store_lanes_optab_fn expand_mask_store_optab_fn
+#define expand_len_store_optab_fn expand_partial_store_optab_fn
/* Expand VCOND, VCONDU and VCONDEQ optab internal functions.
The expansion of STMT happens based on OPTAB table associated. */
rtx_op2 = expand_normal (op2);
mask = force_reg (mask_mode, mask);
- rtx_op1 = force_reg (GET_MODE (rtx_op1), rtx_op1);
+ rtx_op1 = force_reg (mode, rtx_op1);
rtx target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE);
create_output_operand (&ops[0], target, mode);
#define expand_vec_cond_mask_optab_fn expand_vect_cond_mask_optab_fn
+/* Expand VEC_SET internal functions. */
+
+static void
+expand_vec_set_optab_fn (internal_fn, gcall *stmt, convert_optab optab)
+{
+ tree lhs = gimple_call_lhs (stmt);
+ tree op0 = gimple_call_arg (stmt, 0);
+ tree op1 = gimple_call_arg (stmt, 1);
+ tree op2 = gimple_call_arg (stmt, 2);
+ rtx target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE);
+ rtx src = expand_normal (op0);
+
+ machine_mode outermode = TYPE_MODE (TREE_TYPE (op0));
+ scalar_mode innermode = GET_MODE_INNER (outermode);
+
+ rtx value = expand_normal (op1);
+ rtx pos = expand_normal (op2);
+
+ class expand_operand ops[3];
+ enum insn_code icode = optab_handler (optab, outermode);
+
+ if (icode != CODE_FOR_nothing)
+ {
+ rtx temp = gen_reg_rtx (outermode);
+ emit_move_insn (temp, src);
+
+ create_fixed_operand (&ops[0], temp);
+ create_input_operand (&ops[1], value, innermode);
+ create_convert_operand_from (&ops[2], pos, TYPE_MODE (TREE_TYPE (op2)),
+ true);
+ if (maybe_expand_insn (icode, 3, ops))
+ {
+ emit_move_insn (target, temp);
+ return;
+ }
+ }
+ gcc_unreachable ();
+}
+
static void
expand_ABNORMAL_DISPATCHER (internal_fn, gcall *)
{
emit_move_insn (lhs_rtx, ops[0].value);
}
+/* Helper for expand_DIVMOD. Return true if the sequence starting with
+ INSN contains any call insns or insns with {,U}{DIV,MOD} rtxes. */
+
+static bool
+contains_call_div_mod (rtx_insn *insn)
+{
+ subrtx_iterator::array_type array;
+ for (; insn; insn = NEXT_INSN (insn))
+ if (CALL_P (insn))
+ return true;
+ else if (INSN_P (insn))
+ FOR_EACH_SUBRTX (iter, array, PATTERN (insn), NONCONST)
+ switch (GET_CODE (*iter))
+ {
+ case CALL:
+ case DIV:
+ case UDIV:
+ case MOD:
+ case UMOD:
+ return true;
+ default:
+ break;
+ }
+ return false;
+ }
+
/* Expand DIVMOD() using:
a) optab handler for udivmod/sdivmod if it is available.
b) If optab_handler doesn't exist, generate call to
rtx op1 = expand_normal (arg1);
rtx target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE);
- rtx quotient, remainder, libfunc;
+ rtx quotient = NULL_RTX, remainder = NULL_RTX;
+ rtx_insn *insns = NULL;
+
+ if (TREE_CODE (arg1) == INTEGER_CST)
+ {
+ /* For DIVMOD by integral constants, there could be efficient code
+ expanded inline e.g. using shifts and plus/minus. Try to expand
+ the division and modulo and if it emits any library calls or any
+ {,U}{DIV,MOD} rtxes throw it away and use a divmod optab or
+ divmod libcall. */
+ scalar_int_mode int_mode;
+ if (remainder == NULL_RTX
+ && optimize
+ && CONST_INT_P (op1)
+ && !pow2p_hwi (INTVAL (op1))
+ && is_int_mode (TYPE_MODE (type), &int_mode)
+ && GET_MODE_SIZE (int_mode) == 2 * UNITS_PER_WORD
+ && optab_handler (and_optab, word_mode) != CODE_FOR_nothing
+ && optab_handler (add_optab, word_mode) != CODE_FOR_nothing
+ && optimize_insn_for_speed_p ())
+ {
+ rtx_insn *last = get_last_insn ();
+ remainder = NULL_RTX;
+ quotient = expand_doubleword_divmod (int_mode, op0, op1, &remainder,
+ TYPE_UNSIGNED (type));
+ if (quotient != NULL_RTX)
+ {
+ if (optab_handler (mov_optab, int_mode) != CODE_FOR_nothing)
+ {
+ rtx_insn *move = emit_move_insn (quotient, quotient);
+ set_dst_reg_note (move, REG_EQUAL,
+ gen_rtx_fmt_ee (TYPE_UNSIGNED (type)
+ ? UDIV : DIV, int_mode,
+ copy_rtx (op0), op1),
+ quotient);
+ move = emit_move_insn (remainder, remainder);
+ set_dst_reg_note (move, REG_EQUAL,
+ gen_rtx_fmt_ee (TYPE_UNSIGNED (type)
+ ? UMOD : MOD, int_mode,
+ copy_rtx (op0), op1),
+ quotient);
+ }
+ }
+ else
+ delete_insns_since (last);
+ }
+
+ if (remainder == NULL_RTX)
+ {
+ struct separate_ops ops;
+ ops.code = TRUNC_DIV_EXPR;
+ ops.type = type;
+ ops.op0 = make_tree (ops.type, op0);
+ ops.op1 = arg1;
+ ops.op2 = NULL_TREE;
+ ops.location = gimple_location (call_stmt);
+ start_sequence ();
+ quotient = expand_expr_real_2 (&ops, NULL_RTX, mode, EXPAND_NORMAL);
+ if (contains_call_div_mod (get_insns ()))
+ quotient = NULL_RTX;
+ else
+ {
+ ops.code = TRUNC_MOD_EXPR;
+ remainder = expand_expr_real_2 (&ops, NULL_RTX, mode,
+ EXPAND_NORMAL);
+ if (contains_call_div_mod (get_insns ()))
+ remainder = NULL_RTX;
+ }
+ if (remainder)
+ insns = get_insns ();
+ end_sequence ();
+ }
+ }
+
+ if (remainder)
+ emit_insn (insns);
/* Check if optab_handler exists for divmod_optab for given mode. */
- if (optab_handler (tab, mode) != CODE_FOR_nothing)
+ else if (optab_handler (tab, mode) != CODE_FOR_nothing)
{
quotient = gen_reg_rtx (mode);
remainder = gen_reg_rtx (mode);
}
/* Generate call to divmod libfunc if it exists. */
- else if ((libfunc = optab_libfunc (tab, mode)) != NULL_RTX)
+ else if (rtx libfunc = optab_libfunc (tab, mode))
targetm.expand_divmod_libfunc (libfunc, mode, op0, op1,
"ient, &remainder);
#define direct_cond_unary_optab_supported_p direct_optab_supported_p
#define direct_cond_binary_optab_supported_p direct_optab_supported_p
#define direct_cond_ternary_optab_supported_p direct_optab_supported_p
-#define direct_mask_load_optab_supported_p direct_optab_supported_p
+#define direct_mask_load_optab_supported_p convert_optab_supported_p
#define direct_load_lanes_optab_supported_p multi_vector_optab_supported_p
#define direct_mask_load_lanes_optab_supported_p multi_vector_optab_supported_p
#define direct_gather_load_optab_supported_p convert_optab_supported_p
-#define direct_mask_store_optab_supported_p direct_optab_supported_p
+#define direct_len_load_optab_supported_p direct_optab_supported_p
+#define direct_mask_store_optab_supported_p convert_optab_supported_p
#define direct_store_lanes_optab_supported_p multi_vector_optab_supported_p
#define direct_mask_store_lanes_optab_supported_p multi_vector_optab_supported_p
#define direct_vec_cond_mask_optab_supported_p multi_vector_optab_supported_p
#define direct_vec_condu_optab_supported_p multi_vector_optab_supported_p
#define direct_vec_condeq_optab_supported_p multi_vector_optab_supported_p
#define direct_scatter_store_optab_supported_p convert_optab_supported_p
+#define direct_len_store_optab_supported_p direct_optab_supported_p
#define direct_while_optab_supported_p convert_optab_supported_p
#define direct_fold_extract_optab_supported_p direct_optab_supported_p
#define direct_fold_left_optab_supported_p direct_optab_supported_p
#define direct_mask_fold_left_optab_supported_p direct_optab_supported_p
#define direct_check_ptrs_optab_supported_p direct_optab_supported_p
+#define direct_vec_set_optab_supported_p direct_optab_supported_p
/* Return the optab used by internal function FN. */
case IFN_MASK_LOAD_LANES:
case IFN_GATHER_LOAD:
case IFN_MASK_GATHER_LOAD:
+ case IFN_LEN_LOAD:
return true;
default:
case IFN_MASK_STORE_LANES:
case IFN_SCATTER_STORE:
case IFN_MASK_SCATTER_STORE:
+ case IFN_LEN_STORE:
return true;
default:
switch (fn)
{
case IFN_MASK_STORE:
+ case IFN_MASK_STORE_LANES:
case IFN_SCATTER_STORE:
case IFN_MASK_SCATTER_STORE:
+ case IFN_LEN_STORE:
return 3;
default: