| /* brig-atomic-inst-handler.cc -- brig atomic instruction handling |
| Copyright (C) 2016-2019 Free Software Foundation, Inc. |
| |
| Contributed by Pekka Jaaskelainen <pekka.jaaskelainen@parmance.com> |
| for General Processor Tech. |
| 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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include <sstream> |
| |
| #include "brig-code-entry-handler.h" |
| #include "brig-util.h" |
| #include "fold-const.h" |
| #include "diagnostic.h" |
| #include "tree-pretty-print.h" |
| #include "print-tree.h" |
| #include "convert.h" |
| #include "langhooks.h" |
| #include "gimple-expr.h" |
| #include "stringpool.h" |
| #include "brig-builtins.h" |
| |
| brig_atomic_inst_handler::brig_atomic_inst_handler (brig_to_generic &parent) |
| : brig_code_entry_handler (parent) |
| { |
| } |
| |
| size_t |
| brig_atomic_inst_handler::generate_tree (const BrigInstBase &inst, |
| BrigAtomicOperation8_t atomic_opcode) |
| { |
| tree_stl_vec operands = build_operands (inst); |
| const int first_input |
| = gccbrig_hsa_opcode_op_output_p (inst.opcode, 0) ? 1 : 0; |
| |
| tree instr_type = gccbrig_tree_type_for_hsa_type (inst.type); |
| |
| /* Utilize the atomic data types (from C++11 support) for implementing |
| atomic operations. */ |
| |
| tree atomic_type = build_qualified_type (instr_type, TYPE_QUAL_ATOMIC); |
| |
| gcc_assert (atomic_type != NULL_TREE); |
| |
| tree signal_handle = operands[first_input]; |
| tree atomic_ptype = build_pointer_type (atomic_type); |
| tree casted_to_ptr = convert_to_pointer (atomic_ptype, signal_handle); |
| |
| tree src0 = NULL_TREE; |
| if (atomic_opcode != BRIG_ATOMIC_LD) |
| src0 = operands[first_input + 1]; |
| |
| tree instr_expr = NULL_TREE; |
| |
| tree ptype = build_pointer_type (instr_type); |
| tree ptr = convert_to_pointer (ptype, operands[first_input]); |
| |
| if (atomic_opcode == BRIG_ATOMIC_ST) |
| { |
| tree mem_ref = build2 (MEM_REF, atomic_type, casted_to_ptr, |
| build_int_cst (atomic_ptype, 0)); |
| instr_expr = build2 (MODIFY_EXPR, atomic_type, mem_ref, src0); |
| } |
| else if (atomic_opcode == BRIG_ATOMIC_LD |
| || (atomic_opcode >= BRIG_ATOMIC_WAIT_EQ |
| && atomic_opcode <= BRIG_ATOMIC_WAITTIMEOUT_GTE)) |
| { |
| tree mem_ref = build2 (MEM_REF, atomic_type, casted_to_ptr, |
| build_int_cst (atomic_ptype, 0)); |
| /* signal_wait* instructions can return spuriously before the |
| condition becomes true. Therefore it's legal to return |
| right away. TODO: builtin calls which can be |
| implemented with a power efficient sleep-wait. */ |
| instr_expr = mem_ref; |
| } |
| else if (atomic_opcode == BRIG_ATOMIC_CAS) |
| { |
| /* Special case for CAS due to the two args. */ |
| tree built_in = NULL_TREE; |
| switch (gccbrig_hsa_type_bit_size (inst.type)) |
| { |
| case 32: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_4); |
| break; |
| case 64: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_8); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| tree src1 = operands[first_input + 2]; |
| |
| tree src0_type |
| = TREE_VALUE (TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (built_in)))); |
| |
| tree src1_type = TREE_VALUE |
| (TREE_CHAIN (TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (built_in))))); |
| |
| instr_expr = call_builtin (built_in, 3, instr_type, ptype, ptr, |
| src0_type, src0, src1_type, src1); |
| } |
| else |
| { |
| tree built_in = NULL_TREE; |
| /* The rest of the builtins have the same number of parameters. |
| Generate a big if..else that finds the correct builtin |
| automagically from the def file. */ |
| #undef DEF_HSAIL_SAT_BUILTIN |
| #undef DEF_HSAIL_BUILTIN |
| #undef DEF_HSAIL_ATOMIC_BUILTIN |
| #undef DEF_HSAIL_INTR_BUILTIN |
| #undef DEF_HSAIL_CVT_ZEROI_SAT_BUILTIN |
| |
| #define DEF_HSAIL_ATOMIC_BUILTIN(ENUM, ATOMIC_OPCODE, HSAIL_TYPE, \ |
| NAME, TYPE, ATTRS) \ |
| if (atomic_opcode == ATOMIC_OPCODE && inst.type == HSAIL_TYPE) \ |
| built_in = builtin_decl_explicit (ENUM); \ |
| else |
| #include "brig-builtins.def" |
| switch (atomic_opcode) |
| { |
| case BRIG_ATOMIC_ADD: |
| switch (gccbrig_hsa_type_bit_size (inst.type)) |
| { |
| case 32: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_ADD_4); |
| break; |
| case 64: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_ADD_8); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| case BRIG_ATOMIC_SUB: |
| switch (gccbrig_hsa_type_bit_size (inst.type)) |
| { |
| case 32: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_SUB_4); |
| break; |
| case 64: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_SUB_8); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| case BRIG_ATOMIC_AND: |
| switch (gccbrig_hsa_type_bit_size (inst.type)) |
| { |
| case 32: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_AND_4); |
| break; |
| case 64: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_AND_8); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| case BRIG_ATOMIC_XOR: |
| switch (gccbrig_hsa_type_bit_size (inst.type)) |
| { |
| case 32: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_XOR_4); |
| break; |
| case 64: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_XOR_8); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| case BRIG_ATOMIC_OR: |
| switch (gccbrig_hsa_type_bit_size (inst.type)) |
| { |
| case 32: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_OR_4); |
| break; |
| case 64: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_FETCH_AND_OR_8); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| case BRIG_ATOMIC_EXCH: |
| switch (gccbrig_hsa_type_bit_size (inst.type)) |
| { |
| case 32: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_LOCK_TEST_AND_SET_4); |
| break; |
| case 64: |
| built_in |
| = builtin_decl_explicit (BUILT_IN_SYNC_LOCK_TEST_AND_SET_8); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| break; |
| default: |
| gcc_unreachable (); |
| }; |
| |
| gcc_assert (built_in != NULL_TREE); |
| tree arg0_type |
| = TREE_VALUE (TREE_CHAIN (TYPE_ARG_TYPES (TREE_TYPE (built_in)))); |
| |
| instr_expr = call_builtin (built_in, 2, instr_type, ptr_type_node, |
| ptr, arg0_type, src0); |
| |
| /* We need a temp variable for the result, because otherwise |
| the gimplifier drops a necessary (unsigned to signed) cast in |
| the output assignment and fails a check later. */ |
| tree tmp_var = create_tmp_var (arg0_type, "builtin_out"); |
| tree tmp_assign |
| = build2 (MODIFY_EXPR, TREE_TYPE (tmp_var), tmp_var, instr_expr); |
| m_parent.m_cf->append_statement (tmp_assign); |
| instr_expr = tmp_var; |
| } |
| |
| if (first_input > 0) |
| build_output_assignment (inst, operands[0], instr_expr); |
| else |
| m_parent.m_cf->append_statement (instr_expr); |
| |
| return inst.base.byteCount; |
| } |
| |
| size_t |
| brig_atomic_inst_handler::operator () (const BrigBase *base) |
| { |
| const BrigInstAtomic *inst = (const BrigInstAtomic *) base; |
| BrigAtomicOperation8_t atomic_opcode; |
| atomic_opcode = inst->atomicOperation; |
| |
| return generate_tree (inst->base, atomic_opcode); |
| } |