| /* Lowering pass for OMP directives. Converts OMP directives into explicit |
| calls to the runtime library (libgomp), data marshalling to implement data |
| sharing and copying clauses, offloading to accelerators, and more. |
| |
| Contributed by Diego Novillo <dnovillo@redhat.com> |
| |
| Copyright (C) 2005-2015 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 |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "hash-set.h" |
| #include "machmode.h" |
| #include "vec.h" |
| #include "double-int.h" |
| #include "input.h" |
| #include "alias.h" |
| #include "symtab.h" |
| #include "wide-int.h" |
| #include "inchash.h" |
| #include "tree.h" |
| #include "fold-const.h" |
| #include "stringpool.h" |
| #include "stor-layout.h" |
| #include "rtl.h" |
| #include "predict.h" |
| #include "hard-reg-set.h" |
| #include "function.h" |
| #include "dominance.h" |
| #include "cfg.h" |
| #include "cfganal.h" |
| #include "basic-block.h" |
| #include "tree-ssa-alias.h" |
| #include "internal-fn.h" |
| #include "gimple-fold.h" |
| #include "gimple-expr.h" |
| #include "is-a.h" |
| #include "gimple.h" |
| #include "gimplify.h" |
| #include "gimple-iterator.h" |
| #include "gimplify-me.h" |
| #include "gimple-walk.h" |
| #include "tree-iterator.h" |
| #include "tree-inline.h" |
| #include "langhooks.h" |
| #include "diagnostic-core.h" |
| #include "gimple-ssa.h" |
| #include "hash-map.h" |
| #include "plugin-api.h" |
| #include "ipa-ref.h" |
| #include "cgraph.h" |
| #include "tree-cfg.h" |
| #include "tree-phinodes.h" |
| #include "ssa-iterators.h" |
| #include "tree-ssanames.h" |
| #include "tree-into-ssa.h" |
| #include "hashtab.h" |
| #include "flags.h" |
| #include "statistics.h" |
| #include "real.h" |
| #include "fixed-value.h" |
| #include "insn-config.h" |
| #include "expmed.h" |
| #include "dojump.h" |
| #include "explow.h" |
| #include "calls.h" |
| #include "emit-rtl.h" |
| #include "varasm.h" |
| #include "stmt.h" |
| #include "expr.h" |
| #include "tree-dfa.h" |
| #include "tree-ssa.h" |
| #include "tree-pass.h" |
| #include "except.h" |
| #include "splay-tree.h" |
| #include "insn-codes.h" |
| #include "optabs.h" |
| #include "cfgloop.h" |
| #include "target.h" |
| #include "common/common-target.h" |
| #include "omp-low.h" |
| #include "gimple-low.h" |
| #include "tree-cfgcleanup.h" |
| #include "pretty-print.h" |
| #include "alloc-pool.h" |
| #include "symbol-summary.h" |
| #include "ipa-prop.h" |
| #include "tree-nested.h" |
| #include "tree-eh.h" |
| #include "cilk.h" |
| #include "context.h" |
| #include "lto-section-names.h" |
| #include "gomp-constants.h" |
| |
| |
| /* Lowering of OMP parallel and workshare constructs proceeds in two |
| phases. The first phase scans the function looking for OMP statements |
| and then for variables that must be replaced to satisfy data sharing |
| clauses. The second phase expands code for the constructs, as well as |
| re-gimplifying things when variables have been replaced with complex |
| expressions. |
| |
| Final code generation is done by pass_expand_omp. The flowgraph is |
| scanned for regions which are then moved to a new |
| function, to be invoked by the thread library, or offloaded. */ |
| |
| /* OMP region information. Every parallel and workshare |
| directive is enclosed between two markers, the OMP_* directive |
| and a corresponding OMP_RETURN statement. */ |
| |
| struct omp_region |
| { |
| /* The enclosing region. */ |
| struct omp_region *outer; |
| |
| /* First child region. */ |
| struct omp_region *inner; |
| |
| /* Next peer region. */ |
| struct omp_region *next; |
| |
| /* Block containing the omp directive as its last stmt. */ |
| basic_block entry; |
| |
| /* Block containing the OMP_RETURN as its last stmt. */ |
| basic_block exit; |
| |
| /* Block containing the OMP_CONTINUE as its last stmt. */ |
| basic_block cont; |
| |
| /* If this is a combined parallel+workshare region, this is a list |
| of additional arguments needed by the combined parallel+workshare |
| library call. */ |
| vec<tree, va_gc> *ws_args; |
| |
| /* The code for the omp directive of this region. */ |
| enum gimple_code type; |
| |
| /* Schedule kind, only used for OMP_FOR type regions. */ |
| enum omp_clause_schedule_kind sched_kind; |
| |
| /* True if this is a combined parallel+workshare region. */ |
| bool is_combined_parallel; |
| }; |
| |
| /* Levels of parallelism as defined by OpenACC. Increasing numbers |
| correspond to deeper loop nesting levels. */ |
| #define MASK_GANG 1 |
| #define MASK_WORKER 2 |
| #define MASK_VECTOR 4 |
| |
| /* Context structure. Used to store information about each parallel |
| directive in the code. */ |
| |
| typedef struct omp_context |
| { |
| /* This field must be at the beginning, as we do "inheritance": Some |
| callback functions for tree-inline.c (e.g., omp_copy_decl) |
| receive a copy_body_data pointer that is up-casted to an |
| omp_context pointer. */ |
| copy_body_data cb; |
| |
| /* The tree of contexts corresponding to the encountered constructs. */ |
| struct omp_context *outer; |
| gimple stmt; |
| |
| /* Map variables to fields in a structure that allows communication |
| between sending and receiving threads. */ |
| splay_tree field_map; |
| tree record_type; |
| tree sender_decl; |
| tree receiver_decl; |
| |
| /* These are used just by task contexts, if task firstprivate fn is |
| needed. srecord_type is used to communicate from the thread |
| that encountered the task construct to task firstprivate fn, |
| record_type is allocated by GOMP_task, initialized by task firstprivate |
| fn and passed to the task body fn. */ |
| splay_tree sfield_map; |
| tree srecord_type; |
| |
| /* A chain of variables to add to the top-level block surrounding the |
| construct. In the case of a parallel, this is in the child function. */ |
| tree block_vars; |
| |
| /* A map of reduction pointer variables. For accelerators, each |
| reduction variable is replaced with an array. Each thread, in turn, |
| is assigned to a slot on that array. */ |
| splay_tree reduction_map; |
| |
| /* Label to which GOMP_cancel{,llation_point} and explicit and implicit |
| barriers should jump to during omplower pass. */ |
| tree cancel_label; |
| |
| /* What to do with variables with implicitly determined sharing |
| attributes. */ |
| enum omp_clause_default_kind default_kind; |
| |
| /* Nesting depth of this context. Used to beautify error messages re |
| invalid gotos. The outermost ctx is depth 1, with depth 0 being |
| reserved for the main body of the function. */ |
| int depth; |
| |
| /* True if this parallel directive is nested within another. */ |
| bool is_nested; |
| |
| /* True if this construct can be cancelled. */ |
| bool cancellable; |
| |
| /* For OpenACC loops, a mask of gang, worker and vector used at |
| levels below this one. */ |
| int gwv_below; |
| /* For OpenACC loops, a mask of gang, worker and vector used at |
| this level and above. For parallel and kernels clauses, a mask |
| indicating which of num_gangs/num_workers/num_vectors was used. */ |
| int gwv_this; |
| } omp_context; |
| |
| /* A structure holding the elements of: |
| for (V = N1; V cond N2; V += STEP) [...] */ |
| |
| struct omp_for_data_loop |
| { |
| tree v, n1, n2, step; |
| enum tree_code cond_code; |
| }; |
| |
| /* A structure describing the main elements of a parallel loop. */ |
| |
| struct omp_for_data |
| { |
| struct omp_for_data_loop loop; |
| tree chunk_size; |
| gomp_for *for_stmt; |
| tree pre, iter_type; |
| int collapse; |
| bool have_nowait, have_ordered; |
| enum omp_clause_schedule_kind sched_kind; |
| struct omp_for_data_loop *loops; |
| }; |
| |
| |
| static splay_tree all_contexts; |
| static int taskreg_nesting_level; |
| static int target_nesting_level; |
| static struct omp_region *root_omp_region; |
| static bitmap task_shared_vars; |
| static vec<omp_context *> taskreg_contexts; |
| |
| static void scan_omp (gimple_seq *, omp_context *); |
| static tree scan_omp_1_op (tree *, int *, void *); |
| |
| #define WALK_SUBSTMTS \ |
| case GIMPLE_BIND: \ |
| case GIMPLE_TRY: \ |
| case GIMPLE_CATCH: \ |
| case GIMPLE_EH_FILTER: \ |
| case GIMPLE_TRANSACTION: \ |
| /* The sub-statements for these should be walked. */ \ |
| *handled_ops_p = false; \ |
| break; |
| |
| /* Helper function to get the name of the array containing the partial |
| reductions for OpenACC reductions. */ |
| static const char * |
| oacc_get_reduction_array_id (tree node) |
| { |
| const char *id = IDENTIFIER_POINTER (DECL_NAME (node)); |
| int len = strlen ("OACC") + strlen (id); |
| char *temp_name = XALLOCAVEC (char, len + 1); |
| snprintf (temp_name, len + 1, "OACC%s", id); |
| return IDENTIFIER_POINTER (get_identifier (temp_name)); |
| } |
| |
| /* Determine the number of threads OpenACC threads used to determine the |
| size of the array of partial reductions. Currently, this is num_gangs |
| * vector_length. This value may be different than GOACC_GET_NUM_THREADS, |
| because it is independed of the device used. */ |
| |
| static tree |
| oacc_max_threads (omp_context *ctx) |
| { |
| tree nthreads, vector_length, gangs, clauses; |
| |
| gangs = fold_convert (sizetype, integer_one_node); |
| vector_length = gangs; |
| |
| /* The reduction clause may be nested inside a loop directive. |
| Scan for the innermost vector_length clause. */ |
| for (omp_context *oc = ctx; oc; oc = oc->outer) |
| { |
| if (gimple_code (oc->stmt) != GIMPLE_OMP_TARGET |
| || (gimple_omp_target_kind (oc->stmt) |
| != GF_OMP_TARGET_KIND_OACC_PARALLEL)) |
| continue; |
| |
| clauses = gimple_omp_target_clauses (oc->stmt); |
| |
| vector_length = find_omp_clause (clauses, OMP_CLAUSE_VECTOR_LENGTH); |
| if (vector_length) |
| vector_length = fold_convert_loc (OMP_CLAUSE_LOCATION (vector_length), |
| sizetype, |
| OMP_CLAUSE_VECTOR_LENGTH_EXPR |
| (vector_length)); |
| else |
| vector_length = fold_convert (sizetype, integer_one_node); |
| |
| gangs = find_omp_clause (clauses, OMP_CLAUSE_NUM_GANGS); |
| if (gangs) |
| gangs = fold_convert_loc (OMP_CLAUSE_LOCATION (gangs), sizetype, |
| OMP_CLAUSE_NUM_GANGS_EXPR (gangs)); |
| else |
| gangs = fold_convert (sizetype, integer_one_node); |
| |
| break; |
| } |
| |
| nthreads = fold_build2 (MULT_EXPR, sizetype, gangs, vector_length); |
| |
| return nthreads; |
| } |
| |
| /* Holds offload tables with decls. */ |
| vec<tree, va_gc> *offload_funcs, *offload_vars; |
| |
| /* Convenience function for calling scan_omp_1_op on tree operands. */ |
| |
| static inline tree |
| scan_omp_op (tree *tp, omp_context *ctx) |
| { |
| struct walk_stmt_info wi; |
| |
| memset (&wi, 0, sizeof (wi)); |
| wi.info = ctx; |
| wi.want_locations = true; |
| |
| return walk_tree (tp, scan_omp_1_op, &wi, NULL); |
| } |
| |
| static void lower_omp (gimple_seq *, omp_context *); |
| static tree lookup_decl_in_outer_ctx (tree, omp_context *); |
| static tree maybe_lookup_decl_in_outer_ctx (tree, omp_context *); |
| |
| /* Find an OMP clause of type KIND within CLAUSES. */ |
| |
| tree |
| find_omp_clause (tree clauses, enum omp_clause_code kind) |
| { |
| for (; clauses ; clauses = OMP_CLAUSE_CHAIN (clauses)) |
| if (OMP_CLAUSE_CODE (clauses) == kind) |
| return clauses; |
| |
| return NULL_TREE; |
| } |
| |
| /* Return true if CTX is for an omp parallel. */ |
| |
| static inline bool |
| is_parallel_ctx (omp_context *ctx) |
| { |
| return gimple_code (ctx->stmt) == GIMPLE_OMP_PARALLEL; |
| } |
| |
| |
| /* Return true if CTX is for an omp task. */ |
| |
| static inline bool |
| is_task_ctx (omp_context *ctx) |
| { |
| return gimple_code (ctx->stmt) == GIMPLE_OMP_TASK; |
| } |
| |
| |
| /* Return true if CTX is for an omp parallel or omp task. */ |
| |
| static inline bool |
| is_taskreg_ctx (omp_context *ctx) |
| { |
| return gimple_code (ctx->stmt) == GIMPLE_OMP_PARALLEL |
| || gimple_code (ctx->stmt) == GIMPLE_OMP_TASK; |
| } |
| |
| |
| /* Return true if REGION is a combined parallel+workshare region. */ |
| |
| static inline bool |
| is_combined_parallel (struct omp_region *region) |
| { |
| return region->is_combined_parallel; |
| } |
| |
| |
| /* Extract the header elements of parallel loop FOR_STMT and store |
| them into *FD. */ |
| |
| static void |
| extract_omp_for_data (gomp_for *for_stmt, struct omp_for_data *fd, |
| struct omp_for_data_loop *loops) |
| { |
| tree t, var, *collapse_iter, *collapse_count; |
| tree count = NULL_TREE, iter_type = long_integer_type_node; |
| struct omp_for_data_loop *loop; |
| int i; |
| struct omp_for_data_loop dummy_loop; |
| location_t loc = gimple_location (for_stmt); |
| bool simd = gimple_omp_for_kind (for_stmt) & GF_OMP_FOR_SIMD; |
| bool distribute = gimple_omp_for_kind (for_stmt) |
| == GF_OMP_FOR_KIND_DISTRIBUTE; |
| |
| fd->for_stmt = for_stmt; |
| fd->pre = NULL; |
| fd->collapse = gimple_omp_for_collapse (for_stmt); |
| if (fd->collapse > 1) |
| fd->loops = loops; |
| else |
| fd->loops = &fd->loop; |
| |
| fd->have_nowait = distribute || simd; |
| fd->have_ordered = false; |
| fd->sched_kind = OMP_CLAUSE_SCHEDULE_STATIC; |
| fd->chunk_size = NULL_TREE; |
| if (gimple_omp_for_kind (fd->for_stmt) == GF_OMP_FOR_KIND_CILKFOR) |
| fd->sched_kind = OMP_CLAUSE_SCHEDULE_CILKFOR; |
| collapse_iter = NULL; |
| collapse_count = NULL; |
| |
| for (t = gimple_omp_for_clauses (for_stmt); t ; t = OMP_CLAUSE_CHAIN (t)) |
| switch (OMP_CLAUSE_CODE (t)) |
| { |
| case OMP_CLAUSE_NOWAIT: |
| fd->have_nowait = true; |
| break; |
| case OMP_CLAUSE_ORDERED: |
| fd->have_ordered = true; |
| break; |
| case OMP_CLAUSE_SCHEDULE: |
| gcc_assert (!distribute); |
| fd->sched_kind = OMP_CLAUSE_SCHEDULE_KIND (t); |
| fd->chunk_size = OMP_CLAUSE_SCHEDULE_CHUNK_EXPR (t); |
| break; |
| case OMP_CLAUSE_DIST_SCHEDULE: |
| gcc_assert (distribute); |
| fd->chunk_size = OMP_CLAUSE_DIST_SCHEDULE_CHUNK_EXPR (t); |
| break; |
| case OMP_CLAUSE_COLLAPSE: |
| if (fd->collapse > 1) |
| { |
| collapse_iter = &OMP_CLAUSE_COLLAPSE_ITERVAR (t); |
| collapse_count = &OMP_CLAUSE_COLLAPSE_COUNT (t); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| /* FIXME: for now map schedule(auto) to schedule(static). |
| There should be analysis to determine whether all iterations |
| are approximately the same amount of work (then schedule(static) |
| is best) or if it varies (then schedule(dynamic,N) is better). */ |
| if (fd->sched_kind == OMP_CLAUSE_SCHEDULE_AUTO) |
| { |
| fd->sched_kind = OMP_CLAUSE_SCHEDULE_STATIC; |
| gcc_assert (fd->chunk_size == NULL); |
| } |
| gcc_assert (fd->collapse == 1 || collapse_iter != NULL); |
| if (fd->sched_kind == OMP_CLAUSE_SCHEDULE_RUNTIME) |
| gcc_assert (fd->chunk_size == NULL); |
| else if (fd->chunk_size == NULL) |
| { |
| /* We only need to compute a default chunk size for ordered |
| static loops and dynamic loops. */ |
| if (fd->sched_kind != OMP_CLAUSE_SCHEDULE_STATIC |
| || fd->have_ordered) |
| fd->chunk_size = (fd->sched_kind == OMP_CLAUSE_SCHEDULE_STATIC) |
| ? integer_zero_node : integer_one_node; |
| } |
| |
| for (i = 0; i < fd->collapse; i++) |
| { |
| if (fd->collapse == 1) |
| loop = &fd->loop; |
| else if (loops != NULL) |
| loop = loops + i; |
| else |
| loop = &dummy_loop; |
| |
| loop->v = gimple_omp_for_index (for_stmt, i); |
| gcc_assert (SSA_VAR_P (loop->v)); |
| gcc_assert (TREE_CODE (TREE_TYPE (loop->v)) == INTEGER_TYPE |
| || TREE_CODE (TREE_TYPE (loop->v)) == POINTER_TYPE); |
| var = TREE_CODE (loop->v) == SSA_NAME ? SSA_NAME_VAR (loop->v) : loop->v; |
| loop->n1 = gimple_omp_for_initial (for_stmt, i); |
| |
| loop->cond_code = gimple_omp_for_cond (for_stmt, i); |
| loop->n2 = gimple_omp_for_final (for_stmt, i); |
| switch (loop->cond_code) |
| { |
| case LT_EXPR: |
| case GT_EXPR: |
| break; |
| case NE_EXPR: |
| gcc_assert (gimple_omp_for_kind (for_stmt) |
| == GF_OMP_FOR_KIND_CILKSIMD |
| || (gimple_omp_for_kind (for_stmt) |
| == GF_OMP_FOR_KIND_CILKFOR)); |
| break; |
| case LE_EXPR: |
| if (POINTER_TYPE_P (TREE_TYPE (loop->n2))) |
| loop->n2 = fold_build_pointer_plus_hwi_loc (loc, loop->n2, 1); |
| else |
| loop->n2 = fold_build2_loc (loc, |
| PLUS_EXPR, TREE_TYPE (loop->n2), loop->n2, |
| build_int_cst (TREE_TYPE (loop->n2), 1)); |
| loop->cond_code = LT_EXPR; |
| break; |
| case GE_EXPR: |
| if (POINTER_TYPE_P (TREE_TYPE (loop->n2))) |
| loop->n2 = fold_build_pointer_plus_hwi_loc (loc, loop->n2, -1); |
| else |
| loop->n2 = fold_build2_loc (loc, |
| MINUS_EXPR, TREE_TYPE (loop->n2), loop->n2, |
| build_int_cst (TREE_TYPE (loop->n2), 1)); |
| loop->cond_code = GT_EXPR; |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| t = gimple_omp_for_incr (for_stmt, i); |
| gcc_assert (TREE_OPERAND (t, 0) == var); |
| switch (TREE_CODE (t)) |
| { |
| case PLUS_EXPR: |
| loop->step = TREE_OPERAND (t, 1); |
| break; |
| case POINTER_PLUS_EXPR: |
| loop->step = fold_convert (ssizetype, TREE_OPERAND (t, 1)); |
| break; |
| case MINUS_EXPR: |
| loop->step = TREE_OPERAND (t, 1); |
| loop->step = fold_build1_loc (loc, |
| NEGATE_EXPR, TREE_TYPE (loop->step), |
| loop->step); |
| break; |
| default: |
| gcc_unreachable (); |
| } |
| |
| if (simd |
| || (fd->sched_kind == OMP_CLAUSE_SCHEDULE_STATIC |
| && !fd->have_ordered)) |
| { |
| if (fd->collapse == 1) |
| iter_type = TREE_TYPE (loop->v); |
| else if (i == 0 |
| || TYPE_PRECISION (iter_type) |
| < TYPE_PRECISION (TREE_TYPE (loop->v))) |
| iter_type |
| = build_nonstandard_integer_type |
| (TYPE_PRECISION (TREE_TYPE (loop->v)), 1); |
| } |
| else if (iter_type != long_long_unsigned_type_node) |
| { |
| if (POINTER_TYPE_P (TREE_TYPE (loop->v))) |
| iter_type = long_long_unsigned_type_node; |
| else if (TYPE_UNSIGNED (TREE_TYPE (loop->v)) |
| && TYPE_PRECISION (TREE_TYPE (loop->v)) |
| >= TYPE_PRECISION (iter_type)) |
| { |
| tree n; |
| |
| if (loop->cond_code == LT_EXPR) |
| n = fold_build2_loc (loc, |
| PLUS_EXPR, TREE_TYPE (loop->v), |
| loop->n2, loop->step); |
| else |
| n = loop->n1; |
| if (TREE_CODE (n) != INTEGER_CST |
| || tree_int_cst_lt (TYPE_MAX_VALUE (iter_type), n)) |
| iter_type = long_long_unsigned_type_node; |
| } |
| else if (TYPE_PRECISION (TREE_TYPE (loop->v)) |
| > TYPE_PRECISION (iter_type)) |
| { |
| tree n1, n2; |
| |
| if (loop->cond_code == LT_EXPR) |
| { |
| n1 = loop->n1; |
| n2 = fold_build2_loc (loc, |
| PLUS_EXPR, TREE_TYPE (loop->v), |
| loop->n2, loop->step); |
| } |
| else |
| { |
| n1 = fold_build2_loc (loc, |
| MINUS_EXPR, TREE_TYPE (loop->v), |
| loop->n2, loop->step); |
| n2 = loop->n1; |
| } |
| if (TREE_CODE (n1) != INTEGER_CST |
| || TREE_CODE (n2) != INTEGER_CST |
| || !tree_int_cst_lt (TYPE_MIN_VALUE (iter_type), n1) |
| || !tree_int_cst_lt (n2, TYPE_MAX_VALUE (iter_type))) |
| iter_type = long_long_unsigned_type_node; |
| } |
| } |
| |
| if (collapse_count && *collapse_count == NULL) |
| { |
| t = fold_binary (loop->cond_code, boolean_type_node, |
| fold_convert (TREE_TYPE (loop->v), loop->n1), |
| fold_convert (TREE_TYPE (loop->v), loop->n2)); |
| if (t && integer_zerop (t)) |
| count = build_zero_cst (long_long_unsigned_type_node); |
| else if ((i == 0 || count != NULL_TREE) |
| && TREE_CODE (TREE_TYPE (loop->v)) == INTEGER_TYPE |
| && TREE_CONSTANT (loop->n1) |
| && TREE_CONSTANT (loop->n2) |
| && TREE_CODE (loop->step) == INTEGER_CST) |
| { |
| tree itype = TREE_TYPE (loop->v); |
| |
| if (POINTER_TYPE_P (itype)) |
| itype = signed_type_for (itype); |
| t = build_int_cst (itype, (loop->cond_code == LT_EXPR ? -1 : 1)); |
| t = fold_build2_loc (loc, |
| PLUS_EXPR, itype, |
| fold_convert_loc (loc, itype, loop->step), t); |
| t = fold_build2_loc (loc, PLUS_EXPR, itype, t, |
| fold_convert_loc (loc, itype, loop->n2)); |
| t = fold_build2_loc (loc, MINUS_EXPR, itype, t, |
| fold_convert_loc (loc, itype, loop->n1)); |
| if (TYPE_UNSIGNED (itype) && loop->cond_code == GT_EXPR) |
| t = fold_build2_loc (loc, TRUNC_DIV_EXPR, itype, |
| fold_build1_loc (loc, NEGATE_EXPR, itype, t), |
| fold_build1_loc (loc, NEGATE_EXPR, itype, |
| fold_convert_loc (loc, itype, |
| loop->step))); |
| else |
| t = fold_build2_loc (loc, TRUNC_DIV_EXPR, itype, t, |
| fold_convert_loc (loc, itype, loop->step)); |
| t = fold_convert_loc (loc, long_long_unsigned_type_node, t); |
| if (count != NULL_TREE) |
| count = fold_build2_loc (loc, |
| MULT_EXPR, long_long_unsigned_type_node, |
| count, t); |
| else |
| count = t; |
| if (TREE_CODE (count) != INTEGER_CST) |
| count = NULL_TREE; |
| } |
| else if (count && !integer_zerop (count)) |
| count = NULL_TREE; |
| } |
| } |
| |
| if (count |
| && !simd |
| && (fd->sched_kind != OMP_CLAUSE_SCHEDULE_STATIC |
| || fd->have_ordered)) |
| { |
| if (!tree_int_cst_lt (count, TYPE_MAX_VALUE (long_integer_type_node))) |
| iter_type = long_long_unsigned_type_node; |
| else |
| iter_type = long_integer_type_node; |
| } |
| else if (collapse_iter && *collapse_iter != NULL) |
| iter_type = TREE_TYPE (*collapse_iter); |
| fd->iter_type = iter_type; |
| if (collapse_iter && *collapse_iter == NULL) |
| *collapse_iter = create_tmp_var (iter_type, ".iter"); |
| if (collapse_count && *collapse_count == NULL) |
| { |
| if (count) |
| *collapse_count = fold_convert_loc (loc, iter_type, count); |
| else |
| *collapse_count = create_tmp_var (iter_type, ".count"); |
| } |
| |
| if (fd->collapse > 1) |
| { |
| fd->loop.v = *collapse_iter; |
| fd->loop.n1 = build_int_cst (TREE_TYPE (fd->loop.v), 0); |
| fd->loop.n2 = *collapse_count; |
| fd->loop.step = build_int_cst (TREE_TYPE (fd->loop.v), 1); |
| fd->loop.cond_code = LT_EXPR; |
| } |
| |
| /* For OpenACC loops, force a chunk size of one, as this avoids the default |
| scheduling where several subsequent iterations are being executed by the |
| same thread. */ |
| if (gimple_omp_for_kind (for_stmt) == GF_OMP_FOR_KIND_OACC_LOOP) |
| { |
| gcc_assert (fd->chunk_size == NULL_TREE); |
| fd->chunk_size = build_int_cst (TREE_TYPE (fd->loop.v), 1); |
| } |
| } |
| |
| |
| /* Given two blocks PAR_ENTRY_BB and WS_ENTRY_BB such that WS_ENTRY_BB |
| is the immediate dominator of PAR_ENTRY_BB, return true if there |
| are no data dependencies that would prevent expanding the parallel |
| directive at PAR_ENTRY_BB as a combined parallel+workshare region. |
| |
| When expanding a combined parallel+workshare region, the call to |
| the child function may need additional arguments in the case of |
| GIMPLE_OMP_FOR regions. In some cases, these arguments are |
| computed out of variables passed in from the parent to the child |
| via 'struct .omp_data_s'. For instance: |
| |
| #pragma omp parallel for schedule (guided, i * 4) |
| for (j ...) |
| |
| Is lowered into: |
| |
| # BLOCK 2 (PAR_ENTRY_BB) |
| .omp_data_o.i = i; |
| #pragma omp parallel [child fn: bar.omp_fn.0 ( ..., D.1598) |
| |
| # BLOCK 3 (WS_ENTRY_BB) |
| .omp_data_i = &.omp_data_o; |
| D.1667 = .omp_data_i->i; |
| D.1598 = D.1667 * 4; |
| #pragma omp for schedule (guided, D.1598) |
| |
| When we outline the parallel region, the call to the child function |
| 'bar.omp_fn.0' will need the value D.1598 in its argument list, but |
| that value is computed *after* the call site. So, in principle we |
| cannot do the transformation. |
| |
| To see whether the code in WS_ENTRY_BB blocks the combined |
| parallel+workshare call, we collect all the variables used in the |
| GIMPLE_OMP_FOR header check whether they appear on the LHS of any |
| statement in WS_ENTRY_BB. If so, then we cannot emit the combined |
| call. |
| |
| FIXME. If we had the SSA form built at this point, we could merely |
| hoist the code in block 3 into block 2 and be done with it. But at |
| this point we don't have dataflow information and though we could |
| hack something up here, it is really not worth the aggravation. */ |
| |
| static bool |
| workshare_safe_to_combine_p (basic_block ws_entry_bb) |
| { |
| struct omp_for_data fd; |
| gimple ws_stmt = last_stmt (ws_entry_bb); |
| |
| if (gimple_code (ws_stmt) == GIMPLE_OMP_SECTIONS) |
| return true; |
| |
| gcc_assert (gimple_code (ws_stmt) == GIMPLE_OMP_FOR); |
| |
| extract_omp_for_data (as_a <gomp_for *> (ws_stmt), &fd, NULL); |
| |
| if (fd.collapse > 1 && TREE_CODE (fd.loop.n2) != INTEGER_CST) |
| return false; |
| if (fd.iter_type != long_integer_type_node) |
| return false; |
| |
| /* FIXME. We give up too easily here. If any of these arguments |
| are not constants, they will likely involve variables that have |
| been mapped into fields of .omp_data_s for sharing with the child |
| function. With appropriate data flow, it would be possible to |
| see through this. */ |
| if (!is_gimple_min_invariant (fd.loop.n1) |
| || !is_gimple_min_invariant (fd.loop.n2) |
| || !is_gimple_min_invariant (fd.loop.step) |
| || (fd.chunk_size && !is_gimple_min_invariant (fd.chunk_size))) |
| return false; |
| |
| return true; |
| } |
| |
| |
| /* Collect additional arguments needed to emit a combined |
| parallel+workshare call. WS_STMT is the workshare directive being |
| expanded. */ |
| |
| static vec<tree, va_gc> * |
| get_ws_args_for (gimple par_stmt, gimple ws_stmt) |
| { |
| tree t; |
| location_t loc = gimple_location (ws_stmt); |
| vec<tree, va_gc> *ws_args; |
| |
| if (gomp_for *for_stmt = dyn_cast <gomp_for *> (ws_stmt)) |
| { |
| struct omp_for_data fd; |
| tree n1, n2; |
| |
| extract_omp_for_data (for_stmt, &fd, NULL); |
| n1 = fd.loop.n1; |
| n2 = fd.loop.n2; |
| |
| if (gimple_omp_for_combined_into_p (for_stmt)) |
| { |
| tree innerc |
| = find_omp_clause (gimple_omp_parallel_clauses (par_stmt), |
| OMP_CLAUSE__LOOPTEMP_); |
| gcc_assert (innerc); |
| n1 = OMP_CLAUSE_DECL (innerc); |
| innerc = find_omp_clause (OMP_CLAUSE_CHAIN (innerc), |
| OMP_CLAUSE__LOOPTEMP_); |
| gcc_assert (innerc); |
| n2 = OMP_CLAUSE_DECL (innerc); |
| } |
| |
| vec_alloc (ws_args, 3 + (fd.chunk_size != 0)); |
| |
| t = fold_convert_loc (loc, long_integer_type_node, n1); |
| ws_args->quick_push (t); |
| |
| t = fold_convert_loc (loc, long_integer_type_node, n2); |
| ws_args->quick_push (t); |
| |
| t = fold_convert_loc (loc, long_integer_type_node, fd.loop.step); |
| ws_args->quick_push (t); |
| |
| if (fd.chunk_size) |
| { |
| t = fold_convert_loc (loc, long_integer_type_node, fd.chunk_size); |
| ws_args->quick_push (t); |
| } |
| |
| return ws_args; |
| } |
| else if (gimple_code (ws_stmt) == GIMPLE_OMP_SECTIONS) |
| { |
| /* Number of sections is equal to the number of edges from the |
| GIMPLE_OMP_SECTIONS_SWITCH statement, except for the one to |
| the exit of the sections region. */ |
| basic_block bb = single_succ (gimple_bb (ws_stmt)); |
| t = build_int_cst (unsigned_type_node, EDGE_COUNT (bb->succs) - 1); |
| vec_alloc (ws_args, 1); |
| ws_args->quick_push (t); |
| return ws_args; |
| } |
| |
| gcc_unreachable (); |
| } |
| |
| |
| /* Discover whether REGION is a combined parallel+workshare region. */ |
| |
| static void |
| determine_parallel_type (struct omp_region *region) |
| { |
| basic_block par_entry_bb, par_exit_bb; |
| basic_block ws_entry_bb, ws_exit_bb; |
| |
| if (region == NULL || region->inner == NULL |
| || region->exit == NULL || region->inner->exit == NULL |
| || region->inner->cont == NULL) |
| return; |
| |
| /* We only support parallel+for and parallel+sections. */ |
| if (region->type != GIMPLE_OMP_PARALLEL |
| || (region->inner->type != GIMPLE_OMP_FOR |
| && region->inner->type != GIMPLE_OMP_SECTIONS)) |
| return; |
| |
| /* Check for perfect nesting PAR_ENTRY_BB -> WS_ENTRY_BB and |
| WS_EXIT_BB -> PAR_EXIT_BB. */ |
| par_entry_bb = region->entry; |
| par_exit_bb = region->exit; |
| ws_entry_bb = region->inner->entry; |
| ws_exit_bb = region->inner->exit; |
| |
| if (single_succ (par_entry_bb) == ws_entry_bb |
| && single_succ (ws_exit_bb) == par_exit_bb |
| && workshare_safe_to_combine_p (ws_entry_bb) |
| && (gimple_omp_parallel_combined_p (last_stmt (par_entry_bb)) |
| || (last_and_only_stmt (ws_entry_bb) |
| && last_and_only_stmt (par_exit_bb)))) |
| { |
| gimple par_stmt = last_stmt (par_entry_bb); |
| gimple ws_stmt = last_stmt (ws_entry_bb); |
| |
| if (region->inner->type == GIMPLE_OMP_FOR) |
| { |
| /* If this is a combined parallel loop, we need to determine |
| whether or not to use the combined library calls. There |
| are two cases where we do not apply the transformation: |
| static loops and any kind of ordered loop. In the first |
| case, we already open code the loop so there is no need |
| to do anything else. In the latter case, the combined |
| parallel loop call would still need extra synchronization |
| to implement ordered semantics, so there would not be any |
| gain in using the combined call. */ |
| tree clauses = gimple_omp_for_clauses (ws_stmt); |
| tree c = find_omp_clause (clauses, OMP_CLAUSE_SCHEDULE); |
| if (c == NULL |
| || OMP_CLAUSE_SCHEDULE_KIND (c) == OMP_CLAUSE_SCHEDULE_STATIC |
| || find_omp_clause (clauses, OMP_CLAUSE_ORDERED)) |
| { |
| region->is_combined_parallel = false; |
| region->inner->is_combined_parallel = false; |
| return; |
| } |
| } |
| |
| region->is_combined_parallel = true; |
| region->inner->is_combined_parallel = true; |
| region->ws_args = get_ws_args_for (par_stmt, ws_stmt); |
| } |
| } |
| |
| |
| /* Return true if EXPR is variable sized. */ |
| |
| static inline bool |
| is_variable_sized (const_tree expr) |
| { |
| return !TREE_CONSTANT (TYPE_SIZE_UNIT (TREE_TYPE (expr))); |
| } |
| |
| /* Return true if DECL is a reference type. */ |
| |
| static inline bool |
| is_reference (tree decl) |
| { |
| return lang_hooks.decls.omp_privatize_by_reference (decl); |
| } |
| |
| /* Return the type of a decl. If the decl is reference type, |
| return its base type. */ |
| static inline tree |
| get_base_type (tree decl) |
| { |
| tree type = TREE_TYPE (decl); |
| if (is_reference (decl)) |
| type = TREE_TYPE (type); |
| return type; |
| } |
| |
| /* Lookup variables. The "maybe" form |
| allows for the variable form to not have been entered, otherwise we |
| assert that the variable must have been entered. */ |
| |
| static inline tree |
| lookup_decl (tree var, omp_context *ctx) |
| { |
| tree *n = ctx->cb.decl_map->get (var); |
| return *n; |
| } |
| |
| static inline tree |
| maybe_lookup_decl (const_tree var, omp_context *ctx) |
| { |
| tree *n = ctx->cb.decl_map->get (const_cast<tree> (var)); |
| return n ? *n : NULL_TREE; |
| } |
| |
| static inline tree |
| lookup_field (tree var, omp_context *ctx) |
| { |
| splay_tree_node n; |
| n = splay_tree_lookup (ctx->field_map, (splay_tree_key) var); |
| return (tree) n->value; |
| } |
| |
| static inline tree |
| lookup_sfield (tree var, omp_context *ctx) |
| { |
| splay_tree_node n; |
| n = splay_tree_lookup (ctx->sfield_map |
| ? ctx->sfield_map : ctx->field_map, |
| (splay_tree_key) var); |
| return (tree) n->value; |
| } |
| |
| static inline tree |
| maybe_lookup_field (tree var, omp_context *ctx) |
| { |
| splay_tree_node n; |
| n = splay_tree_lookup (ctx->field_map, (splay_tree_key) var); |
| return n ? (tree) n->value : NULL_TREE; |
| } |
| |
| static inline tree |
| lookup_oacc_reduction (const char *id, omp_context *ctx) |
| { |
| splay_tree_node n; |
| n = splay_tree_lookup (ctx->reduction_map, (splay_tree_key) id); |
| return (tree) n->value; |
| } |
| |
| static inline tree |
| maybe_lookup_oacc_reduction (tree var, omp_context *ctx) |
| { |
| splay_tree_node n = NULL; |
| if (ctx->reduction_map) |
| n = splay_tree_lookup (ctx->reduction_map, (splay_tree_key) var); |
| return n ? (tree) n->value : NULL_TREE; |
| } |
| |
| /* Return true if DECL should be copied by pointer. SHARED_CTX is |
| the parallel context if DECL is to be shared. */ |
| |
| static bool |
| use_pointer_for_field (tree decl, omp_context *shared_ctx) |
| { |
| if (AGGREGATE_TYPE_P (TREE_TYPE (decl))) |
| return true; |
| |
| /* We can only use copy-in/copy-out semantics for shared variables |
| when we know the value is not accessible from an outer scope. */ |
| if (shared_ctx) |
| { |
| gcc_assert (!is_gimple_omp_oacc (shared_ctx->stmt)); |
| |
| /* ??? Trivially accessible from anywhere. But why would we even |
| be passing an address in this case? Should we simply assert |
| this to be false, or should we have a cleanup pass that removes |
| these from the list of mappings? */ |
| if (TREE_STATIC (decl) || DECL_EXTERNAL (decl)) |
| return true; |
| |
| /* For variables with DECL_HAS_VALUE_EXPR_P set, we cannot tell |
| without analyzing the expression whether or not its location |
| is accessible to anyone else. In the case of nested parallel |
| regions it certainly may be. */ |
| if (TREE_CODE (decl) != RESULT_DECL && DECL_HAS_VALUE_EXPR_P (decl)) |
| return true; |
| |
| /* Do not use copy-in/copy-out for variables that have their |
| address taken. */ |
| if (TREE_ADDRESSABLE (decl)) |
| return true; |
| |
| /* lower_send_shared_vars only uses copy-in, but not copy-out |
| for these. */ |
| if (TREE_READONLY (decl) |
| || ((TREE_CODE (decl) == RESULT_DECL |
| || TREE_CODE (decl) == PARM_DECL) |
| && DECL_BY_REFERENCE (decl))) |
| return false; |
| |
| /* Disallow copy-in/out in nested parallel if |
| decl is shared in outer parallel, otherwise |
| each thread could store the shared variable |
| in its own copy-in location, making the |
| variable no longer really shared. */ |
| if (shared_ctx->is_nested) |
| { |
| omp_context *up; |
| |
| for (up = shared_ctx->outer; up; up = up->outer) |
| if (is_taskreg_ctx (up) && maybe_lookup_decl (decl, up)) |
| break; |
| |
| if (up) |
| { |
| tree c; |
| |
| for (c = gimple_omp_taskreg_clauses (up->stmt); |
| c; c = OMP_CLAUSE_CHAIN (c)) |
| if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_SHARED |
| && OMP_CLAUSE_DECL (c) == decl) |
| break; |
| |
| if (c) |
| goto maybe_mark_addressable_and_ret; |
| } |
| } |
| |
| /* For tasks avoid using copy-in/out. As tasks can be |
| deferred or executed in different thread, when GOMP_task |
| returns, the task hasn't necessarily terminated. */ |
| if (is_task_ctx (shared_ctx)) |
| { |
| tree outer; |
| maybe_mark_addressable_and_ret: |
| outer = maybe_lookup_decl_in_outer_ctx (decl, shared_ctx); |
| if (is_gimple_reg (outer)) |
| { |
| /* Taking address of OUTER in lower_send_shared_vars |
| might need regimplification of everything that uses the |
| variable. */ |
| if (!task_shared_vars) |
| task_shared_vars = BITMAP_ALLOC (NULL); |
| bitmap_set_bit (task_shared_vars, DECL_UID (outer)); |
| TREE_ADDRESSABLE (outer) = 1; |
| } |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /* Construct a new automatic decl similar to VAR. */ |
| |
| static tree |
| omp_copy_decl_2 (tree var, tree name, tree type, omp_context *ctx) |
| { |
| tree copy = copy_var_decl (var, name, type); |
| |
| DECL_CONTEXT (copy) = current_function_decl; |
| DECL_CHAIN (copy) = ctx->block_vars; |
| ctx->block_vars = copy; |
| |
| return copy; |
| } |
| |
| static tree |
| omp_copy_decl_1 (tree var, omp_context *ctx) |
| { |
| return omp_copy_decl_2 (var, DECL_NAME (var), TREE_TYPE (var), ctx); |
| } |
| |
| /* Build COMPONENT_REF and set TREE_THIS_VOLATILE and TREE_READONLY on it |
| as appropriate. */ |
| static tree |
| omp_build_component_ref (tree obj, tree field) |
| { |
| tree ret = build3 (COMPONENT_REF, TREE_TYPE (field), obj, field, NULL); |
| if (TREE_THIS_VOLATILE (field)) |
| TREE_THIS_VOLATILE (ret) |= 1; |
| if (TREE_READONLY (field)) |
| TREE_READONLY (ret) |= 1; |
| return ret; |
| } |
| |
| /* Build tree nodes to access the field for VAR on the receiver side. */ |
| |
| static tree |
| build_receiver_ref (tree var, bool by_ref, omp_context *ctx) |
| { |
| tree x, field = lookup_field (var, ctx); |
| |
| /* If the receiver record type was remapped in the child function, |
| remap the field into the new record type. */ |
| x = maybe_lookup_field (field, ctx); |
| if (x != NULL) |
| field = x; |
| |
| x = build_simple_mem_ref (ctx->receiver_decl); |
| x = omp_build_component_ref (x, field); |
| if (by_ref) |
| x = build_simple_mem_ref (x); |
| |
| return x; |
| } |
| |
| /* Build tree nodes to access VAR in the scope outer to CTX. In the case |
| of a parallel, this is a component reference; for workshare constructs |
| this is some variable. */ |
| |
| static tree |
| build_outer_var_ref (tree var, omp_context *ctx, |
| enum omp_clause_code code = OMP_CLAUSE_ERROR) |
| { |
| tree x; |
| |
| if (is_global_var (maybe_lookup_decl_in_outer_ctx (var, ctx))) |
| x = var; |
| else if (is_variable_sized (var)) |
| { |
| x = TREE_OPERAND (DECL_VALUE_EXPR (var), 0); |
| x = build_outer_var_ref (x, ctx, code); |
| x = build_simple_mem_ref (x); |
| } |
| else if (is_taskreg_ctx (ctx)) |
| { |
| bool by_ref = use_pointer_for_field (var, NULL); |
| x = build_receiver_ref (var, by_ref, ctx); |
| } |
| else if ((gimple_code (ctx->stmt) == GIMPLE_OMP_FOR |
| && gimple_omp_for_kind (ctx->stmt) & GF_OMP_FOR_SIMD) |
| || (code == OMP_CLAUSE_PRIVATE |
| && (gimple_code (ctx->stmt) == GIMPLE_OMP_FOR |
| || gimple_code (ctx->stmt) == GIMPLE_OMP_SECTIONS |
| || gimple_code (ctx->stmt) == GIMPLE_OMP_SINGLE))) |
| { |
| /* #pragma omp simd isn't a worksharing construct, and can reference |
| even private vars in its linear etc. clauses. |
| Similarly for OMP_CLAUSE_PRIVATE with outer ref, that can refer |
| to private vars in all worksharing constructs. */ |
| x = NULL_TREE; |
| if (ctx->outer && is_taskreg_ctx (ctx)) |
| x = lookup_decl (var, ctx->outer); |
| else if (ctx->outer) |
| x = maybe_lookup_decl_in_outer_ctx (var, ctx); |
| if (x == NULL_TREE) |
| x = var; |
| } |
| else if (ctx->outer) |
| x = lookup_decl (var, ctx->outer); |
| else if (is_reference (var)) |
| /* This can happen with orphaned constructs. If var is reference, it is |
| possible it is shared and as such valid. */ |
| x = var; |
| else |
| gcc_unreachable (); |
| |
| if (is_reference (var)) |
| x = build_simple_mem_ref (x); |
| |
| return x; |
| } |
| |
| /* Build tree nodes to access the field for VAR on the sender side. */ |
| |
| static tree |
| build_sender_ref (tree var, omp_context *ctx) |
| { |
| tree field = lookup_sfield (var, ctx); |
| return omp_build_component_ref (ctx->sender_decl, field); |
| } |
| |
| /* Add a new field for VAR inside the structure CTX->SENDER_DECL. */ |
| |
| static void |
| install_var_field (tree var, bool by_ref, int mask, omp_context *ctx) |
| { |
| tree field, type, sfield = NULL_TREE; |
| |
| gcc_assert ((mask & 1) == 0 |
| || !splay_tree_lookup (ctx->field_map, (splay_tree_key) var)); |
| gcc_assert ((mask & 2) == 0 || !ctx->sfield_map |
| || !splay_tree_lookup (ctx->sfield_map, (splay_tree_key) var)); |
| gcc_assert ((mask & 3) == 3 |
| || !is_gimple_omp_oacc (ctx->stmt)); |
| |
| type = TREE_TYPE (var); |
| if (mask & 4) |
| { |
| gcc_assert (TREE_CODE (type) == ARRAY_TYPE); |
| type = build_pointer_type (build_pointer_type (type)); |
| } |
| else if (by_ref) |
| type = build_pointer_type (type); |
| else if ((mask & 3) == 1 && is_reference (var)) |
| type = TREE_TYPE (type); |
| |
| field = build_decl (DECL_SOURCE_LOCATION (var), |
| FIELD_DECL, DECL_NAME (var), type); |
| |
| /* Remember what variable this field was created for. This does have a |
| side effect of making dwarf2out ignore this member, so for helpful |
| debugging we clear it later in delete_omp_context. */ |
| DECL_ABSTRACT_ORIGIN (field) = var; |
| if (type == TREE_TYPE (var)) |
| { |
| DECL_ALIGN (field) = DECL_ALIGN (var); |
| DECL_USER_ALIGN (field) = DECL_USER_ALIGN (var); |
| TREE_THIS_VOLATILE (field) = TREE_THIS_VOLATILE (var); |
| } |
| else |
| DECL_ALIGN (field) = TYPE_ALIGN (type); |
| |
| if ((mask & 3) == 3) |
| { |
| insert_field_into_struct (ctx->record_type, field); |
| if (ctx->srecord_type) |
| { |
| sfield = build_decl (DECL_SOURCE_LOCATION (var), |
| FIELD_DECL, DECL_NAME (var), type); |
| DECL_ABSTRACT_ORIGIN (sfield) = var; |
| DECL_ALIGN (sfield) = DECL_ALIGN (field); |
| DECL_USER_ALIGN (sfield) = DECL_USER_ALIGN (field); |
| TREE_THIS_VOLATILE (sfield) = TREE_THIS_VOLATILE (field); |
| insert_field_into_struct (ctx->srecord_type, sfield); |
| } |
| } |
| else |
| { |
| if (ctx->srecord_type == NULL_TREE) |
| { |
| tree t; |
| |
| ctx->srecord_type = lang_hooks.types.make_type (RECORD_TYPE); |
| ctx->sfield_map = splay_tree_new (splay_tree_compare_pointers, 0, 0); |
| for (t = TYPE_FIELDS (ctx->record_type); t ; t = TREE_CHAIN (t)) |
| { |
| sfield = build_decl (DECL_SOURCE_LOCATION (var), |
| FIELD_DECL, DECL_NAME (t), TREE_TYPE (t)); |
| DECL_ABSTRACT_ORIGIN (sfield) = DECL_ABSTRACT_ORIGIN (t); |
| insert_field_into_struct (ctx->srecord_type, sfield); |
| splay_tree_insert (ctx->sfield_map, |
| (splay_tree_key) DECL_ABSTRACT_ORIGIN (t), |
| (splay_tree_value) sfield); |
| } |
| } |
| sfield = field; |
| insert_field_into_struct ((mask & 1) ? ctx->record_type |
| : ctx->srecord_type, field); |
| } |
| |
| if (mask & 1) |
| splay_tree_insert (ctx->field_map, (splay_tree_key) var, |
| (splay_tree_value) field); |
| if ((mask & 2) && ctx->sfield_map) |
| splay_tree_insert (ctx->sfield_map, (splay_tree_key) var, |
| (splay_tree_value) sfield); |
| } |
| |
| static tree |
| install_var_local (tree var, omp_context *ctx) |
| { |
| tree new_var = omp_copy_decl_1 (var, ctx); |
| insert_decl_map (&ctx->cb, var, new_var); |
| return new_var; |
| } |
| |
| /* Adjust the replacement for DECL in CTX for the new context. This means |
| copying the DECL_VALUE_EXPR, and fixing up the type. */ |
| |
| static void |
| fixup_remapped_decl (tree decl, omp_context *ctx, bool private_debug) |
| { |
| tree new_decl, size; |
| |
| new_decl = lookup_decl (decl, ctx); |
| |
| TREE_TYPE (new_decl) = remap_type (TREE_TYPE (decl), &ctx->cb); |
| |
| if ((!TREE_CONSTANT (DECL_SIZE (new_decl)) || private_debug) |
| && DECL_HAS_VALUE_EXPR_P (decl)) |
| { |
| tree ve = DECL_VALUE_EXPR (decl); |
| walk_tree (&ve, copy_tree_body_r, &ctx->cb, NULL); |
| SET_DECL_VALUE_EXPR (new_decl, ve); |
| DECL_HAS_VALUE_EXPR_P (new_decl) = 1; |
| } |
| |
| if (!TREE_CONSTANT (DECL_SIZE (new_decl))) |
| { |
| size = remap_decl (DECL_SIZE (decl), &ctx->cb); |
| if (size == error_mark_node) |
| size = TYPE_SIZE (TREE_TYPE (new_decl)); |
| DECL_SIZE (new_decl) = size; |
| |
| size = remap_decl (DECL_SIZE_UNIT (decl), &ctx->cb); |
| if (size == error_mark_node) |
| size = TYPE_SIZE_UNIT (TREE_TYPE (new_decl)); |
| DECL_SIZE_UNIT (new_decl) = size; |
| } |
| } |
| |
| /* The callback for remap_decl. Search all containing contexts for a |
| mapping of the variable; this avoids having to duplicate the splay |
| tree ahead of time. We know a mapping doesn't already exist in the |
| given context. Create new mappings to implement default semantics. */ |
| |
| static tree |
| omp_copy_decl (tree var, copy_body_data *cb) |
| { |
| omp_context *ctx = (omp_context *) cb; |
| tree new_var; |
| |
| if (TREE_CODE (var) == LABEL_DECL) |
| { |
| if (FORCED_LABEL (var) || DECL_NONLOCAL (var)) |
| return var; |
| new_var = create_artificial_label (DECL_SOURCE_LOCATION (var)); |
| DECL_CONTEXT (new_var) = current_function_decl; |
| insert_decl_map (&ctx->cb, var, new_var); |
| return new_var; |
| } |
| |
| while (!is_taskreg_ctx (ctx)) |
| { |
| ctx = ctx->outer; |
| if (ctx == NULL) |
| return var; |
| new_var = maybe_lookup_decl (var, ctx); |
| if (new_var) |
| return new_var; |
| } |
| |
| if (is_global_var (var) || decl_function_context (var) != ctx->cb.src_fn) |
| return var; |
| |
| return error_mark_node; |
| } |
| |
| |
| /* Debugging dumps for parallel regions. */ |
| void dump_omp_region (FILE *, struct omp_region *, int); |
| void debug_omp_region (struct omp_region *); |
| void debug_all_omp_regions (void); |
| |
| /* Dump the parallel region tree rooted at REGION. */ |
| |
| void |
| dump_omp_region (FILE *file, struct omp_region *region, int indent) |
| { |
| fprintf (file, "%*sbb %d: %s\n", indent, "", region->entry->index, |
| gimple_code_name[region->type]); |
| |
| if (region->inner) |
| dump_omp_region (file, region->inner, indent + 4); |
| |
| if (region->cont) |
| { |
| fprintf (file, "%*sbb %d: GIMPLE_OMP_CONTINUE\n", indent, "", |
| region->cont->index); |
| } |
| |
| if (region->exit) |
| fprintf (file, "%*sbb %d: GIMPLE_OMP_RETURN\n", indent, "", |
| region->exit->index); |
| else |
| fprintf (file, "%*s[no exit marker]\n", indent, ""); |
| |
| if (region->next) |
| dump_omp_region (file, region->next, indent); |
| } |
| |
| DEBUG_FUNCTION void |
| debug_omp_region (struct omp_region *region) |
| { |
| dump_omp_region (stderr, region, 0); |
| } |
| |
| DEBUG_FUNCTION void |
| debug_all_omp_regions (void) |
| { |
| dump_omp_region (stderr, root_omp_region, 0); |
| } |
| |
| |
| /* Create a new parallel region starting at STMT inside region PARENT. */ |
| |
| static struct omp_region * |
| new_omp_region (basic_block bb, enum gimple_code type, |
| struct omp_region *parent) |
| { |
| struct omp_region *region = XCNEW (struct omp_region); |
| |
| region->outer = parent; |
| region->entry = bb; |
| region->type = type; |
| |
| if (parent) |
| { |
| /* This is a nested region. Add it to the list of inner |
| regions in PARENT. */ |
| region->next = parent->inner; |
| parent->inner = region; |
| } |
| else |
| { |
| /* This is a toplevel region. Add it to the list of toplevel |
| regions in ROOT_OMP_REGION. */ |
| region->next = root_omp_region; |
| root_omp_region = region; |
| } |
| |
| return region; |
| } |
| |
| /* Release the memory associated with the region tree rooted at REGION. */ |
| |
| static void |
| free_omp_region_1 (struct omp_region *region) |
| { |
| struct omp_region *i, *n; |
| |
| for (i = region->inner; i ; i = n) |
| { |
| n = i->next; |
| free_omp_region_1 (i); |
| } |
| |
| free (region); |
| } |
| |
| /* Release the memory for the entire omp region tree. */ |
| |
| void |
| free_omp_regions (void) |
| { |
| struct omp_region *r, *n; |
| for (r = root_omp_region; r ; r = n) |
| { |
| n = r->next; |
| free_omp_region_1 (r); |
| } |
| root_omp_region = NULL; |
| } |
| |
| |
| /* Create a new context, with OUTER_CTX being the surrounding context. */ |
| |
| static omp_context * |
| new_omp_context (gimple stmt, omp_context *outer_ctx) |
| { |
| omp_context *ctx = XCNEW (omp_context); |
| |
| splay_tree_insert (all_contexts, (splay_tree_key) stmt, |
| (splay_tree_value) ctx); |
| ctx->stmt = stmt; |
| |
| if (outer_ctx) |
| { |
| ctx->outer = outer_ctx; |
| ctx->cb = outer_ctx->cb; |
| ctx->cb.block = NULL; |
| ctx->depth = outer_ctx->depth + 1; |
| ctx->reduction_map = outer_ctx->reduction_map; |
| } |
| else |
| { |
| ctx->cb.src_fn = current_function_decl; |
| ctx->cb.dst_fn = current_function_decl; |
| ctx->cb.src_node = cgraph_node::get (current_function_decl); |
| gcc_checking_assert (ctx->cb.src_node); |
| ctx->cb.dst_node = ctx->cb.src_node; |
| ctx->cb.src_cfun = cfun; |
| ctx->cb.copy_decl = omp_copy_decl; |
| ctx->cb.eh_lp_nr = 0; |
| ctx->cb.transform_call_graph_edges = CB_CGE_MOVE; |
| ctx->depth = 1; |
| } |
| |
| ctx->cb.decl_map = new hash_map<tree, tree>; |
| |
| return ctx; |
| } |
| |
| static gimple_seq maybe_catch_exception (gimple_seq); |
| |
| /* Finalize task copyfn. */ |
| |
| static void |
| finalize_task_copyfn (gomp_task *task_stmt) |
| { |
| struct function *child_cfun; |
| tree child_fn; |
| gimple_seq seq = NULL, new_seq; |
| gbind *bind; |
| |
| child_fn = gimple_omp_task_copy_fn (task_stmt); |
| if (child_fn == NULL_TREE) |
| return; |
| |
| child_cfun = DECL_STRUCT_FUNCTION (child_fn); |
| DECL_STRUCT_FUNCTION (child_fn)->curr_properties = cfun->curr_properties; |
| |
| push_cfun (child_cfun); |
| bind = gimplify_body (child_fn, false); |
| gimple_seq_add_stmt (&seq, bind); |
| new_seq = maybe_catch_exception (seq); |
| if (new_seq != seq) |
| { |
| bind = gimple_build_bind (NULL, new_seq, NULL); |
| seq = NULL; |
| gimple_seq_add_stmt (&seq, bind); |
| } |
| gimple_set_body (child_fn, seq); |
| pop_cfun (); |
| |
| /* Inform the callgraph about the new function. */ |
| cgraph_node::add_new_function (child_fn, false); |
| cgraph_node::get (child_fn)->parallelized_function = 1; |
| } |
| |
| /* Destroy a omp_context data structures. Called through the splay tree |
| value delete callback. */ |
| |
| static void |
| delete_omp_context (splay_tree_value value) |
| { |
| omp_context *ctx = (omp_context *) value; |
| |
| delete ctx->cb.decl_map; |
| |
| if (ctx->field_map) |
| splay_tree_delete (ctx->field_map); |
| if (ctx->sfield_map) |
| splay_tree_delete (ctx->sfield_map); |
| /* Reduction map is copied to nested contexts, so only delete it in the |
| owner. */ |
| if (ctx->reduction_map |
| && gimple_code (ctx->stmt) == GIMPLE_OMP_TARGET |
| && is_gimple_omp_offloaded (ctx->stmt) |
| && is_gimple_omp_oacc (ctx->stmt)) |
| splay_tree_delete (ctx->reduction_map); |
| |
| /* We hijacked DECL_ABSTRACT_ORIGIN earlier. We need to clear it before |
| it produces corrupt debug information. */ |
| if (ctx->record_type) |
| { |
| tree t; |
| for (t = TYPE_FIELDS (ctx->record_type); t ; t = DECL_CHAIN (t)) |
| DECL_ABSTRACT_ORIGIN (t) = NULL; |
| } |
| if (ctx->srecord_type) |
| { |
| tree t; |
| for (t = TYPE_FIELDS (ctx->srecord_type); t ; t = DECL_CHAIN (t)) |
| DECL_ABSTRACT_ORIGIN (t) = NULL; |
| } |
| |
| if (is_task_ctx (ctx)) |
| finalize_task_copyfn (as_a <gomp_task *> (ctx->stmt)); |
| |
| XDELETE (ctx); |
| } |
| |
| /* Fix up RECEIVER_DECL with a type that has been remapped to the child |
| context. */ |
| |
| static void |
| fixup_child_record_type (omp_context *ctx) |
| { |
| tree f, type = ctx->record_type; |
| |
| /* ??? It isn't sufficient to just call remap_type here, because |
| variably_modified_type_p doesn't work the way we expect for |
| record types. Testing each field for whether it needs remapping |
| and creating a new record by hand works, however. */ |
| for (f = TYPE_FIELDS (type); f ; f = DECL_CHAIN (f)) |
| if (variably_modified_type_p (TREE_TYPE (f), ctx->cb.src_fn)) |
| break; |
| if (f) |
| { |
| tree name, new_fields = NULL; |
| |
| type = lang_hooks.types.make_type (RECORD_TYPE); |
| name = DECL_NAME (TYPE_NAME (ctx->record_type)); |
| name = build_decl (DECL_SOURCE_LOCATION (ctx->receiver_decl), |
| TYPE_DECL, name, type); |
| TYPE_NAME (type) = name; |
| |
| for (f = TYPE_FIELDS (ctx->record_type); f ; f = DECL_CHAIN (f)) |
| { |
| tree new_f = copy_node (f); |
| DECL_CONTEXT (new_f) = type; |
| TREE_TYPE (new_f) = remap_type (TREE_TYPE (f), &ctx->cb); |
| DECL_CHAIN (new_f) = new_fields; |
| walk_tree (&DECL_SIZE (new_f), copy_tree_body_r, &ctx->cb, NULL); |
| walk_tree (&DECL_SIZE_UNIT (new_f), copy_tree_body_r, |
| &ctx->cb, NULL); |
| walk_tree (&DECL_FIELD_OFFSET (new_f), copy_tree_body_r, |
| &ctx->cb, NULL); |
| new_fields = new_f; |
| |
| /* Arrange to be able to look up the receiver field |
| given the sender field. */ |
| splay_tree_insert (ctx->field_map, (splay_tree_key) f, |
| (splay_tree_value) new_f); |
| } |
| TYPE_FIELDS (type) = nreverse (new_fields); |
| layout_type (type); |
| } |
| |
| TREE_TYPE (ctx->receiver_decl) |
| = build_qualified_type (build_reference_type (type), TYPE_QUAL_RESTRICT); |
| } |
| |
| /* Instantiate decls as necessary in CTX to satisfy the data sharing |
| specified by CLAUSES. */ |
| |
| static void |
| scan_sharing_clauses (tree clauses, omp_context *ctx) |
| { |
| tree c, decl; |
| bool scan_array_reductions = false; |
| |
| for (c = clauses; c; c = OMP_CLAUSE_CHAIN (c)) |
| { |
| bool by_ref; |
| |
| switch (OMP_CLAUSE_CODE (c)) |
| { |
| case OMP_CLAUSE_PRIVATE: |
| decl = OMP_CLAUSE_DECL (c); |
| if (OMP_CLAUSE_PRIVATE_OUTER_REF (c)) |
| goto do_private; |
| else if (!is_variable_sized (decl)) |
| install_var_local (decl, ctx); |
| break; |
| |
| case OMP_CLAUSE_SHARED: |
| decl = OMP_CLAUSE_DECL (c); |
| /* Ignore shared directives in teams construct. */ |
| if (gimple_code (ctx->stmt) == GIMPLE_OMP_TEAMS) |
| { |
| /* Global variables don't need to be copied, |
| the receiver side will use them directly. */ |
| tree odecl = maybe_lookup_decl_in_outer_ctx (decl, ctx); |
| if (is_global_var (odecl)) |
| break; |
| insert_decl_map (&ctx->cb, decl, odecl); |
| break; |
| } |
| gcc_assert (is_taskreg_ctx (ctx)); |
| gcc_assert (!COMPLETE_TYPE_P (TREE_TYPE (decl)) |
| || !is_variable_sized (decl)); |
| /* Global variables don't need to be copied, |
| the receiver side will use them directly. */ |
| if (is_global_var (maybe_lookup_decl_in_outer_ctx (decl, ctx))) |
| break; |
| by_ref = use_pointer_for_field (decl, ctx); |
| if (! TREE_READONLY (decl) |
| || TREE_ADDRESSABLE (decl) |
| || by_ref |
| || is_reference (decl)) |
| { |
| install_var_field (decl, by_ref, 3, ctx); |
| install_var_local (decl, ctx); |
| break; |
| } |
| /* We don't need to copy const scalar vars back. */ |
| OMP_CLAUSE_SET_CODE (c, OMP_CLAUSE_FIRSTPRIVATE); |
| goto do_private; |
| |
| case OMP_CLAUSE_LASTPRIVATE: |
| /* Let the corresponding firstprivate clause create |
| the variable. */ |
| if (OMP_CLAUSE_LASTPRIVATE_FIRSTPRIVATE (c)) |
| break; |
| /* FALLTHRU */ |
| |
| case OMP_CLAUSE_FIRSTPRIVATE: |
| if (is_gimple_omp_oacc (ctx->stmt)) |
| { |
| sorry ("clause not supported yet"); |
| break; |
| } |
| /* FALLTHRU */ |
| case OMP_CLAUSE_REDUCTION: |
| case OMP_CLAUSE_LINEAR: |
| decl = OMP_CLAUSE_DECL (c); |
| do_private: |
| if (is_variable_sized (decl)) |
| { |
| if (is_task_ctx (ctx)) |
| install_var_field (decl, false, 1, ctx); |
| break; |
| } |
| else if (is_taskreg_ctx (ctx)) |
| { |
| bool global |
| = is_global_var (maybe_lookup_decl_in_outer_ctx (decl, ctx)); |
| by_ref = use_pointer_for_field (decl, NULL); |
| |
| if (is_task_ctx (ctx) |
| && (global || by_ref || is_reference (decl))) |
| { |
| install_var_field (decl, false, 1, ctx); |
| if (!global) |
| install_var_field (decl, by_ref, 2, ctx); |
| } |
| else if (!global) |
| install_var_field (decl, by_ref, 3, ctx); |
| } |
| install_var_local (decl, ctx); |
| if (is_gimple_omp_oacc (ctx->stmt) |
| && OMP_CLAUSE_CODE (c) == OMP_CLAUSE_REDUCTION) |
| { |
| /* Create a decl for the reduction array. */ |
| tree var = OMP_CLAUSE_DECL (c); |
| tree type = get_base_type (var); |
| tree ptype = build_pointer_type (type); |
| tree array = create_tmp_var (ptype, |
| oacc_get_reduction_array_id (var)); |
| omp_context *c = (ctx->field_map ? ctx : ctx->outer); |
| install_var_field (array, true, 3, c); |
| install_var_local (array, c); |
| |
| /* Insert it into the current context. */ |
| splay_tree_insert (ctx->reduction_map, (splay_tree_key) |
| oacc_get_reduction_array_id (var), |
| (splay_tree_value) array); |
| splay_tree_insert (ctx->reduction_map, |
| (splay_tree_key) array, |
| (splay_tree_value) array); |
| } |
| break; |
| |
| case OMP_CLAUSE__LOOPTEMP_: |
| gcc_assert (is_parallel_ctx (ctx)); |
| decl = OMP_CLAUSE_DECL (c); |
| install_var_field (decl, false, 3, ctx); |
| install_var_local (decl, ctx); |
| break; |
| |
| case OMP_CLAUSE_COPYPRIVATE: |
| case OMP_CLAUSE_COPYIN: |
| decl = OMP_CLAUSE_DECL (c); |
| by_ref = use_pointer_for_field (decl, NULL); |
| install_var_field (decl, by_ref, 3, ctx); |
| break; |
| |
| case OMP_CLAUSE_DEFAULT: |
| ctx->default_kind = OMP_CLAUSE_DEFAULT_KIND (c); |
| break; |
| |
| case OMP_CLAUSE_FINAL: |
| case OMP_CLAUSE_IF: |
| case OMP_CLAUSE_NUM_THREADS: |
| case OMP_CLAUSE_NUM_TEAMS: |
| case OMP_CLAUSE_THREAD_LIMIT: |
| case OMP_CLAUSE_DEVICE: |
| case OMP_CLAUSE_SCHEDULE: |
| case OMP_CLAUSE_DIST_SCHEDULE: |
| case OMP_CLAUSE_DEPEND: |
| case OMP_CLAUSE__CILK_FOR_COUNT_: |
| case OMP_CLAUSE_NUM_GANGS: |
| case OMP_CLAUSE_NUM_WORKERS: |
| case OMP_CLAUSE_VECTOR_LENGTH: |
| if (ctx->outer) |
| scan_omp_op (&OMP_CLAUSE_OPERAND (c, 0), ctx->outer); |
| break; |
| |
| case OMP_CLAUSE_TO: |
| case OMP_CLAUSE_FROM: |
| case OMP_CLAUSE_MAP: |
| if (ctx->outer) |
| scan_omp_op (&OMP_CLAUSE_SIZE (c), ctx->outer); |
| decl = OMP_CLAUSE_DECL (c); |
| /* Global variables with "omp declare target" attribute |
| don't need to be copied, the receiver side will use them |
| directly. */ |
| if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_MAP |
| && DECL_P (decl) |
| && is_global_var (maybe_lookup_decl_in_outer_ctx (decl, ctx)) |
| && varpool_node::get_create (decl)->offloadable) |
| break; |
| if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_MAP |
| && OMP_CLAUSE_MAP_KIND (c) == GOMP_MAP_POINTER) |
| { |
| /* Ignore GOMP_MAP_POINTER kind for arrays in regions that are |
| not offloaded; there is nothing to map for those. */ |
| if (!is_gimple_omp_offloaded (ctx->stmt) |
| && !POINTER_TYPE_P (TREE_TYPE (decl)) |
| && !OMP_CLAUSE_MAP_ZERO_BIAS_ARRAY_SECTION (c)) |
| break; |
| } |
| if (DECL_P (decl)) |
| { |
| if (DECL_SIZE (decl) |
| && TREE_CODE (DECL_SIZE (decl)) != INTEGER_CST) |
| { |
| tree decl2 = DECL_VALUE_EXPR (decl); |
| gcc_assert (TREE_CODE (decl2) == INDIRECT_REF); |
| decl2 = TREE_OPERAND (decl2, 0); |
| gcc_assert (DECL_P (decl2)); |
| install_var_field (decl2, true, 3, ctx); |
| install_var_local (decl2, ctx); |
| install_var_local (decl, ctx); |
| } |
| else |
| { |
| if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_MAP |
| && OMP_CLAUSE_MAP_KIND (c) == GOMP_MAP_POINTER |
| && !OMP_CLAUSE_MAP_ZERO_BIAS_ARRAY_SECTION (c) |
| && TREE_CODE (TREE_TYPE (decl)) == ARRAY_TYPE) |
| install_var_field (decl, true, 7, ctx); |
| else |
| install_var_field (decl, true, 3, ctx); |
| if (is_gimple_omp_offloaded (ctx->stmt)) |
| install_var_local (decl, ctx); |
| } |
| } |
| else |
| { |
| tree base = get_base_address (decl); |
| tree nc = OMP_CLAUSE_CHAIN (c); |
| if (DECL_P (base) |
| && nc != NULL_TREE |
| && OMP_CLAUSE_CODE (nc) == OMP_CLAUSE_MAP |
| && OMP_CLAUSE_DECL (nc) == base |
| && OMP_CLAUSE_MAP_KIND (nc) == GOMP_MAP_POINTER |
| && integer_zerop (OMP_CLAUSE_SIZE (nc))) |
| { |
| OMP_CLAUSE_MAP_ZERO_BIAS_ARRAY_SECTION (c) = 1; |
| OMP_CLAUSE_MAP_ZERO_BIAS_ARRAY_SECTION (nc) = 1; |
| } |
| else |
| { |
| if (ctx->outer) |
| { |
| scan_omp_op (&OMP_CLAUSE_DECL (c), ctx->outer); |
| decl = OMP_CLAUSE_DECL (c); |
| } |
| gcc_assert (!splay_tree_lookup (ctx->field_map, |
| (splay_tree_key) decl)); |
| tree field |
| = build_decl (OMP_CLAUSE_LOCATION (c), |
| FIELD_DECL, NULL_TREE, ptr_type_node); |
| DECL_ALIGN (field) = TYPE_ALIGN (ptr_type_node); |
| insert_field_into_struct (ctx->record_type, field); |
| splay_tree_insert (ctx->field_map, (splay_tree_key) decl, |
| (splay_tree_value) field); |
| } |
| } |
| break; |
| |
| case OMP_CLAUSE_NOWAIT: |
| case OMP_CLAUSE_ORDERED: |
| case OMP_CLAUSE_COLLAPSE: |
| case OMP_CLAUSE_UNTIED: |
| case OMP_CLAUSE_MERGEABLE: |
| case OMP_CLAUSE_PROC_BIND: |
| case OMP_CLAUSE_SAFELEN: |
| case OMP_CLAUSE_ASYNC: |
| case OMP_CLAUSE_WAIT: |
| case OMP_CLAUSE_GANG: |
| case OMP_CLAUSE_WORKER: |
| case OMP_CLAUSE_VECTOR: |
| break; |
| |
| case OMP_CLAUSE_ALIGNED: |
| decl = OMP_CLAUSE_DECL (c); |
| if (is_global_var (decl) |
| && TREE_CODE (TREE_TYPE (decl)) == ARRAY_TYPE) |
| install_var_local (decl, ctx); |
| break; |
| |
| case OMP_CLAUSE_DEVICE_RESIDENT: |
| case OMP_CLAUSE_USE_DEVICE: |
| case OMP_CLAUSE__CACHE_: |
| case OMP_CLAUSE_INDEPENDENT: |
| case OMP_CLAUSE_AUTO: |
| case OMP_CLAUSE_SEQ: |
| sorry ("Clause not supported yet"); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| for (c = clauses; c; c = OMP_CLAUSE_CHAIN (c)) |
| { |
| switch (OMP_CLAUSE_CODE (c)) |
| { |
| case OMP_CLAUSE_LASTPRIVATE: |
| /* Let the corresponding firstprivate clause create |
| the variable. */ |
| if (OMP_CLAUSE_LASTPRIVATE_GIMPLE_SEQ (c)) |
| scan_array_reductions = true; |
| if (OMP_CLAUSE_LASTPRIVATE_FIRSTPRIVATE (c)) |
| break; |
| /* FALLTHRU */ |
| |
| case OMP_CLAUSE_FIRSTPRIVATE: |
| if (is_gimple_omp_oacc (ctx->stmt)) |
| { |
| sorry ("clause not supported yet"); |
| break; |
| } |
| /* FALLTHRU */ |
| case OMP_CLAUSE_PRIVATE: |
| case OMP_CLAUSE_REDUCTION: |
| case OMP_CLAUSE_LINEAR: |
| decl = OMP_CLAUSE_DECL (c); |
| if (is_variable_sized (decl)) |
| install_var_local (decl, ctx); |
| fixup_remapped_decl (decl, ctx, |
| OMP_CLAUSE_CODE (c) == OMP_CLAUSE_PRIVATE |
| && OMP_CLAUSE_PRIVATE_DEBUG (c)); |
| if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_REDUCTION |
| && OMP_CLAUSE_REDUCTION_PLACEHOLDER (c)) |
| scan_array_reductions = true; |
| else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_LINEAR |
| && OMP_CLAUSE_LINEAR_GIMPLE_SEQ (c)) |
| scan_array_reductions = true; |
| break; |
| |
| case OMP_CLAUSE_SHARED: |
| /* Ignore shared directives in teams construct. */ |
| if (gimple_code (ctx->stmt) == GIMPLE_OMP_TEAMS) |
| break; |
| decl = OMP_CLAUSE_DECL (c); |
| if (! is_global_var (maybe_lookup_decl_in_outer_ctx (decl, ctx))) |
| fixup_remapped_decl (decl, ctx, false); |
| break; |
| |
| case OMP_CLAUSE_MAP: |
| if (!is_gimple_omp_offloaded (ctx->stmt)) |
| break; |
| decl = OMP_CLAUSE_DECL (c); |
| if (DECL_P (decl) |
| && is_global_var (maybe_lookup_decl_in_outer_ctx (decl, ctx)) |
| && varpool_node::get_create (decl)->offloadable) |
| break; |
| if (DECL_P (decl)) |
| { |
| if (OMP_CLAUSE_MAP_KIND (c) == GOMP_MAP_POINTER |
| && TREE_CODE (TREE_TYPE (decl)) == ARRAY_TYPE |
| && !COMPLETE_TYPE_P (TREE_TYPE (decl))) |
| { |
| tree new_decl = lookup_decl (decl, ctx); |
| TREE_TYPE (new_decl) |
| = remap_type (TREE_TYPE (decl), &ctx->cb); |
| } |
| else if (DECL_SIZE (decl) |
| && TREE_CODE (DECL_SIZE (decl)) != INTEGER_CST) |
| { |
| tree decl2 = DECL_VALUE_EXPR (decl); |
| gcc_assert (TREE_CODE (decl2) == INDIRECT_REF); |
| decl2 = TREE_OPERAND (decl2, 0); |
| gcc_assert (DECL_P (decl2)); |
| fixup_remapped_decl (decl2, ctx, false); |
| fixup_remapped_decl (decl, ctx, true); |
| } |
| else |
| fixup_remapped_decl (decl, ctx, false); |
| } |
| break; |
| |
| case OMP_CLAUSE_COPYPRIVATE: |
| case OMP_CLAUSE_COPYIN: |
| case OMP_CLAUSE_DEFAULT: |
| case OMP_CLAUSE_IF: |
| case OMP_CLAUSE_NUM_THREADS: |
| case OMP_CLAUSE_NUM_TEAMS: |
| case OMP_CLAUSE_THREAD_LIMIT: |
| case OMP_CLAUSE_DEVICE: |
| case OMP_CLAUSE_SCHEDULE: |
| case OMP_CLAUSE_DIST_SCHEDULE: |
| case OMP_CLAUSE_NOWAIT: |
| case OMP_CLAUSE_ORDERED: |
| case OMP_CLAUSE_COLLAPSE: |
| case OMP_CLAUSE_UNTIED: |
| case OMP_CLAUSE_FINAL: |
| case OMP_CLAUSE_MERGEABLE: |
| case OMP_CLAUSE_PROC_BIND: |
| case OMP_CLAUSE_SAFELEN: |
| case OMP_CLAUSE_ALIGNED: |
| case OMP_CLAUSE_DEPEND: |
| case OMP_CLAUSE__LOOPTEMP_: |
| case OMP_CLAUSE_TO: |
| case OMP_CLAUSE_FROM: |
| case OMP_CLAUSE__CILK_FOR_COUNT_: |
| case OMP_CLAUSE_ASYNC: |
| case OMP_CLAUSE_WAIT: |
| case OMP_CLAUSE_NUM_GANGS: |
| case OMP_CLAUSE_NUM_WORKERS: |
| case OMP_CLAUSE_VECTOR_LENGTH: |
| case OMP_CLAUSE_GANG: |
| case OMP_CLAUSE_WORKER: |
| case OMP_CLAUSE_VECTOR: |
| break; |
| |
| case OMP_CLAUSE_DEVICE_RESIDENT: |
| case OMP_CLAUSE_USE_DEVICE: |
| case OMP_CLAUSE__CACHE_: |
| case OMP_CLAUSE_INDEPENDENT: |
| case OMP_CLAUSE_AUTO: |
| case OMP_CLAUSE_SEQ: |
| sorry ("Clause not supported yet"); |
| break; |
| |
| default: |
| gcc_unreachable (); |
| } |
| } |
| |
| gcc_checking_assert (!scan_array_reductions |
| || !is_gimple_omp_oacc (ctx->stmt)); |
| if (scan_array_reductions) |
| for (c = clauses; c; c = OMP_CLAUSE_CHAIN (c)) |
| if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_REDUCTION |
| && OMP_CLAUSE_REDUCTION_PLACEHOLDER (c)) |
| { |
| scan_omp (&OMP_CLAUSE_REDUCTION_GIMPLE_INIT (c), ctx); |
| scan_omp (&OMP_CLAUSE_REDUCTION_GIMPLE_MERGE (c), ctx); |
| } |
| else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_LASTPRIVATE |
| && OMP_CLAUSE_LASTPRIVATE_GIMPLE_SEQ (c)) |
| scan_omp (&OMP_CLAUSE_LASTPRIVATE_GIMPLE_SEQ (c), ctx); |
| else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_LINEAR |
| && OMP_CLAUSE_LINEAR_GIMPLE_SEQ (c)) |
| scan_omp (&OMP_CLAUSE_LINEAR_GIMPLE_SEQ (c), ctx); |
| } |
| |
| /* Create a new name for omp child function. Returns an identifier. If |
| IS_CILK_FOR is true then the suffix for the child function is |
| "_cilk_for_fn." */ |
| |
| static tree |
| create_omp_child_function_name (bool task_copy, bool is_cilk_for) |
| { |
| if (is_cilk_for) |
| return clone_function_name (current_function_decl, "_cilk_for_fn"); |
| return clone_function_name (current_function_decl, |
| task_copy ? "_omp_cpyfn" : "_omp_fn"); |
| } |
| |
| /* Returns the type of the induction variable for the child function for |
| _Cilk_for and the types for _high and _low variables based on TYPE. */ |
| |
| static tree |
| cilk_for_check_loop_diff_type (tree type) |
| { |
| if (TYPE_PRECISION (type) <= TYPE_PRECISION (uint32_type_node)) |
| { |
| if (TYPE_UNSIGNED (type)) |
| return uint32_type_node; |
| else |
| return integer_type_node; |
| } |
| else |
| { |
| if (TYPE_UNSIGNED (type)) |
| return uint64_type_node; |
| else |
| return long_long_integer_type_node; |
| } |
| } |
| |
| /* Build a decl for the omp child function. It'll not contain a body |
| yet, just the bare decl. */ |
| |
| static void |
| create_omp_child_function (omp_context *ctx, bool task_copy) |
| { |
| tree decl, type, name, t; |
| |
| tree cilk_for_count |
| = (flag_cilkplus && gimple_code (ctx->stmt) == GIMPLE_OMP_PARALLEL) |
| ? find_omp_clause (gimple_omp_parallel_clauses (ctx->stmt), |
| OMP_CLAUSE__CILK_FOR_COUNT_) : NULL_TREE; |
| tree cilk_var_type = NULL_TREE; |
| |
| name = create_omp_child_function_name (task_copy, |
| cilk_for_count != NULL_TREE); |
| if (task_copy) |
| type = build_function_type_list (void_type_node, ptr_type_node, |
| ptr_type_node, NULL_TREE); |
| else if (cilk_for_count) |
| { |
| type = TREE_TYPE (OMP_CLAUSE_OPERAND (cilk_for_count, 0)); |
| cilk_var_type = cilk_for_check_loop_diff_type (type); |
| type = build_function_type_list (void_type_node, ptr_type_node, |
| cilk_var_type, cilk_var_type, NULL_TREE); |
| } |
| else |
| type = build_function_type_list (void_type_node, ptr_type_node, NULL_TREE); |
| |
| decl = build_decl (gimple_location (ctx->stmt), FUNCTION_DECL, name, type); |
| |
| gcc_checking_assert (!is_gimple_omp_oacc (ctx->stmt) |
| || !task_copy); |
| if (!task_copy) |
| ctx->cb.dst_fn = decl; |
| else |
| gimple_omp_task_set_copy_fn (ctx->stmt, decl); |
| |
| TREE_STATIC (decl) = 1; |
| TREE_USED (decl) = 1; |
| DECL_ARTIFICIAL (decl) = 1; |
| DECL_IGNORED_P (decl) = 0; |
| TREE_PUBLIC (decl) = 0; |
| DECL_UNINLINABLE (decl) = 1; |
| DECL_EXTERNAL (decl) = 0; |
| DECL_CONTEXT (decl) = NULL_TREE; |
| DECL_INITIAL (decl) = make_node (BLOCK); |
| if (cgraph_node::get (current_function_decl)->offloadable) |
| cgraph_node::get_create (decl)->offloadable = 1; |
| else |
| { |
| omp_context *octx; |
| for (octx = ctx; octx; octx = octx->outer) |
| if (is_gimple_omp_offloaded (octx->stmt)) |
| { |
| cgraph_node::get_create (decl)->offloadable = 1; |
| #ifdef ENABLE_OFFLOADING |
| g->have_offload = true; |
| #endif |
| break; |
| } |
| } |
| |
| if (cgraph_node::get_create (decl)->offloadable |
| && !lookup_attribute ("omp declare target", |
| DECL_ATTRIBUTES (current_function_decl))) |
| DECL_ATTRIBUTES (decl) |
| = tree_cons (get_identifier ("omp target entrypoint"), |
| NULL_TREE, DECL_ATTRIBUTES (decl)); |
| |
| t = build_decl (DECL_SOURCE_LOCATION (decl), |
| RESULT_DECL, NULL_TREE, void_type_node); |
| DECL_ARTIFICIAL (t) = 1; |
| DECL_IGNORED_P (t) = 1; |
| DECL_CONTEXT (t) = decl; |
| DECL_RESULT (decl) = t; |
| |
| /* _Cilk_for's child function requires two extra parameters called |
| __low and __high that are set the by Cilk runtime when it calls this |
| function. */ |
| if (cilk_for_count) |
| { |
| t = build_decl (DECL_SOURCE_LOCATION (decl), |
| PARM_DECL, get_identifier ("__high"), cilk_var_type); |
| DECL_ARTIFICIAL (t) = 1; |
| DECL_NAMELESS (t) = 1; |
| DECL_ARG_TYPE (t) = ptr_type_node; |
| DECL_CONTEXT (t) = current_function_decl; |
| TREE_USED (t) = 1; |
| DECL_CHAIN (t) = DECL_ARGUMENTS (decl); |
| DECL_ARGUMENTS (decl) = t; |
| |
| t = build_decl (DECL_SOURCE_LOCATION (decl), |
| PARM_DECL, get_identifier ("__low"), cilk_var_type); |
| DECL_ARTIFICIAL (t) = 1; |
| DECL_NAMELESS (t) = 1; |
| DECL_ARG_TYPE (t) = ptr_type_node; |
| DECL_CONTEXT (t) = current_function_decl; |
| TREE_USED (t) = 1; |
| DECL_CHAIN (t) = DECL_ARGUMENTS (decl); |
| DECL_ARGUMENTS (decl) = t; |
| } |
| |
| tree data_name = get_identifier (".omp_data_i"); |
| t = build_decl (DECL_SOURCE_LOCATION (decl), PARM_DECL, data_name, |
| ptr_type_node); |
| DECL_ARTIFICIAL (t) = 1; |
| DECL_NAMELESS (t) = 1; |
| DECL_ARG_TYPE (t) = ptr_type_node; |
| DECL_CONTEXT (t) = current_function_decl; |
| TREE_USED (t) = 1; |
| if (cilk_for_count) |
| DECL_CHAIN (t) = DECL_ARGUMENTS (decl); |
| DECL_ARGUMENTS (decl) = t; |
| if (!task_copy) |
| ctx->receiver_decl = t; |
| else |
| { |
| t = build_decl (DECL_SOURCE_LOCATION (decl), |
| PARM_DECL, get_identifier (".omp_data_o"), |
| ptr_type_node); |
| DECL_ARTIFICIAL (t) = 1; |
| DECL_NAMELESS (t) = 1; |
| DECL_ARG_TYPE (t) = ptr_type_node; |
| DECL_CONTEXT (t) = current_function_decl; |
| TREE_USED (t) = 1; |
| TREE_ADDRESSABLE (t) = 1; |
| DECL_CHAIN (t) = DECL_ARGUMENTS (decl); |
| DECL_ARGUMENTS (decl) = t; |
| } |
| |
| /* Allocate memory for the function structure. The call to |
| allocate_struct_function clobbers CFUN, so we need to restore |
| it afterward. */ |
| push_struct_function (decl); |
| cfun->function_end_locus = gimple_location (ctx->stmt); |
| pop_cfun (); |
| } |
| |
| /* Callback for walk_gimple_seq. Check if combined parallel |
| contains gimple_omp_for_combined_into_p OMP_FOR. */ |
| |
| static tree |
| find_combined_for (gimple_stmt_iterator *gsi_p, |
| bool *handled_ops_p, |
| struct walk_stmt_info *wi) |
| { |
| gimple stmt = gsi_stmt (*gsi_p); |
| |
| *handled_ops_p = true; |
| switch (gimple_code (stmt)) |
| { |
| WALK_SUBSTMTS; |
| |
| case GIMPLE_OMP_FOR: |
| if (gimple_omp_for_combined_into_p (stmt) |
| && gimple_omp_for_kind (stmt) == GF_OMP_FOR_KIND_FOR) |
| { |
| wi->info = stmt; |
| return integer_zero_node; |
| } |
| break; |
| default: |
| break; |
| } |
| return NULL; |
| } |
| |
| /* Scan an OpenMP parallel directive. */ |
| |
| static void |
| scan_omp_parallel (gimple_stmt_iterator *gsi, omp_context *outer_ctx) |
| { |
| omp_context *ctx; |
| tree name; |
| gomp_parallel *stmt = as_a <gomp_parallel *> (gsi_stmt (*gsi)); |
| |
| /* Ignore parallel directives with empty bodies, unless there |
| are copyin clauses. */ |
| if (optimize > 0 |
| && empty_body_p (gimple_omp_body (stmt)) |
| && find_omp_clause (gimple_omp_parallel_clauses (stmt), |
| OMP_CLAUSE_COPYIN) == NULL) |
| { |
| gsi_replace (gsi, gimple_build_nop (), false); |
| return; |
| } |
| |
| if (gimple_omp_parallel_combined_p (stmt)) |
| { |
| struct walk_stmt_info wi; |
| |
| memset (&wi, 0, sizeof (wi)); |
| wi.val_only = true; |
| walk_gimple_seq (gimple_omp_body (stmt), |
| find_combined_for, NULL, &wi); |
| if (wi.info) |
| { |
| gomp_for *for_stmt = as_a <gomp_for *> ((gimple) wi.info); |
| struct omp_for_data fd; |
| extract_omp_for_data (for_stmt, &fd, NULL); |
| /* We need two temporaries with fd.loop.v type (istart/iend) |
| and then (fd.collapse - 1) temporaries with the same |
| type for count2 ... countN-1 vars if not constant. */ |
| size_t count = 2, i; |
| tree type = fd.iter_type; |
| if (fd.collapse > 1 |
| && TREE_CODE (fd.loop.n2) != INTEGER_CST) |
| count += fd.collapse - 1; |
| for (i = 0; i < count; i++) |
| { |
| tree temp = create_tmp_var (type); |
| tree c = build_omp_clause (UNKNOWN_LOCATION, |
| OMP_CLAUSE__LOOPTEMP_); |
| insert_decl_map (&outer_ctx->cb, temp, temp); |
| OMP_CLAUSE_DECL (c) = temp; |
| OMP_CLAUSE_CHAIN (c) = gimple_omp_parallel_clauses (stmt); |
| gimple_omp_parallel_set_clauses (stmt, c); |
| } |
| } |
| } |
| |
| ctx = new_omp_context (stmt, outer_ctx); |
| taskreg_contexts.safe_push (ctx); |
| if (taskreg_nesting_level > 1) |
| ctx->is_nested = true; |
| ctx->field_map = splay_tree_new (splay_tree_compare_pointers, 0, 0); |
| ctx->default_kind = OMP_CLAUSE_DEFAULT_SHARED; |
| ctx->record_type = lang_hooks.types.make_type (RECORD_TYPE); |
| name = create_tmp_var_name (".omp_data_s"); |
| name = build_decl (gimple_location (stmt), |
| TYPE_DECL, name, ctx->record_type); |
| DECL_ARTIFICIAL (name) = 1; |
| DECL_NAMELESS (name) = 1; |
| TYPE_NAME (ctx->record_type) = name; |
| TYPE_ARTIFICIAL (ctx->record_type) = 1; |
| create_omp_child_function (ctx, false); |
| gimple_omp_parallel_set_child_fn (stmt, ctx->cb.dst_fn); |
| |
| scan_sharing_clauses (gimple_omp_parallel_clauses (stmt), ctx); |
| scan_omp (gimple_omp_body_ptr (stmt), ctx); |
| |
| if (TYPE_FIELDS (ctx->record_type) == NULL) |
| ctx->record_type = ctx->receiver_decl = NULL; |
| } |
| |
| /* Scan an OpenMP task directive. */ |
| |
| static void |
| scan_omp_task (gimple_stmt_iterator *gsi, omp_context *outer_ctx) |
| { |
| omp_context *ctx; |
| tree name, t; |
| gomp_task *stmt = as_a <gomp_task *> (gsi_stmt (*gsi)); |
| |
| /* Ignore task directives with empty bodies, unless they have depend |
| clause. */ |
| if (optimize > 0 |
| && empty_body_p (gimple_omp_body (stmt)) |
| && !find_omp_clause (gimple_omp_task_clauses (stmt), OMP_CLAUSE_DEPEND)) |
| { |
| gsi_replace (gsi, gimple_build_nop (), false); |
| return; |
| } |
| |
| ctx = new_omp_context (stmt, outer_ctx); |
| taskreg_contexts.safe_push (ctx); |
| if (taskreg_nesting_level > 1) |
| ctx->is_nested = true; |
| ctx->field_map = splay_tree_new (splay_tree_compare_pointers, 0, 0); |
| ctx->default_kind = OMP_CLAUSE_DEFAULT_SHARED; |
| ctx->record_type = lang_hooks.types.make_type (RECORD_TYPE); |
| name = create_tmp_var_name (".omp_data_s"); |
| name = build_decl (gimple_location (stmt), |
| TYPE_DECL, name, ctx->record_type); |
| DECL_ARTIFICIAL (name) = 1; |
| DECL_NAMELESS (name) = 1; |
| TYPE_NAME (ctx->record_type) = name; |
| TYPE_ARTIFICIAL (ctx->record_type) = 1; |
| create_omp_child_function (ctx, false); |
| gimple_omp_task_set_child_fn (stmt, ctx->cb.dst_fn); |
| |
| scan_sharing_clauses (gimple_omp_task_clauses (stmt), ctx); |
| |
| if (ctx->srecord_type) |
| { |
| name = create_tmp_var_name (".omp_data_a"); |
| name = build_decl (gimple_location (stmt), |
| TYPE_DECL, name, ctx->srecord_type); |
| DECL_ARTIFICIAL (name) = 1; |
| DECL_NAMELESS (name) = 1; |
| TYPE_NAME (ctx->srecord_type) = name; |
| TYPE_ARTIFICIAL (ctx->srecord_type) = 1; |
| create_omp_child_function (ctx, true); |
| } |
| |
| scan_omp (gimple_omp_body_ptr (stmt), ctx); |
| |
| if (TYPE_FIELDS (ctx->record_type) == NULL) |
| { |
| ctx->record_type = ctx->receiver_decl = NULL; |
| t = build_int_cst (long_integer_type_node, 0); |
| gimple_omp_task_set_arg_size (stmt, t); |
| t = build_int_cst (long_integer_type_node, 1); |
| gimple_omp_task_set_arg_align (stmt, t); |
| } |
| } |
| |
| |
| /* If any decls have been made addressable during scan_omp, |
| adjust their fields if needed, and layout record types |
| of parallel/task constructs. */ |
| |
| static void |
| finish_taskreg_scan (omp_context *ctx) |
| { |
| if (ctx->record_type == NULL_TREE) |
| return; |
| |
| /* If any task_shared_vars were needed, verify all |
| OMP_CLAUSE_SHARED clauses on GIMPLE_OMP_{PARALLEL,TASK} |
| statements if use_pointer_for_field hasn't changed |
| because of that. If it did, update field types now. */ |
| if (task_shared_vars) |
| { |
| tree c; |
| |
| for (c = gimple_omp_taskreg_clauses (ctx->stmt); |
| c; c = OMP_CLAUSE_CHAIN (c)) |
| if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_SHARED) |
| { |
| tree decl = OMP_CLAUSE_DECL (c); |
| |
| /* Global variables don't need to be copied, |
| the receiver side will use them directly. */ |
| if (is_global_var (maybe_lookup_decl_in_outer_ctx (decl, ctx))) |
| continue; |
| if (!bitmap_bit_p (task_shared_vars, DECL_UID (decl)) |
| || !use_pointer_for_field (decl, ctx)) |
| continue; |
| tree field = lookup_field (decl, ctx); |
| if (TREE_CODE (TREE_TYPE (field)) == POINTER_TYPE |
| && TREE_TYPE (TREE_TYPE (field)) == TREE_TYPE (decl)) |
| continue; |
| TREE_TYPE (field) = build_pointer_type (TREE_TYPE (decl)); |
| TREE_THIS_VOLATILE (field) = 0; |
| DECL_USER_ALIGN (field) = 0; |
| DECL_ALIGN (field) = TYPE_ALIGN (TREE_TYPE (field)); |
| if (TYPE_ALIGN (ctx->record_type) < DECL_ALIGN (field)) |
| TYPE_ALIGN (ctx->record_type) = DECL_ALIGN (field); |
| if (ctx->srecord_type) |
| { |
| tree sfield = lookup_sfield (decl, ctx); |
| TREE_TYPE (sfield) = TREE_TYPE (field); |
| TREE_THIS_VOLATILE (sfield) = 0; |
| DECL_USER_ALIGN (sfield) = 0; |
| DECL_ALIGN (sfield) = DECL_ALIGN (field); |
| if (TYPE_ALIGN (ctx->srecord_type) < DECL_ALIGN (sfield)) |
| TYPE_ALIGN (ctx->srecord_type) = DECL_ALIGN (sfield); |
| } |
| } |
| } |
| |
| if (gimple_code (ctx->stmt) == GIMPLE_OMP_PARALLEL) |
| { |
| layout_type (ctx->record_type); |
| fixup_child_record_type (ctx); |
| } |
| else |
| { |
| location_t loc = gimple_location (ctx->stmt); |
| tree *p, vla_fields = NULL_TREE, *q = &vla_fields; |
| /* Move VLA fields to the end. */ |
| p = &TYPE_FIELDS (ctx->record_type); |
| while (*p) |
| if (!TYPE_SIZE_UNIT (TREE_TYPE (*p)) |
| || ! TREE_CONSTANT (TYPE_SIZE_UNIT (TREE_TYPE (*p)))) |
| { |
| *q = *p; |
| *p = TREE_CHAIN (*p); |
| TREE_CHAIN (*q) = NULL_TREE; |
| q = &TREE_CHAIN (*q); |
| } |
| else |
| p = &DECL_CHAIN (*p); |
| *p = vla_fields; |
| layout_type (ctx->record_type); |
| fixup_child_record_type (ctx); |
| if (ctx->srecord_type) |
| layout_type (ctx->srecord_type); |
| tree t = fold_convert_loc (loc, long_integer_type_node, |
| TYPE_SIZE_UNIT (ctx->record_type)); |
| gimple_omp_task_set_arg_size (ctx->stmt, t); |
| t = build_int_cst (long_integer_type_node, |
| TYPE_ALIGN_UNIT (ctx->record_type)); |
| gimple_omp_task_set_arg_align (ctx->stmt, t); |
| } |
| } |
| |
| |
| static omp_context * |
| enclosing_target_ctx (omp_context *ctx) |
| { |
| while (ctx != NULL |
| && gimple_code (ctx->stmt) != GIMPLE_OMP_TARGET) |
| ctx = ctx->outer; |
| gcc_assert (ctx != NULL); |
| return ctx; |
| } |
| |
| static bool |
| oacc_loop_or_target_p (gimple stmt) |
| { |
| enum gimple_code outer_type = gimple_code (stmt); |
| return ((outer_type == GIMPLE_OMP_TARGET |
| && ((gimple_omp_target_kind (stmt) |
| == GF_OMP_TARGET_KIND_OACC_PARALLEL) |
| || (gimple_omp_target_kind (stmt) |
| == GF_OMP_TARGET_KIND_OACC_KERNELS))) |
| || (outer_type == GIMPLE_OMP_FOR |
| && gimple_omp_for_kind (stmt) == GF_OMP_FOR_KIND_OACC_LOOP)); |
| } |
| |
| /* Scan a GIMPLE_OMP_FOR. */ |
| |
| static void |
| scan_omp_for (gomp_for *stmt, omp_context *outer_ctx) |
| { |
| enum gimple_code outer_type = GIMPLE_ERROR_MARK; |
| omp_context *ctx; |
| size_t i; |
| tree clauses = gimple_omp_for_clauses (stmt); |
| |
| if (outer_ctx) |
| outer_type = gimple_code (outer_ctx->stmt); |
| |
| ctx = new_omp_context (stmt, outer_ctx); |
| |
| if (is_gimple_omp_oacc (stmt)) |
| { |
| if (outer_ctx && outer_type == GIMPLE_OMP_FOR) |
| ctx->gwv_this = outer_ctx->gwv_this; |
| for (tree c = clauses; c; c = OMP_CLAUSE_CHAIN (c)) |
| { |
| int val; |
| if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_GANG) |
| val = MASK_GANG; |
| else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_WORKER) |
| val = MASK_WORKER; |
| else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_VECTOR) |
| val = MASK_VECTOR; |
| else |
| continue; |
| ctx->gwv_this |= val; |
| if (!outer_ctx) |
| { |
| /* Skip; not nested inside a region. */ |
| continue; |
| } |
| if (!oacc_loop_or_target_p (outer_ctx->stmt)) |
| { |
| /* Skip; not nested inside an OpenACC region. */ |
| continue; |
| } |
| if (outer_type == GIMPLE_OMP_FOR) |
| outer_ctx->gwv_below |= val; |
| if (OMP_CLAUSE_OPERAND (c, 0) != NULL_TREE) |
| { |
| omp_context *enclosing = enclosing_target_ctx (outer_ctx); |
| if (gimple_omp_target_kind (enclosing->stmt) |
| == GF_OMP_TARGET_KIND_OACC_PARALLEL) |
| error_at (gimple_location (stmt), |
| "no arguments allowed to gang, worker and vector clauses inside parallel"); |
| } |
| } |
| } |
| |
| scan_sharing_clauses (clauses, ctx); |
| |
| scan_omp (gimple_omp_for_pre_body_ptr (stmt), ctx); |
| for (i = 0; i < gimple_omp_for_collapse (stmt); i++) |
| { |
| scan_omp_op (gimple_omp_for_index_ptr (stmt, i), ctx); |
| scan_omp_op (gimple_omp_for_initial_ptr (stmt, i), ctx); |
| scan_omp_op (gimple_omp_for_final_ptr (stmt, i), ctx); |
| scan_omp_op (gimple_omp_for_incr_ptr (stmt, i), ctx); |
| } |
| scan_omp (gimple_omp_body_ptr (stmt), ctx); |
| |
| if (is_gimple_omp_oacc (stmt)) |
| { |
| if (ctx->gwv_this & ctx->gwv_below) |
| error_at (gimple_location (stmt), |
| "gang, worker and vector may occur only once in a loop nest"); |
| else if (ctx->gwv_below != 0 |
| && ctx->gwv_this > ctx->gwv_below) |
| error_at (gimple_location (stmt), |
| "gang, worker and vector must occur in this order in a loop nest"); |
| if (outer_ctx && outer_type == GIMPLE_OMP_FOR) |
| outer_ctx->gwv_below |= ctx->gwv_below; |
| } |
| } |
| |
| /* Scan an OpenMP sections directive. */ |
| |
| static void |
| scan_omp_sections (gomp_sections *stmt, omp_context *outer_ctx) |
| { |
| omp_context *ctx; |
| |
| ctx = new_omp_context (stmt, outer_ctx); |
| scan_sharing_clauses (gimple_omp_sections_clauses (stmt), ctx); |
| scan_omp (gimple_omp_body_ptr (stmt), ctx); |
| } |
| |
| /* Scan an OpenMP single directive. */ |
| |
| static void |
| scan_omp_single (gomp_single *stmt, omp_context *outer_ctx) |
| { |
| omp_context *ctx; |
| tree name; |
| |
| ctx = new_omp_context (stmt, outer_ctx); |
| ctx->field_map = splay_tree_new (splay_tree_compare_pointers, 0, 0); |
| ctx->record_type = lang_hooks.types.make_type (RECORD_TYPE); |
| name = create_tmp_var_name (".omp_copy_s"); |
| name = build_decl (gimple_location (stmt), |
| TYPE_DECL, name, ctx->record_type); |
| TYPE_NAME (ctx->record_type) = name; |
| |
| scan_sharing_clauses (gimple_omp_single_clauses (stmt), ctx); |
| scan_omp (gimple_omp_body_ptr (stmt), ctx); |
| |
| if (TYPE_FIELDS (ctx->record_type) == NULL) |
| ctx->record_type = NULL; |
| else |
| layout_type (ctx->record_type); |
| } |
| |
| /* Scan a GIMPLE_OMP_TARGET. */ |
| |
| static void |
| scan_omp_target (gomp_target *stmt, omp_context *outer_ctx) |
| { |
| omp_context *ctx; |
| tree name; |
| bool offloaded = is_gimple_omp_offloaded (stmt); |
| tree clauses = gimple_omp_target_clauses (stmt); |
| |
| ctx = new_omp_context (stmt, outer_ctx); |
| ctx->field_map = splay_tree_new (splay_tree_compare_pointers, 0, 0); |
| ctx->default_kind = OMP_CLAUSE_DEFAULT_SHARED; |
| ctx->record_type = lang_hooks.types.make_type (RECORD_TYPE); |
| name = create_tmp_var_name (".omp_data_t"); |
| name = build_decl (gimple_location (stmt), |
| TYPE_DECL, name, ctx->record_type); |
| DECL_ARTIFICIAL (name) = 1; |
| DECL_NAMELESS (name) = 1; |
| TYPE_NAME (ctx->record_type) = name; |
| TYPE_ARTIFICIAL (ctx->record_type) = 1; |
| if (offloaded) |
| { |
| if (is_gimple_omp_oacc (stmt)) |
| ctx->reduction_map = splay_tree_new (splay_tree_compare_pointers, |
| 0, 0); |
| |
| create_omp_child_function (ctx, false); |
| gimple_omp_target_set_child_fn (stmt, ctx->cb.dst_fn); |
| } |
| |
| if (is_gimple_omp_oacc (stmt)) |
| { |
| for (tree c = clauses; c; c = OMP_CLAUSE_CHAIN (c)) |
| { |
| if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_NUM_GANGS) |
| ctx->gwv_this |= MASK_GANG; |
| else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_NUM_WORKERS) |
| ctx->gwv_this |= MASK_WORKER; |
| else if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_VECTOR_LENGTH) |
| ctx->gwv_this |= MASK_VECTOR; |
| } |
| } |
| |
| scan_sharing_clauses (clauses, ctx); |
| scan_omp (gimple_omp_body_ptr (stmt), ctx); |
| |
| if (TYPE_FIELDS (ctx->record_type) == NULL) |
| ctx->record_type = ctx->receiver_decl = NULL; |
| else |
| { |
| TYPE_FIELDS (ctx->record_type) |
| = nreverse (TYPE_FIELDS (ctx->record_type)); |
| #ifdef ENABLE_CHECKING |
| tree field; |
| unsigned int align = DECL_ALIGN (TYPE_FIELDS (ctx->record_type)); |
| for (field = TYPE_FIELDS (ctx->record_type); |
| field; |
| field = DECL_CHAIN (field)) |
| gcc_assert (DECL_ALIGN (field) == align); |
| #endif |
| layout_type (ctx->record_type); |
| if (offloaded) |
| fixup_child_record_type (ctx); |
| } |
| } |
| |
| /* Scan an OpenMP teams directive. */ |
| |
| static void |
| scan_omp_teams (gomp_teams *stmt, omp_context *outer_ctx) |
| { |
| omp_context *ctx = new_omp_context (stmt, outer_ctx); |
| scan_sharing_clauses (gimple_omp_teams_clauses (stmt), ctx); |
| scan_omp (gimple_omp_body_ptr (stmt), ctx); |
| } |
| |
| /* Check nesting restrictions. */ |
| static bool |
| check_omp_nesting_restrictions (gimple stmt, omp_context *ctx) |
| { |
| /* No nesting of non-OpenACC STMT (that is, an OpenMP one, or a GOMP builtin) |
| inside an OpenACC CTX. */ |
| if (!(is_gimple_omp (stmt) |
| && is_gimple_omp_oacc (stmt))) |
| { |
| for (omp_context *ctx_ = ctx; ctx_ != NULL; ctx_ = ctx_->outer) |
| if (is_gimple_omp (ctx_->stmt) |
| && is_gimple_omp_oacc (ctx_->stmt)) |
| { |
| error_at (gimple_location (stmt), |
| "non-OpenACC construct inside of OpenACC region"); |
| return false; |
| } |
| } |
| |
| if (ctx != NULL) |
| { |
| if (gimple_code (ctx->stmt) == GIMPLE_OMP_FOR |
| && gimple_omp_for_kind (ctx->stmt) & GF_OMP_FOR_SIMD) |
| { |
| error_at (gimple_location (stmt), |
| "OpenMP constructs may not be nested inside simd region"); |
| return false; |
| } |
| else if (gimple_code (ctx->stmt) == GIMPLE_OMP_TEAMS) |
| { |
| if ((gimple_code (stmt) != GIMPLE_OMP_FOR |
| || (gimple_omp_for_kind (stmt) |
| != GF_OMP_FOR_KIND_DISTRIBUTE)) |
| && gimple_code (stmt) != GIMPLE_OMP_PARALLEL) |
| { |
| error_at (gimple_location (stmt), |
| "only distribute or parallel constructs are allowed to " |
| "be closely nested inside teams construct"); |
| return false; |
| } |
| } |
| } |
| switch (gimple_code (stmt)) |
| { |
| case GIMPLE_OMP_FOR: |
| if (gimple_omp_for_kind (stmt) & GF_OMP_FOR_SIMD) |
| return true; |
| if (gimple_omp_for_kind (stmt) == GF_OMP_FOR_KIND_DISTRIBUTE) |
| { |
| if (ctx != NULL && gimple_code (ctx->stmt) != GIMPLE_OMP_TEAMS) |
| { |
| error_at (gimple_location (stmt), |
| "distribute construct must be closely nested inside " |
| "teams construct"); |
| return false; |
| } |
| return true; |
| } |
| /* FALLTHRU */ |
| case GIMPLE_CALL: |
| if (is_gimple_call (stmt) |
| && (DECL_FUNCTION_CODE (gimple_call_fndecl (stmt)) |
| == BUILT_IN_GOMP_CANCEL |
| || DECL_FUNCTION_CODE (gimple_call_fndecl (stmt)) |
| == BUILT_IN_GOMP_CANCELLATION_POINT)) |
| { |
| const char *bad = NULL; |
| const char *kind = NULL; |
| if (ctx == NULL) |
| { |
| error_at (gimple_location (stmt), "orphaned %qs construct", |
| DECL_FUNCTION_CODE (gimple_call_fndecl (stmt)) |
| == BUILT_IN_GOMP_CANCEL |
| ? "#pragma omp cancel" |
| : "#pragma omp cancellation point"); |
| return false; |
| } |
| switch (tree_fits_shwi_p (gimple_call_arg (stmt, 0)) |
| ? tree_to_shwi (gimple_call_arg (stmt, 0)) |
| : 0) |
| { |
| case 1: |
| if (gimple_code (ctx->stmt) != GIMPLE_OMP_PARALLEL) |
| bad = "#pragma omp parallel"; |
| else if (DECL_FUNCTION_CODE (gimple_call_fndecl (stmt)) |
| == BUILT_IN_GOMP_CANCEL |
| && !integer_zerop (gimple_call_arg (stmt, 1))) |
| ctx->cancellable = true; |
| kind = "parallel"; |
| break; |
| case 2: |
| if (gimple_code (ctx->stmt) != GIMPLE_OMP_FOR |
| || gimple_omp_for_kind (ctx->stmt) != GF_OMP_FOR_KIND_FOR) |
| bad = "#pragma omp for"; |
| else if (DECL_FUNCTION_CODE (gimple_call_fndecl (stmt)) |
| == BUILT_IN_GOMP_CANCEL |
| && !integer_zerop (gimple_call_arg (stmt, 1))) |
| { |
| ctx->cancellable = true; |
| if (find_omp_clause (gimple_omp_for_clauses (ctx->stmt), |
| OMP_CLAUSE_NOWAIT)) |
| warning_at (gimple_location (stmt), 0, |
| "%<#pragma omp cancel for%> inside " |
| "%<nowait%> for construct"); |
| if (find_omp_clause (gimple_omp_for_clauses (ctx->stmt), |
| OMP_CLAUSE_ORDERED)) |
| warning_at (gimple_location (stmt), 0, |
| "%<#pragma omp cancel for%> inside " |
| "%<ordered%> for construct"); |
| } |
| kind = "for"; |
| break; |
| case 4: |
| if (gimple_code (ctx->stmt) != GIMPLE_OMP_SECTIONS |
| && gimple_code (ctx->stmt) != GIMPLE_OMP_SECTION) |
| bad = "#pragma omp sections"; |
| else if (DECL_FUNCTION_CODE (gimple_call_fndecl (stmt)) |
| == BUILT_IN_GOMP_CANCEL |
| && !integer_zerop (gimple_call_arg (stmt, 1))) |
| { |
| if (gimple_code (ctx->stmt) == GIMPLE_OMP_SECTIONS) |
| { |
| ctx->cancellable = true; |
| if (find_omp_clause (gimple_omp_sections_clauses |
| (ctx->stmt), |
| OMP_CLAUSE_NOWAIT)) |
| warning_at (gimple_location (stmt), 0, |
| "%<#pragma omp cancel sections%> inside " |
| "%<nowait%> sections construct"); |
| } |
| else |
| { |
| gcc_assert (ctx->outer |
| && gimple_code (ctx->outer->stmt) |
| == GIMPLE_OMP_SECTIONS); |
| ctx->outer->cancellable = true; |
| if (find_omp_clause (gimple_omp_sections_clauses |
| (ctx->outer->stmt), |
| OMP_CLAUSE_NOWAIT)) |
| warning_at (gimple_location (stmt), 0, |
| "%<#pragma omp cancel sections%> inside " |
| "%<nowait%> sections construct"); |
| } |
| } |
| kind = "sections"; |
| break; |
| case 8: |
| if (gimple_code (ctx->stmt) != GIMPLE_OMP_TASK) |
| bad = "#pragma omp task"; |
| else |
| ctx->cancellable = true; |
| kind = "taskgroup"; |
| break; |
| default: |
| error_at (gimple_location (stmt), "invalid arguments"); |
| return false; |
| } |
| if (bad) |
| { |
| error_at (gimple_location (stmt), |
| "%<%s %s%> construct not closely nested inside of %qs", |
| DECL_FUNCTION_CODE (gimple_call_fndecl (stmt)) |
| == BUILT_IN_GOMP_CANCEL |
| ? "#pragma omp cancel" |
| : "#pragma omp cancellation point", kind, bad); |
| return false; |
| } |
| } |
| /* FALLTHRU */ |
| case GIMPLE_OMP_SECTIONS: |
| case GIMPLE_OMP_SINGLE: |
| for (; ctx != NULL; ctx = ctx->outer) |
| switch (gimple_code (ctx->stmt)) |
| { |
| case GIMPLE_OMP_FOR: |
| case GIMPLE_OMP_SECTIONS: |
| case GIMPLE_OMP_SINGLE: |
| case GIMPLE_OMP_ORDERED: |
| case GIMPLE_OMP_MASTER: |
| case GIMPLE_OMP_TASK: |
| case GIMPLE_OMP_CRITICAL: |
| if (is_gimple_call (stmt)) |
| { |
| if (DECL_FUNCTION_CODE (gimple_call_fndecl (stmt)) |
| != BUILT_IN_GOMP_BARRIER) |
| return true; |
| error_at (gimple_location (stmt), |
| "barrier region may not be closely nested inside " |
| "of work-sharing, critical, ordered, master or " |
| "explicit task region"); |
| return false; |
| } |
| error_at (gimple_location (stmt), |
| "work-sharing region may not be closely nested inside " |
| "of work-sharing, critical, ordered, master or explicit " |
| "task region"); |
| return false; |
| case GIMPLE_OMP_PARALLEL: |
| return true; |
| default: |
| break; |
| } |
| break; |
| case GIMPLE_OMP_MASTER: |
| for (; ctx != NULL; ctx = ctx->outer) |
| switch (gimple_code (ctx->stmt)) |
| { |
| case GIMPLE_OMP_FOR: |
| case GIMPLE_OMP_SECTIONS: |
| case GIMPLE_OMP_SINGLE: |
| case GIMPLE_OMP_TASK: |
| error_at (gimple_location (stmt), |
| "master region may not be closely nested inside " |
| "of work-sharing or explicit task region"); |
| return false; |
| case GIMPLE_OMP_PARALLEL: |
| return true; |
| default: |
| break; |
| } |
| break; |
| case GIMPLE_OMP_ORDERED: |
| for (; ctx != NULL; ctx = ctx->outer) |
| switch (gimple_code (ctx->stmt)) |
| { |
| case GIMPLE_OMP_CRITICAL: |
| case GIMPLE_OMP_TASK: |
| error_at (gimple_location (stmt), |
| "ordered region may not be closely nested inside " |
| "of critical or explicit task region"); |
| return false; |
| case GIMPLE_OMP_FOR: |
| if (find_omp_clause (gimple_omp_for_clauses (ctx->stmt), |
| OMP_CLAUSE_ORDERED) == NULL) |
| { |
| error_at (gimple_location (stmt), |
| "ordered region must be closely nested inside " |
| "a loop region with an ordered clause"); |
| return false; |
| } |
| return true; |
| case GIMPLE_OMP_PARALLEL: |
| error_at (gimple_location (stmt), |
| "ordered region must be closely nested inside " |
| "a loop region with an ordered clause"); |
| return false; |
| default: |
| break; |
| } |
| break; |
| case GIMPLE_OMP_CRITICAL: |
| { |
| tree this_stmt_name |
| = gimple_omp_critical_name (as_a <gomp_critical *> (stmt)); |
| for (; ctx != NULL; ctx = ctx->outer) |
| if (gomp_critical *other_crit |
| = dyn_cast <gomp_critical *> (ctx->stmt)) |
| if (this_stmt_name == gimple_omp_critical_name (other_crit)) |
| { |
| error_at (gimple_location (stmt), |
| "critical region may not be nested inside a critical " |
| "region with the same name"); |
| return false; |
| } |
| } |
| break; |
| case GIMPLE_OMP_TEAMS: |
| if (ctx == NULL |
| || gimple_code (ctx->stmt) != GIMPLE_OMP_TARGET |
| || gimple_omp_target_kind (ctx->stmt) != GF_OMP_TARGET_KIND_REGION) |
| { |
| error_at (gimple_location (stmt), |
| "teams construct not closely nested inside of target " |
| "region"); |
| return false; |
| } |
| break; |
| case GIMPLE_OMP_TARGET: |
| for (; ctx != NULL; ctx = ctx->outer) |
| { |
| if (gimple_code (ctx->stmt) != GIMPLE_OMP_TARGET) |
| { |
| if (is_gimple_omp (stmt) |
| && is_gimple_omp_oacc (stmt) |
| && is_gimple_omp (ctx->stmt)) |
| { |
| error_at (gimple_location (stmt), |
| "OpenACC construct inside of non-OpenACC region"); |
| return false; |
| } |
| continue; |
| } |
| |
| const char *stmt_name, *ctx_stmt_name; |
| switch (gimple_omp_target_kind (stmt)) |
| { |
| case GF_OMP_TARGET_KIND_REGION: stmt_name = "target"; break; |
| case GF_OMP_TARGET_KIND_DATA: stmt_name = "target data"; break; |
| case GF_OMP_TARGET_KIND_UPDATE: stmt_name = "target update"; break; |
| case GF_OMP_TARGET_KIND_OACC_PARALLEL: stmt_name = "parallel"; break; |
| case GF_OMP_TARGET_KIND_OACC_KERNELS: stmt_name = "kernels"; break; |
| case GF_OMP_TARGET_KIND_OACC_DATA: stmt_name = "data"; break; |
| case GF_OMP_TARGET_KIND_OACC_UPDATE: stmt_name = "update"; break; |
| case GF_OMP_TARGET_KIND_OACC_ENTER_EXIT_DATA: stmt_name = "enter/exit data"; break; |
| default: gcc_unreachable (); |
| } |
| switch (gimple_omp_target_kind (ctx->stmt)) |
| { |
| case GF_OMP_TARGET_KIND_REGION: ctx_stmt_name = "target"; break; |
| case GF_OMP_TARGET_KIND_DATA: ctx_stmt_name = "target data"; break; |
| case GF_OMP_TARGET_KIND_OACC_PARALLEL: ctx_stmt_name = "parallel"; break; |
| case GF_OMP_TARGET_KIND_OACC_KERNELS: ctx_stmt_name = "kernels"; break; |
| case GF_OMP_TARGET_KIND_OACC_DATA: ctx_stmt_name = "data"; break; |
| default: gcc_unreachable (); |
| } |
| |
| /* OpenACC/OpenMP mismatch? */ |
|