/* Internal functions. Copyright (C) 2011-2013 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 . */ #include "config.h" #include "system.h" #include "coretypes.h" #include "internal-fn.h" #include "tree.h" #include "stor-layout.h" #include "expr.h" #include "optabs.h" #include "basic-block.h" #include "tree-ssa-alias.h" #include "internal-fn.h" #include "gimple-expr.h" #include "is-a.h" #include "gimple.h" #include "ubsan.h" #include "target.h" #include "predict.h" /* The names of each internal function, indexed by function number. */ const char *const internal_fn_name_array[] = { #define DEF_INTERNAL_FN(CODE, FLAGS) #CODE, #include "internal-fn.def" #undef DEF_INTERNAL_FN "" }; /* The ECF_* flags of each internal function, indexed by function number. */ const int internal_fn_flags_array[] = { #define DEF_INTERNAL_FN(CODE, FLAGS) FLAGS, #include "internal-fn.def" #undef DEF_INTERNAL_FN 0 }; /* ARRAY_TYPE is an array of vector modes. Return the associated insn for load-lanes-style optab OPTAB. The insn must exist. */ static enum insn_code get_multi_vector_move (tree array_type, convert_optab optab) { enum insn_code icode; enum machine_mode imode; enum machine_mode vmode; gcc_assert (TREE_CODE (array_type) == ARRAY_TYPE); imode = TYPE_MODE (array_type); vmode = TYPE_MODE (TREE_TYPE (array_type)); icode = convert_optab_handler (optab, imode, vmode); gcc_assert (icode != CODE_FOR_nothing); return icode; } /* Expand LOAD_LANES call STMT. */ static void expand_LOAD_LANES (gimple stmt) { struct expand_operand ops[2]; tree type, lhs, rhs; rtx target, mem; lhs = gimple_call_lhs (stmt); rhs = gimple_call_arg (stmt, 0); type = TREE_TYPE (lhs); target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); mem = expand_normal (rhs); gcc_assert (MEM_P (mem)); PUT_MODE (mem, TYPE_MODE (type)); create_output_operand (&ops[0], target, TYPE_MODE (type)); create_fixed_operand (&ops[1], mem); expand_insn (get_multi_vector_move (type, vec_load_lanes_optab), 2, ops); } /* Expand STORE_LANES call STMT. */ static void expand_STORE_LANES (gimple stmt) { struct expand_operand ops[2]; tree type, lhs, rhs; rtx target, reg; lhs = gimple_call_lhs (stmt); rhs = gimple_call_arg (stmt, 0); type = TREE_TYPE (rhs); target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); reg = expand_normal (rhs); gcc_assert (MEM_P (target)); PUT_MODE (target, TYPE_MODE (type)); create_fixed_operand (&ops[0], target); create_input_operand (&ops[1], reg, TYPE_MODE (type)); expand_insn (get_multi_vector_move (type, vec_store_lanes_optab), 2, ops); } static void expand_ANNOTATE (gimple stmt ATTRIBUTE_UNUSED) { gcc_unreachable (); } /* This should get expanded in adjust_simduid_builtins. */ static void expand_GOMP_SIMD_LANE (gimple stmt ATTRIBUTE_UNUSED) { gcc_unreachable (); } /* This should get expanded in adjust_simduid_builtins. */ static void expand_GOMP_SIMD_VF (gimple stmt ATTRIBUTE_UNUSED) { gcc_unreachable (); } /* This should get expanded in adjust_simduid_builtins. */ static void expand_GOMP_SIMD_LAST_LANE (gimple stmt ATTRIBUTE_UNUSED) { gcc_unreachable (); } /* This should get expanded in the sanopt pass. */ static void expand_UBSAN_NULL (gimple stmt ATTRIBUTE_UNUSED) { gcc_unreachable (); } /* Add sub/add overflow checking to the statement STMT. CODE says whether the operation is +, or -. */ void ubsan_expand_si_overflow_addsub_check (tree_code code, gimple stmt) { rtx res, op0, op1; tree lhs, fn, arg0, arg1; rtx done_label, do_error, target = NULL_RTX; lhs = gimple_call_lhs (stmt); arg0 = gimple_call_arg (stmt, 0); arg1 = gimple_call_arg (stmt, 1); done_label = gen_label_rtx (); do_error = gen_label_rtx (); do_pending_stack_adjust (); op0 = expand_normal (arg0); op1 = expand_normal (arg1); enum machine_mode mode = TYPE_MODE (TREE_TYPE (arg0)); if (lhs) target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); enum insn_code icode = optab_handler (code == PLUS_EXPR ? addv4_optab : subv4_optab, mode); if (icode != CODE_FOR_nothing) { struct expand_operand ops[4]; rtx last = get_last_insn (); res = gen_reg_rtx (mode); create_output_operand (&ops[0], res, mode); create_input_operand (&ops[1], op0, mode); create_input_operand (&ops[2], op1, mode); create_fixed_operand (&ops[3], do_error); if (maybe_expand_insn (icode, 4, ops)) { last = get_last_insn (); if (profile_status_for_fn (cfun) != PROFILE_ABSENT && JUMP_P (last) && any_condjump_p (last) && !find_reg_note (last, REG_BR_PROB, 0)) add_int_reg_note (last, REG_BR_PROB, PROB_VERY_UNLIKELY); emit_jump (done_label); } else { delete_insns_since (last); icode = CODE_FOR_nothing; } } if (icode == CODE_FOR_nothing) { rtx sub_check = gen_label_rtx (); /* Compute the operation. On RTL level, the addition is always unsigned. */ res = expand_binop (mode, add_optab, op0, op1, NULL_RTX, false, OPTAB_LIB_WIDEN); /* If the op1 is negative, we have to use a different check. */ emit_cmp_and_jump_insns (op1, const0_rtx, LT, NULL_RTX, mode, false, sub_check, PROB_EVEN); /* Compare the result of the addition with one of the operands. */ emit_cmp_and_jump_insns (res, op0, code == PLUS_EXPR ? GE : LE, NULL_RTX, mode, false, done_label, PROB_VERY_LIKELY); /* If we get here, we have to print the error. */ emit_jump (do_error); emit_label (sub_check); /* We have k = a + b for b < 0 here. k <= a must hold. */ emit_cmp_and_jump_insns (res, op0, code == PLUS_EXPR ? LE : GE, NULL_RTX, mode, false, done_label, PROB_VERY_LIKELY); } emit_label (do_error); /* Expand the ubsan builtin call. */ push_temp_slots (); fn = ubsan_build_overflow_builtin (code, gimple_location (stmt), TREE_TYPE (arg0), arg0, arg1); expand_normal (fn); pop_temp_slots (); do_pending_stack_adjust (); /* We're done. */ emit_label (done_label); if (lhs) emit_move_insn (target, res); } /* Add negate overflow checking to the statement STMT. */ void ubsan_expand_si_overflow_neg_check (gimple stmt) { rtx res, op1; tree lhs, fn, arg1; rtx done_label, do_error, target = NULL_RTX; lhs = gimple_call_lhs (stmt); arg1 = gimple_call_arg (stmt, 1); done_label = gen_label_rtx (); do_error = gen_label_rtx (); do_pending_stack_adjust (); op1 = expand_normal (arg1); enum machine_mode mode = TYPE_MODE (TREE_TYPE (arg1)); if (lhs) target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); enum insn_code icode = optab_handler (negv3_optab, mode); if (icode != CODE_FOR_nothing) { struct expand_operand ops[3]; rtx last = get_last_insn (); res = gen_reg_rtx (mode); create_output_operand (&ops[0], res, mode); create_input_operand (&ops[1], op1, mode); create_fixed_operand (&ops[2], do_error); if (maybe_expand_insn (icode, 3, ops)) { last = get_last_insn (); if (profile_status_for_fn (cfun) != PROFILE_ABSENT && JUMP_P (last) && any_condjump_p (last) && !find_reg_note (last, REG_BR_PROB, 0)) add_int_reg_note (last, REG_BR_PROB, PROB_VERY_UNLIKELY); emit_jump (done_label); } else { delete_insns_since (last); icode = CODE_FOR_nothing; } } if (icode == CODE_FOR_nothing) { /* Compute the operation. On RTL level, the addition is always unsigned. */ res = expand_unop (mode, neg_optab, op1, NULL_RTX, false); /* Compare the operand with the most negative value. */ rtx minv = expand_normal (TYPE_MIN_VALUE (TREE_TYPE (arg1))); emit_cmp_and_jump_insns (op1, minv, NE, NULL_RTX, mode, false, done_label, PROB_VERY_LIKELY); } emit_label (do_error); /* Expand the ubsan builtin call. */ push_temp_slots (); fn = ubsan_build_overflow_builtin (NEGATE_EXPR, gimple_location (stmt), TREE_TYPE (arg1), arg1, NULL_TREE); expand_normal (fn); pop_temp_slots (); do_pending_stack_adjust (); /* We're done. */ emit_label (done_label); if (lhs) emit_move_insn (target, res); } /* Add mul overflow checking to the statement STMT. */ void ubsan_expand_si_overflow_mul_check (gimple stmt) { rtx res, op0, op1; tree lhs, fn, arg0, arg1; rtx done_label, do_error, target = NULL_RTX; lhs = gimple_call_lhs (stmt); arg0 = gimple_call_arg (stmt, 0); arg1 = gimple_call_arg (stmt, 1); done_label = gen_label_rtx (); do_error = gen_label_rtx (); do_pending_stack_adjust (); op0 = expand_normal (arg0); op1 = expand_normal (arg1); enum machine_mode mode = TYPE_MODE (TREE_TYPE (arg0)); if (lhs) target = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); enum insn_code icode = optab_handler (mulv4_optab, mode); if (icode != CODE_FOR_nothing) { struct expand_operand ops[4]; rtx last = get_last_insn (); res = gen_reg_rtx (mode); create_output_operand (&ops[0], res, mode); create_input_operand (&ops[1], op0, mode); create_input_operand (&ops[2], op1, mode); create_fixed_operand (&ops[3], do_error); if (maybe_expand_insn (icode, 4, ops)) { last = get_last_insn (); if (profile_status_for_fn (cfun) != PROFILE_ABSENT && JUMP_P (last) && any_condjump_p (last) && !find_reg_note (last, REG_BR_PROB, 0)) add_int_reg_note (last, REG_BR_PROB, PROB_VERY_UNLIKELY); emit_jump (done_label); } else { delete_insns_since (last); icode = CODE_FOR_nothing; } } if (icode == CODE_FOR_nothing) { struct separate_ops ops; ops.op0 = arg0; ops.op1 = arg1; ops.op2 = NULL_TREE; ops.location = gimple_location (stmt); if (GET_MODE_2XWIDER_MODE (mode) != VOIDmode && targetm.scalar_mode_supported_p (GET_MODE_2XWIDER_MODE (mode))) { enum machine_mode wmode = GET_MODE_2XWIDER_MODE (mode); ops.code = WIDEN_MULT_EXPR; ops.type = build_nonstandard_integer_type (GET_MODE_PRECISION (wmode), 0); res = expand_expr_real_2 (&ops, NULL_RTX, wmode, EXPAND_NORMAL); rtx hipart = expand_shift (RSHIFT_EXPR, wmode, res, GET_MODE_PRECISION (mode), NULL_RTX, 0); hipart = gen_lowpart (mode, hipart); res = gen_lowpart (mode, res); rtx signbit = expand_shift (RSHIFT_EXPR, mode, res, GET_MODE_PRECISION (mode) - 1, NULL_RTX, 0); /* RES is low half of the double width result, HIPART the high half. There was overflow if HIPART is different from RES < 0 ? -1 : 0. */ emit_cmp_and_jump_insns (signbit, hipart, EQ, NULL_RTX, mode, false, done_label, PROB_VERY_LIKELY); } else { /* For now we don't instrument this. See __mulvDI3 in libgcc2.c for what could be done. */ ops.code = MULT_EXPR; ops.type = TREE_TYPE (arg0); res = expand_expr_real_2 (&ops, NULL_RTX, mode, EXPAND_NORMAL); emit_jump (done_label); } } emit_label (do_error); /* Expand the ubsan builtin call. */ push_temp_slots (); fn = ubsan_build_overflow_builtin (MULT_EXPR, gimple_location (stmt), TREE_TYPE (arg0), arg0, arg1); expand_normal (fn); pop_temp_slots (); do_pending_stack_adjust (); /* We're done. */ emit_label (done_label); if (lhs) emit_move_insn (target, res); } /* Expand UBSAN_CHECK_ADD call STMT. */ static void expand_UBSAN_CHECK_ADD (gimple stmt) { ubsan_expand_si_overflow_addsub_check (PLUS_EXPR, stmt); } /* Expand UBSAN_CHECK_SUB call STMT. */ static void expand_UBSAN_CHECK_SUB (gimple stmt) { if (integer_zerop (gimple_call_arg (stmt, 0))) ubsan_expand_si_overflow_neg_check (stmt); else ubsan_expand_si_overflow_addsub_check (MINUS_EXPR, stmt); } /* Expand UBSAN_CHECK_MUL call STMT. */ static void expand_UBSAN_CHECK_MUL (gimple stmt) { ubsan_expand_si_overflow_mul_check (stmt); } /* This should get folded in tree-vectorizer.c. */ static void expand_LOOP_VECTORIZED (gimple stmt ATTRIBUTE_UNUSED) { gcc_unreachable (); } static void expand_MASK_LOAD (gimple stmt) { struct expand_operand ops[3]; tree type, lhs, rhs, maskt; rtx mem, target, mask; maskt = gimple_call_arg (stmt, 2); lhs = gimple_call_lhs (stmt); type = TREE_TYPE (lhs); rhs = fold_build2 (MEM_REF, type, gimple_call_arg (stmt, 0), gimple_call_arg (stmt, 1)); mem = expand_expr (rhs, NULL_RTX, VOIDmode, EXPAND_WRITE); gcc_assert (MEM_P (mem)); mask = expand_normal (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))); expand_insn (optab_handler (maskload_optab, TYPE_MODE (type)), 3, ops); } static void expand_MASK_STORE (gimple stmt) { struct expand_operand ops[3]; tree type, lhs, rhs, maskt; rtx mem, reg, mask; maskt = gimple_call_arg (stmt, 2); rhs = gimple_call_arg (stmt, 3); type = TREE_TYPE (rhs); lhs = fold_build2 (MEM_REF, type, gimple_call_arg (stmt, 0), gimple_call_arg (stmt, 1)); mem = expand_expr (lhs, NULL_RTX, VOIDmode, EXPAND_WRITE); gcc_assert (MEM_P (mem)); mask = expand_normal (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))); expand_insn (optab_handler (maskstore_optab, TYPE_MODE (type)), 3, ops); } /* Routines to expand each internal function, indexed by function number. Each routine has the prototype: expand_ (gimple stmt) where STMT is the statement that performs the call. */ static void (*const internal_fn_expanders[]) (gimple) = { #define DEF_INTERNAL_FN(CODE, FLAGS) expand_##CODE, #include "internal-fn.def" #undef DEF_INTERNAL_FN 0 }; /* Expand STMT, which is a call to internal function FN. */ void expand_internal_call (gimple stmt) { internal_fn_expanders[(int) gimple_call_internal_fn (stmt)] (stmt); }