5898 lines
147 KiB
C
5898 lines
147 KiB
C
/* Pass manager for Fortran front end.
|
|
Copyright (C) 2010-2021 Free Software Foundation, Inc.
|
|
Contributed by Thomas König.
|
|
|
|
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 "options.h"
|
|
#include "gfortran.h"
|
|
#include "dependency.h"
|
|
#include "constructor.h"
|
|
#include "intrinsic.h"
|
|
|
|
/* Forward declarations. */
|
|
|
|
static void strip_function_call (gfc_expr *);
|
|
static void optimize_namespace (gfc_namespace *);
|
|
static void optimize_assignment (gfc_code *);
|
|
static bool optimize_op (gfc_expr *);
|
|
static bool optimize_comparison (gfc_expr *, gfc_intrinsic_op);
|
|
static bool optimize_trim (gfc_expr *);
|
|
static bool optimize_lexical_comparison (gfc_expr *);
|
|
static void optimize_minmaxloc (gfc_expr **);
|
|
static bool is_empty_string (gfc_expr *e);
|
|
static void doloop_warn (gfc_namespace *);
|
|
static int do_intent (gfc_expr **);
|
|
static int do_subscript (gfc_expr **);
|
|
static void optimize_reduction (gfc_namespace *);
|
|
static int callback_reduction (gfc_expr **, int *, void *);
|
|
static void realloc_strings (gfc_namespace *);
|
|
static gfc_expr *create_var (gfc_expr *, const char *vname=NULL);
|
|
static int matmul_to_var_expr (gfc_expr **, int *, void *);
|
|
static int matmul_to_var_code (gfc_code **, int *, void *);
|
|
static int inline_matmul_assign (gfc_code **, int *, void *);
|
|
static gfc_code * create_do_loop (gfc_expr *, gfc_expr *, gfc_expr *,
|
|
locus *, gfc_namespace *,
|
|
char *vname=NULL);
|
|
static gfc_expr* check_conjg_transpose_variable (gfc_expr *, bool *,
|
|
bool *);
|
|
static int call_external_blas (gfc_code **, int *, void *);
|
|
static int matmul_temp_args (gfc_code **, int *,void *data);
|
|
static int index_interchange (gfc_code **, int*, void *);
|
|
static bool is_fe_temp (gfc_expr *e);
|
|
|
|
#ifdef CHECKING_P
|
|
static void check_locus (gfc_namespace *);
|
|
#endif
|
|
|
|
/* How deep we are inside an argument list. */
|
|
|
|
static int count_arglist;
|
|
|
|
/* Vector of gfc_expr ** we operate on. */
|
|
|
|
static vec<gfc_expr **> expr_array;
|
|
|
|
/* Pointer to the gfc_code we currently work on - to be able to insert
|
|
a block before the statement. */
|
|
|
|
static gfc_code **current_code;
|
|
|
|
/* Pointer to the block to be inserted, and the statement we are
|
|
changing within the block. */
|
|
|
|
static gfc_code *inserted_block, **changed_statement;
|
|
|
|
/* The namespace we are currently dealing with. */
|
|
|
|
static gfc_namespace *current_ns;
|
|
|
|
/* If we are within any forall loop. */
|
|
|
|
static int forall_level;
|
|
|
|
/* Keep track of whether we are within an OMP workshare. */
|
|
|
|
static bool in_omp_workshare;
|
|
|
|
/* Keep track of whether we are within an OMP atomic. */
|
|
|
|
static bool in_omp_atomic;
|
|
|
|
/* Keep track of whether we are within a WHERE statement. */
|
|
|
|
static bool in_where;
|
|
|
|
/* Keep track of iterators for array constructors. */
|
|
|
|
static int iterator_level;
|
|
|
|
/* Keep track of DO loop levels. */
|
|
|
|
typedef struct {
|
|
gfc_code *c;
|
|
int branch_level;
|
|
bool seen_goto;
|
|
} do_t;
|
|
|
|
static vec<do_t> doloop_list;
|
|
static int doloop_level;
|
|
|
|
/* Keep track of if and select case levels. */
|
|
|
|
static int if_level;
|
|
static int select_level;
|
|
|
|
/* Vector of gfc_expr * to keep track of DO loops. */
|
|
|
|
struct my_struct *evec;
|
|
|
|
/* Keep track of association lists. */
|
|
|
|
static bool in_assoc_list;
|
|
|
|
/* Counter for temporary variables. */
|
|
|
|
static int var_num = 1;
|
|
|
|
/* What sort of matrix we are dealing with when inlining MATMUL. */
|
|
|
|
enum matrix_case { none=0, A2B2, A2B1, A1B2, A2B2T, A2TB2, A2TB2T };
|
|
|
|
/* Keep track of the number of expressions we have inserted so far
|
|
using create_var. */
|
|
|
|
int n_vars;
|
|
|
|
/* Entry point - run all passes for a namespace. */
|
|
|
|
void
|
|
gfc_run_passes (gfc_namespace *ns)
|
|
{
|
|
|
|
/* Warn about dubious DO loops where the index might
|
|
change. */
|
|
|
|
doloop_level = 0;
|
|
if_level = 0;
|
|
select_level = 0;
|
|
doloop_warn (ns);
|
|
doloop_list.release ();
|
|
int w, e;
|
|
|
|
#ifdef CHECKING_P
|
|
check_locus (ns);
|
|
#endif
|
|
|
|
gfc_get_errors (&w, &e);
|
|
if (e > 0)
|
|
return;
|
|
|
|
if (flag_frontend_optimize || flag_frontend_loop_interchange)
|
|
optimize_namespace (ns);
|
|
|
|
if (flag_frontend_optimize)
|
|
{
|
|
optimize_reduction (ns);
|
|
if (flag_dump_fortran_optimized)
|
|
gfc_dump_parse_tree (ns, stdout);
|
|
|
|
expr_array.release ();
|
|
}
|
|
|
|
if (flag_realloc_lhs)
|
|
realloc_strings (ns);
|
|
}
|
|
|
|
#ifdef CHECKING_P
|
|
|
|
/* Callback function: Warn if there is no location information in a
|
|
statement. */
|
|
|
|
static int
|
|
check_locus_code (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
current_code = c;
|
|
if (c && *c && (((*c)->loc.nextc == NULL) || ((*c)->loc.lb == NULL)))
|
|
gfc_warning_internal (0, "Inconsistent internal state: "
|
|
"No location in statement");
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Callback function: Warn if there is no location information in an
|
|
expression. */
|
|
|
|
static int
|
|
check_locus_expr (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
|
|
if (e && *e && (((*e)->where.nextc == NULL || (*e)->where.lb == NULL)))
|
|
gfc_warning_internal (0, "Inconsistent internal state: "
|
|
"No location in expression near %L",
|
|
&((*current_code)->loc));
|
|
return 0;
|
|
}
|
|
|
|
/* Run check for missing location information. */
|
|
|
|
static void
|
|
check_locus (gfc_namespace *ns)
|
|
{
|
|
gfc_code_walker (&ns->code, check_locus_code, check_locus_expr, NULL);
|
|
|
|
for (ns = ns->contained; ns; ns = ns->sibling)
|
|
{
|
|
if (ns->code == NULL || ns->code->op != EXEC_BLOCK)
|
|
check_locus (ns);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Callback for each gfc_code node invoked from check_realloc_strings.
|
|
For an allocatable LHS string which also appears as a variable on
|
|
the RHS, replace
|
|
|
|
a = a(x:y)
|
|
|
|
with
|
|
|
|
tmp = a(x:y)
|
|
a = tmp
|
|
*/
|
|
|
|
static int
|
|
realloc_string_callback (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_expr *expr1, *expr2;
|
|
gfc_code *co = *c;
|
|
gfc_expr *n;
|
|
gfc_ref *ref;
|
|
bool found_substr;
|
|
|
|
if (co->op != EXEC_ASSIGN)
|
|
return 0;
|
|
|
|
expr1 = co->expr1;
|
|
if (expr1->ts.type != BT_CHARACTER
|
|
|| !gfc_expr_attr(expr1).allocatable
|
|
|| !expr1->ts.deferred)
|
|
return 0;
|
|
|
|
if (is_fe_temp (expr1))
|
|
return 0;
|
|
|
|
expr2 = gfc_discard_nops (co->expr2);
|
|
|
|
if (expr2->expr_type == EXPR_VARIABLE)
|
|
{
|
|
found_substr = false;
|
|
for (ref = expr2->ref; ref; ref = ref->next)
|
|
{
|
|
if (ref->type == REF_SUBSTRING)
|
|
{
|
|
found_substr = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found_substr)
|
|
return 0;
|
|
}
|
|
else if (expr2->expr_type != EXPR_ARRAY
|
|
&& (expr2->expr_type != EXPR_OP
|
|
|| expr2->value.op.op != INTRINSIC_CONCAT))
|
|
return 0;
|
|
|
|
if (!gfc_check_dependency (expr1, expr2, true))
|
|
return 0;
|
|
|
|
/* gfc_check_dependency doesn't always pick up identical expressions.
|
|
However, eliminating the above sends the compiler into an infinite
|
|
loop on valid expressions. Without this check, the gimplifier emits
|
|
an ICE for a = a, where a is deferred character length. */
|
|
if (!gfc_dep_compare_expr (expr1, expr2))
|
|
return 0;
|
|
|
|
current_code = c;
|
|
inserted_block = NULL;
|
|
changed_statement = NULL;
|
|
n = create_var (expr2, "realloc_string");
|
|
co->expr2 = n;
|
|
return 0;
|
|
}
|
|
|
|
/* Callback for each gfc_code node invoked through gfc_code_walker
|
|
from optimize_namespace. */
|
|
|
|
static int
|
|
optimize_code (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
|
|
gfc_exec_op op;
|
|
|
|
op = (*c)->op;
|
|
|
|
if (op == EXEC_CALL || op == EXEC_COMPCALL || op == EXEC_ASSIGN_CALL
|
|
|| op == EXEC_CALL_PPC)
|
|
count_arglist = 1;
|
|
else
|
|
count_arglist = 0;
|
|
|
|
current_code = c;
|
|
inserted_block = NULL;
|
|
changed_statement = NULL;
|
|
|
|
if (op == EXEC_ASSIGN)
|
|
optimize_assignment (*c);
|
|
return 0;
|
|
}
|
|
|
|
/* Callback for each gfc_expr node invoked through gfc_code_walker
|
|
from optimize_namespace. */
|
|
|
|
static int
|
|
optimize_expr (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
bool function_expr;
|
|
|
|
if ((*e)->expr_type == EXPR_FUNCTION)
|
|
{
|
|
count_arglist ++;
|
|
function_expr = true;
|
|
}
|
|
else
|
|
function_expr = false;
|
|
|
|
if (optimize_trim (*e))
|
|
gfc_simplify_expr (*e, 0);
|
|
|
|
if (optimize_lexical_comparison (*e))
|
|
gfc_simplify_expr (*e, 0);
|
|
|
|
if ((*e)->expr_type == EXPR_OP && optimize_op (*e))
|
|
gfc_simplify_expr (*e, 0);
|
|
|
|
if ((*e)->expr_type == EXPR_FUNCTION && (*e)->value.function.isym)
|
|
switch ((*e)->value.function.isym->id)
|
|
{
|
|
case GFC_ISYM_MINLOC:
|
|
case GFC_ISYM_MAXLOC:
|
|
optimize_minmaxloc (e);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (function_expr)
|
|
count_arglist --;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Auxiliary function to handle the arguments to reduction intrnisics. If the
|
|
function is a scalar, just copy it; otherwise returns the new element, the
|
|
old one can be freed. */
|
|
|
|
static gfc_expr *
|
|
copy_walk_reduction_arg (gfc_constructor *c, gfc_expr *fn)
|
|
{
|
|
gfc_expr *fcn, *e = c->expr;
|
|
|
|
fcn = gfc_copy_expr (e);
|
|
if (c->iterator)
|
|
{
|
|
gfc_constructor_base newbase;
|
|
gfc_expr *new_expr;
|
|
gfc_constructor *new_c;
|
|
|
|
newbase = NULL;
|
|
new_expr = gfc_get_expr ();
|
|
new_expr->expr_type = EXPR_ARRAY;
|
|
new_expr->ts = e->ts;
|
|
new_expr->where = e->where;
|
|
new_expr->rank = 1;
|
|
new_c = gfc_constructor_append_expr (&newbase, fcn, &(e->where));
|
|
new_c->iterator = c->iterator;
|
|
new_expr->value.constructor = newbase;
|
|
c->iterator = NULL;
|
|
|
|
fcn = new_expr;
|
|
}
|
|
|
|
if (fcn->rank != 0)
|
|
{
|
|
gfc_isym_id id = fn->value.function.isym->id;
|
|
|
|
if (id == GFC_ISYM_SUM || id == GFC_ISYM_PRODUCT)
|
|
fcn = gfc_build_intrinsic_call (current_ns, id,
|
|
fn->value.function.isym->name,
|
|
fn->where, 3, fcn, NULL, NULL);
|
|
else if (id == GFC_ISYM_ANY || id == GFC_ISYM_ALL)
|
|
fcn = gfc_build_intrinsic_call (current_ns, id,
|
|
fn->value.function.isym->name,
|
|
fn->where, 2, fcn, NULL);
|
|
else
|
|
gfc_internal_error ("Illegal id in copy_walk_reduction_arg");
|
|
|
|
fcn->symtree->n.sym->attr.access = ACCESS_PRIVATE;
|
|
}
|
|
|
|
return fcn;
|
|
}
|
|
|
|
/* Callback function for optimzation of reductions to scalars. Transform ANY
|
|
([f1,f2,f3, ...]) to f1 .or. f2 .or. f3 .or. ..., with ANY, SUM and PRODUCT
|
|
correspondingly. Handly only the simple cases without MASK and DIM. */
|
|
|
|
static int
|
|
callback_reduction (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_expr *fn, *arg;
|
|
gfc_intrinsic_op op;
|
|
gfc_isym_id id;
|
|
gfc_actual_arglist *a;
|
|
gfc_actual_arglist *dim;
|
|
gfc_constructor *c;
|
|
gfc_expr *res, *new_expr;
|
|
gfc_actual_arglist *mask;
|
|
|
|
fn = *e;
|
|
|
|
if (fn->rank != 0 || fn->expr_type != EXPR_FUNCTION
|
|
|| fn->value.function.isym == NULL)
|
|
return 0;
|
|
|
|
id = fn->value.function.isym->id;
|
|
|
|
if (id != GFC_ISYM_SUM && id != GFC_ISYM_PRODUCT
|
|
&& id != GFC_ISYM_ANY && id != GFC_ISYM_ALL)
|
|
return 0;
|
|
|
|
a = fn->value.function.actual;
|
|
|
|
/* Don't handle MASK or DIM. */
|
|
|
|
dim = a->next;
|
|
|
|
if (dim->expr != NULL)
|
|
return 0;
|
|
|
|
if (id == GFC_ISYM_SUM || id == GFC_ISYM_PRODUCT)
|
|
{
|
|
mask = dim->next;
|
|
if ( mask->expr != NULL)
|
|
return 0;
|
|
}
|
|
|
|
arg = a->expr;
|
|
|
|
if (arg->expr_type != EXPR_ARRAY)
|
|
return 0;
|
|
|
|
switch (id)
|
|
{
|
|
case GFC_ISYM_SUM:
|
|
op = INTRINSIC_PLUS;
|
|
break;
|
|
|
|
case GFC_ISYM_PRODUCT:
|
|
op = INTRINSIC_TIMES;
|
|
break;
|
|
|
|
case GFC_ISYM_ANY:
|
|
op = INTRINSIC_OR;
|
|
break;
|
|
|
|
case GFC_ISYM_ALL:
|
|
op = INTRINSIC_AND;
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
c = gfc_constructor_first (arg->value.constructor);
|
|
|
|
/* Don't do any simplififcation if we have
|
|
- no element in the constructor or
|
|
- only have a single element in the array which contains an
|
|
iterator. */
|
|
|
|
if (c == NULL)
|
|
return 0;
|
|
|
|
res = copy_walk_reduction_arg (c, fn);
|
|
|
|
c = gfc_constructor_next (c);
|
|
while (c)
|
|
{
|
|
new_expr = gfc_get_expr ();
|
|
new_expr->ts = fn->ts;
|
|
new_expr->expr_type = EXPR_OP;
|
|
new_expr->rank = fn->rank;
|
|
new_expr->where = fn->where;
|
|
new_expr->value.op.op = op;
|
|
new_expr->value.op.op1 = res;
|
|
new_expr->value.op.op2 = copy_walk_reduction_arg (c, fn);
|
|
res = new_expr;
|
|
c = gfc_constructor_next (c);
|
|
}
|
|
|
|
gfc_simplify_expr (res, 0);
|
|
*e = res;
|
|
gfc_free_expr (fn);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Callback function for common function elimination, called from cfe_expr_0.
|
|
Put all eligible function expressions into expr_array. */
|
|
|
|
static int
|
|
cfe_register_funcs (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
|
|
if ((*e)->expr_type != EXPR_FUNCTION)
|
|
return 0;
|
|
|
|
/* We don't do character functions with unknown charlens. */
|
|
if ((*e)->ts.type == BT_CHARACTER
|
|
&& ((*e)->ts.u.cl == NULL || (*e)->ts.u.cl->length == NULL
|
|
|| (*e)->ts.u.cl->length->expr_type != EXPR_CONSTANT))
|
|
return 0;
|
|
|
|
/* We don't do function elimination within FORALL statements, it can
|
|
lead to wrong-code in certain circumstances. */
|
|
|
|
if (forall_level > 0)
|
|
return 0;
|
|
|
|
/* Function elimination inside an iterator could lead to functions which
|
|
depend on iterator variables being moved outside. FIXME: We should check
|
|
if the functions do indeed depend on the iterator variable. */
|
|
|
|
if (iterator_level > 0)
|
|
return 0;
|
|
|
|
/* If we don't know the shape at compile time, we create an allocatable
|
|
temporary variable to hold the intermediate result, but only if
|
|
allocation on assignment is active. */
|
|
|
|
if ((*e)->rank > 0 && (*e)->shape == NULL && !flag_realloc_lhs)
|
|
return 0;
|
|
|
|
/* Skip the test for pure functions if -faggressive-function-elimination
|
|
is specified. */
|
|
if ((*e)->value.function.esym)
|
|
{
|
|
/* Don't create an array temporary for elemental functions. */
|
|
if ((*e)->value.function.esym->attr.elemental && (*e)->rank > 0)
|
|
return 0;
|
|
|
|
/* Only eliminate potentially impure functions if the
|
|
user specifically requested it. */
|
|
if (!flag_aggressive_function_elimination
|
|
&& !(*e)->value.function.esym->attr.pure
|
|
&& !(*e)->value.function.esym->attr.implicit_pure)
|
|
return 0;
|
|
}
|
|
|
|
if ((*e)->value.function.isym)
|
|
{
|
|
/* Conversions are handled on the fly by the middle end,
|
|
transpose during trans-* stages and TRANSFER by the middle end. */
|
|
if ((*e)->value.function.isym->id == GFC_ISYM_CONVERSION
|
|
|| (*e)->value.function.isym->id == GFC_ISYM_TRANSFER
|
|
|| gfc_inline_intrinsic_function_p (*e))
|
|
return 0;
|
|
|
|
/* Don't create an array temporary for elemental functions,
|
|
as this would be wasteful of memory.
|
|
FIXME: Create a scalar temporary during scalarization. */
|
|
if ((*e)->value.function.isym->elemental && (*e)->rank > 0)
|
|
return 0;
|
|
|
|
if (!(*e)->value.function.isym->pure)
|
|
return 0;
|
|
}
|
|
|
|
expr_array.safe_push (e);
|
|
return 0;
|
|
}
|
|
|
|
/* Auxiliary function to check if an expression is a temporary created by
|
|
create var. */
|
|
|
|
static bool
|
|
is_fe_temp (gfc_expr *e)
|
|
{
|
|
if (e->expr_type != EXPR_VARIABLE)
|
|
return false;
|
|
|
|
return e->symtree->n.sym->attr.fe_temp;
|
|
}
|
|
|
|
/* Determine the length of a string, if it can be evaluated as a constant
|
|
expression. Return a newly allocated gfc_expr or NULL on failure.
|
|
If the user specified a substring which is potentially longer than
|
|
the string itself, the string will be padded with spaces, which
|
|
is harmless. */
|
|
|
|
static gfc_expr *
|
|
constant_string_length (gfc_expr *e)
|
|
{
|
|
|
|
gfc_expr *length;
|
|
gfc_ref *ref;
|
|
gfc_expr *res;
|
|
mpz_t value;
|
|
|
|
if (e->ts.u.cl)
|
|
{
|
|
length = e->ts.u.cl->length;
|
|
if (length && length->expr_type == EXPR_CONSTANT)
|
|
return gfc_copy_expr(length);
|
|
}
|
|
|
|
/* See if there is a substring. If it has a constant length, return
|
|
that and NULL otherwise. */
|
|
for (ref = e->ref; ref; ref = ref->next)
|
|
{
|
|
if (ref->type == REF_SUBSTRING)
|
|
{
|
|
if (gfc_dep_difference (ref->u.ss.end, ref->u.ss.start, &value))
|
|
{
|
|
res = gfc_get_constant_expr (BT_INTEGER, gfc_charlen_int_kind,
|
|
&e->where);
|
|
|
|
mpz_add_ui (res->value.integer, value, 1);
|
|
mpz_clear (value);
|
|
return res;
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Return length of char symbol, if constant. */
|
|
if (e->symtree && e->symtree->n.sym->ts.u.cl
|
|
&& e->symtree->n.sym->ts.u.cl->length
|
|
&& e->symtree->n.sym->ts.u.cl->length->expr_type == EXPR_CONSTANT)
|
|
return gfc_copy_expr (e->symtree->n.sym->ts.u.cl->length);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
/* Insert a block at the current position unless it has already
|
|
been inserted; in this case use the one already there. */
|
|
|
|
static gfc_namespace*
|
|
insert_block ()
|
|
{
|
|
gfc_namespace *ns;
|
|
|
|
/* If the block hasn't already been created, do so. */
|
|
if (inserted_block == NULL)
|
|
{
|
|
inserted_block = XCNEW (gfc_code);
|
|
inserted_block->op = EXEC_BLOCK;
|
|
inserted_block->loc = (*current_code)->loc;
|
|
ns = gfc_build_block_ns (current_ns);
|
|
inserted_block->ext.block.ns = ns;
|
|
inserted_block->ext.block.assoc = NULL;
|
|
|
|
ns->code = *current_code;
|
|
|
|
/* If the statement has a label, make sure it is transferred to
|
|
the newly created block. */
|
|
|
|
if ((*current_code)->here)
|
|
{
|
|
inserted_block->here = (*current_code)->here;
|
|
(*current_code)->here = NULL;
|
|
}
|
|
|
|
inserted_block->next = (*current_code)->next;
|
|
changed_statement = &(inserted_block->ext.block.ns->code);
|
|
(*current_code)->next = NULL;
|
|
/* Insert the BLOCK at the right position. */
|
|
*current_code = inserted_block;
|
|
ns->parent = current_ns;
|
|
}
|
|
else
|
|
ns = inserted_block->ext.block.ns;
|
|
|
|
return ns;
|
|
}
|
|
|
|
|
|
/* Insert a call to the intrinsic len. Use a different name for
|
|
the symbol tree so we don't run into trouble when the user has
|
|
renamed len for some reason. */
|
|
|
|
static gfc_expr*
|
|
get_len_call (gfc_expr *str)
|
|
{
|
|
gfc_expr *fcn;
|
|
gfc_actual_arglist *actual_arglist;
|
|
|
|
fcn = gfc_get_expr ();
|
|
fcn->expr_type = EXPR_FUNCTION;
|
|
fcn->value.function.isym = gfc_intrinsic_function_by_id (GFC_ISYM_LEN);
|
|
actual_arglist = gfc_get_actual_arglist ();
|
|
actual_arglist->expr = str;
|
|
|
|
fcn->value.function.actual = actual_arglist;
|
|
fcn->where = str->where;
|
|
fcn->ts.type = BT_INTEGER;
|
|
fcn->ts.kind = gfc_charlen_int_kind;
|
|
|
|
gfc_get_sym_tree ("__internal_len", current_ns, &fcn->symtree, false);
|
|
fcn->symtree->n.sym->ts = fcn->ts;
|
|
fcn->symtree->n.sym->attr.flavor = FL_PROCEDURE;
|
|
fcn->symtree->n.sym->attr.function = 1;
|
|
fcn->symtree->n.sym->attr.elemental = 1;
|
|
fcn->symtree->n.sym->attr.referenced = 1;
|
|
fcn->symtree->n.sym->attr.access = ACCESS_PRIVATE;
|
|
gfc_commit_symbol (fcn->symtree->n.sym);
|
|
|
|
return fcn;
|
|
}
|
|
|
|
|
|
/* Returns a new expression (a variable) to be used in place of the old one,
|
|
with an optional assignment statement before the current statement to set
|
|
the value of the variable. Creates a new BLOCK for the statement if that
|
|
hasn't already been done and puts the statement, plus the newly created
|
|
variables, in that block. Special cases: If the expression is constant or
|
|
a temporary which has already been created, just copy it. */
|
|
|
|
static gfc_expr*
|
|
create_var (gfc_expr * e, const char *vname)
|
|
{
|
|
char name[GFC_MAX_SYMBOL_LEN +1];
|
|
gfc_symtree *symtree;
|
|
gfc_symbol *symbol;
|
|
gfc_expr *result;
|
|
gfc_code *n;
|
|
gfc_namespace *ns;
|
|
int i;
|
|
bool deferred;
|
|
|
|
if (e->expr_type == EXPR_CONSTANT || is_fe_temp (e))
|
|
return gfc_copy_expr (e);
|
|
|
|
/* Creation of an array of unknown size requires realloc on assignment.
|
|
If that is not possible, just return NULL. */
|
|
if (flag_realloc_lhs == 0 && e->rank > 0 && e->shape == NULL)
|
|
return NULL;
|
|
|
|
ns = insert_block ();
|
|
|
|
if (vname)
|
|
snprintf (name, GFC_MAX_SYMBOL_LEN, "__var_%d_%s", var_num++, vname);
|
|
else
|
|
snprintf (name, GFC_MAX_SYMBOL_LEN, "__var_%d", var_num++);
|
|
|
|
if (gfc_get_sym_tree (name, ns, &symtree, false) != 0)
|
|
gcc_unreachable ();
|
|
|
|
symbol = symtree->n.sym;
|
|
symbol->ts = e->ts;
|
|
|
|
if (e->rank > 0)
|
|
{
|
|
symbol->as = gfc_get_array_spec ();
|
|
symbol->as->rank = e->rank;
|
|
|
|
if (e->shape == NULL)
|
|
{
|
|
/* We don't know the shape at compile time, so we use an
|
|
allocatable. */
|
|
symbol->as->type = AS_DEFERRED;
|
|
symbol->attr.allocatable = 1;
|
|
}
|
|
else
|
|
{
|
|
symbol->as->type = AS_EXPLICIT;
|
|
/* Copy the shape. */
|
|
for (i=0; i<e->rank; i++)
|
|
{
|
|
gfc_expr *p, *q;
|
|
|
|
p = gfc_get_constant_expr (BT_INTEGER, gfc_default_integer_kind,
|
|
&(e->where));
|
|
mpz_set_si (p->value.integer, 1);
|
|
symbol->as->lower[i] = p;
|
|
|
|
q = gfc_get_constant_expr (BT_INTEGER, gfc_index_integer_kind,
|
|
&(e->where));
|
|
mpz_set (q->value.integer, e->shape[i]);
|
|
symbol->as->upper[i] = q;
|
|
}
|
|
}
|
|
}
|
|
|
|
deferred = 0;
|
|
if (e->ts.type == BT_CHARACTER)
|
|
{
|
|
gfc_expr *length;
|
|
|
|
symbol->ts.u.cl = gfc_new_charlen (ns, NULL);
|
|
length = constant_string_length (e);
|
|
if (length)
|
|
symbol->ts.u.cl->length = length;
|
|
else if (e->expr_type == EXPR_VARIABLE
|
|
&& e->symtree->n.sym->ts.type == BT_CHARACTER
|
|
&& e->ts.u.cl->length)
|
|
symbol->ts.u.cl->length = get_len_call (gfc_copy_expr (e));
|
|
else
|
|
{
|
|
symbol->attr.allocatable = 1;
|
|
symbol->ts.u.cl->length = NULL;
|
|
symbol->ts.deferred = 1;
|
|
deferred = 1;
|
|
}
|
|
}
|
|
|
|
symbol->attr.flavor = FL_VARIABLE;
|
|
symbol->attr.referenced = 1;
|
|
symbol->attr.dimension = e->rank > 0;
|
|
symbol->attr.fe_temp = 1;
|
|
gfc_commit_symbol (symbol);
|
|
|
|
result = gfc_get_expr ();
|
|
result->expr_type = EXPR_VARIABLE;
|
|
result->ts = symbol->ts;
|
|
result->ts.deferred = deferred;
|
|
result->rank = e->rank;
|
|
result->shape = gfc_copy_shape (e->shape, e->rank);
|
|
result->symtree = symtree;
|
|
result->where = e->where;
|
|
if (e->rank > 0)
|
|
{
|
|
result->ref = gfc_get_ref ();
|
|
result->ref->type = REF_ARRAY;
|
|
result->ref->u.ar.type = AR_FULL;
|
|
result->ref->u.ar.where = e->where;
|
|
result->ref->u.ar.dimen = e->rank;
|
|
result->ref->u.ar.as = symbol->ts.type == BT_CLASS
|
|
? CLASS_DATA (symbol)->as : symbol->as;
|
|
if (warn_array_temporaries)
|
|
gfc_warning (OPT_Warray_temporaries,
|
|
"Creating array temporary at %L", &(e->where));
|
|
}
|
|
|
|
/* Generate the new assignment. */
|
|
n = XCNEW (gfc_code);
|
|
n->op = EXEC_ASSIGN;
|
|
n->loc = (*current_code)->loc;
|
|
n->next = *changed_statement;
|
|
n->expr1 = gfc_copy_expr (result);
|
|
n->expr2 = e;
|
|
*changed_statement = n;
|
|
n_vars ++;
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Warn about function elimination. */
|
|
|
|
static void
|
|
do_warn_function_elimination (gfc_expr *e)
|
|
{
|
|
const char *name;
|
|
if (e->expr_type == EXPR_FUNCTION
|
|
&& !gfc_pure_function (e, &name) && !gfc_implicit_pure_function (e))
|
|
{
|
|
if (name)
|
|
gfc_warning (OPT_Wfunction_elimination,
|
|
"Removing call to impure function %qs at %L", name,
|
|
&(e->where));
|
|
else
|
|
gfc_warning (OPT_Wfunction_elimination,
|
|
"Removing call to impure function at %L",
|
|
&(e->where));
|
|
}
|
|
}
|
|
|
|
|
|
/* Callback function for the code walker for doing common function
|
|
elimination. This builds up the list of functions in the expression
|
|
and goes through them to detect duplicates, which it then replaces
|
|
by variables. */
|
|
|
|
static int
|
|
cfe_expr_0 (gfc_expr **e, int *walk_subtrees,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
int i,j;
|
|
gfc_expr *newvar;
|
|
gfc_expr **ei, **ej;
|
|
|
|
/* Don't do this optimization within OMP workshare/atomic or ASSOC lists. */
|
|
|
|
if (in_omp_workshare || in_omp_atomic || in_assoc_list)
|
|
{
|
|
*walk_subtrees = 0;
|
|
return 0;
|
|
}
|
|
|
|
expr_array.release ();
|
|
|
|
gfc_expr_walker (e, cfe_register_funcs, NULL);
|
|
|
|
/* Walk through all the functions. */
|
|
|
|
FOR_EACH_VEC_ELT_FROM (expr_array, i, ei, 1)
|
|
{
|
|
/* Skip if the function has been replaced by a variable already. */
|
|
if ((*ei)->expr_type == EXPR_VARIABLE)
|
|
continue;
|
|
|
|
newvar = NULL;
|
|
for (j=0; j<i; j++)
|
|
{
|
|
ej = expr_array[j];
|
|
if (gfc_dep_compare_functions (*ei, *ej, true) == 0)
|
|
{
|
|
if (newvar == NULL)
|
|
newvar = create_var (*ei, "fcn");
|
|
|
|
if (warn_function_elimination)
|
|
do_warn_function_elimination (*ej);
|
|
|
|
free (*ej);
|
|
*ej = gfc_copy_expr (newvar);
|
|
}
|
|
}
|
|
if (newvar)
|
|
*ei = newvar;
|
|
}
|
|
|
|
/* We did all the necessary walking in this function. */
|
|
*walk_subtrees = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Callback function for common function elimination, called from
|
|
gfc_code_walker. This keeps track of the current code, in order
|
|
to insert statements as needed. */
|
|
|
|
static int
|
|
cfe_code (gfc_code **c, int *walk_subtrees, void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
current_code = c;
|
|
inserted_block = NULL;
|
|
changed_statement = NULL;
|
|
|
|
/* Do not do anything inside a WHERE statement; scalar assignments, BLOCKs
|
|
and allocation on assigment are prohibited inside WHERE, and finally
|
|
masking an expression would lead to wrong-code when replacing
|
|
|
|
WHERE (a>0)
|
|
b = sum(foo(a) + foo(a))
|
|
END WHERE
|
|
|
|
with
|
|
|
|
WHERE (a > 0)
|
|
tmp = foo(a)
|
|
b = sum(tmp + tmp)
|
|
END WHERE
|
|
*/
|
|
|
|
if ((*c)->op == EXEC_WHERE)
|
|
{
|
|
*walk_subtrees = 0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Dummy function for expression call back, for use when we
|
|
really don't want to do any walking. */
|
|
|
|
static int
|
|
dummy_expr_callback (gfc_expr **e ATTRIBUTE_UNUSED, int *walk_subtrees,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
*walk_subtrees = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Dummy function for code callback, for use when we really
|
|
don't want to do anything. */
|
|
int
|
|
gfc_dummy_code_callback (gfc_code **e ATTRIBUTE_UNUSED,
|
|
int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/* Code callback function for converting
|
|
do while(a)
|
|
end do
|
|
into the equivalent
|
|
do
|
|
if (.not. a) exit
|
|
end do
|
|
This is because common function elimination would otherwise place the
|
|
temporary variables outside the loop. */
|
|
|
|
static int
|
|
convert_do_while (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_code *co = *c;
|
|
gfc_code *c_if1, *c_if2, *c_exit;
|
|
gfc_code *loopblock;
|
|
gfc_expr *e_not, *e_cond;
|
|
|
|
if (co->op != EXEC_DO_WHILE)
|
|
return 0;
|
|
|
|
if (co->expr1 == NULL || co->expr1->expr_type == EXPR_CONSTANT)
|
|
return 0;
|
|
|
|
e_cond = co->expr1;
|
|
|
|
/* Generate the condition of the if statement, which is .not. the original
|
|
statement. */
|
|
e_not = gfc_get_expr ();
|
|
e_not->ts = e_cond->ts;
|
|
e_not->where = e_cond->where;
|
|
e_not->expr_type = EXPR_OP;
|
|
e_not->value.op.op = INTRINSIC_NOT;
|
|
e_not->value.op.op1 = e_cond;
|
|
|
|
/* Generate the EXIT statement. */
|
|
c_exit = XCNEW (gfc_code);
|
|
c_exit->op = EXEC_EXIT;
|
|
c_exit->ext.which_construct = co;
|
|
c_exit->loc = co->loc;
|
|
|
|
/* Generate the IF statement. */
|
|
c_if2 = XCNEW (gfc_code);
|
|
c_if2->op = EXEC_IF;
|
|
c_if2->expr1 = e_not;
|
|
c_if2->next = c_exit;
|
|
c_if2->loc = co->loc;
|
|
|
|
/* ... plus the one to chain it to. */
|
|
c_if1 = XCNEW (gfc_code);
|
|
c_if1->op = EXEC_IF;
|
|
c_if1->block = c_if2;
|
|
c_if1->loc = co->loc;
|
|
|
|
/* Make the DO WHILE loop into a DO block by replacing the condition
|
|
with a true constant. */
|
|
co->expr1 = gfc_get_logical_expr (gfc_default_integer_kind, &co->loc, true);
|
|
|
|
/* Hang the generated if statement into the loop body. */
|
|
|
|
loopblock = co->block->next;
|
|
co->block->next = c_if1;
|
|
c_if1->next = loopblock;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Code callback function for converting
|
|
if (a) then
|
|
...
|
|
else if (b) then
|
|
end if
|
|
|
|
into
|
|
if (a) then
|
|
else
|
|
if (b) then
|
|
end if
|
|
end if
|
|
|
|
because otherwise common function elimination would place the BLOCKs
|
|
into the wrong place. */
|
|
|
|
static int
|
|
convert_elseif (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_code *co = *c;
|
|
gfc_code *c_if1, *c_if2, *else_stmt;
|
|
|
|
if (co->op != EXEC_IF)
|
|
return 0;
|
|
|
|
/* This loop starts out with the first ELSE statement. */
|
|
else_stmt = co->block->block;
|
|
|
|
while (else_stmt != NULL)
|
|
{
|
|
gfc_code *next_else;
|
|
|
|
/* If there is no condition, we're done. */
|
|
if (else_stmt->expr1 == NULL)
|
|
break;
|
|
|
|
next_else = else_stmt->block;
|
|
|
|
/* Generate the new IF statement. */
|
|
c_if2 = XCNEW (gfc_code);
|
|
c_if2->op = EXEC_IF;
|
|
c_if2->expr1 = else_stmt->expr1;
|
|
c_if2->next = else_stmt->next;
|
|
c_if2->loc = else_stmt->loc;
|
|
c_if2->block = next_else;
|
|
|
|
/* ... plus the one to chain it to. */
|
|
c_if1 = XCNEW (gfc_code);
|
|
c_if1->op = EXEC_IF;
|
|
c_if1->block = c_if2;
|
|
c_if1->loc = else_stmt->loc;
|
|
|
|
/* Insert the new IF after the ELSE. */
|
|
else_stmt->expr1 = NULL;
|
|
else_stmt->next = c_if1;
|
|
else_stmt->block = NULL;
|
|
|
|
else_stmt = next_else;
|
|
}
|
|
/* Don't walk subtrees. */
|
|
return 0;
|
|
}
|
|
|
|
/* Callback function to var_in_expr - return true if expr1 and
|
|
expr2 are identical variables. */
|
|
static int
|
|
var_in_expr_callback (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data)
|
|
{
|
|
gfc_expr *expr1 = (gfc_expr *) data;
|
|
gfc_expr *expr2 = *e;
|
|
|
|
if (expr2->expr_type != EXPR_VARIABLE)
|
|
return 0;
|
|
|
|
return expr1->symtree->n.sym == expr2->symtree->n.sym;
|
|
}
|
|
|
|
/* Return true if expr1 is found in expr2. */
|
|
|
|
static bool
|
|
var_in_expr (gfc_expr *expr1, gfc_expr *expr2)
|
|
{
|
|
gcc_assert (expr1->expr_type == EXPR_VARIABLE);
|
|
|
|
return gfc_expr_walker (&expr2, var_in_expr_callback, (void *) expr1);
|
|
}
|
|
|
|
struct do_stack
|
|
{
|
|
struct do_stack *prev;
|
|
gfc_iterator *iter;
|
|
gfc_code *code;
|
|
} *stack_top;
|
|
|
|
/* Recursively traverse the block of a WRITE or READ statement, and maybe
|
|
optimize by replacing do loops with their analog array slices. For
|
|
example:
|
|
|
|
write (*,*) (a(i), i=1,4)
|
|
|
|
is replaced with
|
|
|
|
write (*,*) a(1:4:1) . */
|
|
|
|
static bool
|
|
traverse_io_block (gfc_code *code, bool *has_reached, gfc_code *prev)
|
|
{
|
|
gfc_code *curr;
|
|
gfc_expr *new_e, *expr, *start;
|
|
gfc_ref *ref;
|
|
struct do_stack ds_push;
|
|
int i, future_rank = 0;
|
|
gfc_iterator *iters[GFC_MAX_DIMENSIONS];
|
|
gfc_expr *e;
|
|
|
|
/* Find the first transfer/do statement. */
|
|
for (curr = code; curr; curr = curr->next)
|
|
{
|
|
if (curr->op == EXEC_DO || curr->op == EXEC_TRANSFER)
|
|
break;
|
|
}
|
|
|
|
/* Ensure it is the only transfer/do statement because cases like
|
|
|
|
write (*,*) (a(i), b(i), i=1,4)
|
|
|
|
cannot be optimized. */
|
|
|
|
if (!curr || curr->next)
|
|
return false;
|
|
|
|
if (curr->op == EXEC_DO)
|
|
{
|
|
if (curr->ext.iterator->var->ref)
|
|
return false;
|
|
ds_push.prev = stack_top;
|
|
ds_push.iter = curr->ext.iterator;
|
|
ds_push.code = curr;
|
|
stack_top = &ds_push;
|
|
if (traverse_io_block (curr->block->next, has_reached, prev))
|
|
{
|
|
if (curr != stack_top->code && !*has_reached)
|
|
{
|
|
curr->block->next = NULL;
|
|
gfc_free_statements (curr);
|
|
}
|
|
else
|
|
*has_reached = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
gcc_assert (curr->op == EXEC_TRANSFER);
|
|
|
|
e = curr->expr1;
|
|
ref = e->ref;
|
|
if (!ref || ref->type != REF_ARRAY || ref->u.ar.codimen != 0 || ref->next)
|
|
return false;
|
|
|
|
/* Find the iterators belonging to each variable and check conditions. */
|
|
for (i = 0; i < ref->u.ar.dimen; i++)
|
|
{
|
|
if (!ref->u.ar.start[i] || ref->u.ar.start[i]->ref
|
|
|| ref->u.ar.dimen_type[i] != DIMEN_ELEMENT)
|
|
return false;
|
|
|
|
start = ref->u.ar.start[i];
|
|
gfc_simplify_expr (start, 0);
|
|
switch (start->expr_type)
|
|
{
|
|
case EXPR_VARIABLE:
|
|
|
|
/* write (*,*) (a(i), i=a%b,1) not handled yet. */
|
|
if (start->ref)
|
|
return false;
|
|
|
|
/* Check for (a(k), i=1,4) or ((a(j, i), i=1,4), j=1,4). */
|
|
if (!stack_top || !stack_top->iter
|
|
|| stack_top->iter->var->symtree != start->symtree)
|
|
{
|
|
/* Check for (a(i,i), i=1,3). */
|
|
int j;
|
|
|
|
for (j=0; j<i; j++)
|
|
if (iters[j] && iters[j]->var->symtree == start->symtree)
|
|
return false;
|
|
|
|
iters[i] = NULL;
|
|
}
|
|
else
|
|
{
|
|
iters[i] = stack_top->iter;
|
|
stack_top = stack_top->prev;
|
|
future_rank++;
|
|
}
|
|
break;
|
|
case EXPR_CONSTANT:
|
|
iters[i] = NULL;
|
|
break;
|
|
case EXPR_OP:
|
|
switch (start->value.op.op)
|
|
{
|
|
case INTRINSIC_PLUS:
|
|
case INTRINSIC_TIMES:
|
|
if (start->value.op.op1->expr_type != EXPR_VARIABLE)
|
|
std::swap (start->value.op.op1, start->value.op.op2);
|
|
gcc_fallthrough ();
|
|
case INTRINSIC_MINUS:
|
|
if ((start->value.op.op1->expr_type!= EXPR_VARIABLE
|
|
&& start->value.op.op2->expr_type != EXPR_CONSTANT)
|
|
|| start->value.op.op1->ref)
|
|
return false;
|
|
if (!stack_top || !stack_top->iter
|
|
|| stack_top->iter->var->symtree
|
|
!= start->value.op.op1->symtree)
|
|
return false;
|
|
iters[i] = stack_top->iter;
|
|
stack_top = stack_top->prev;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
future_rank++;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Check for cases like ((a(i, j), i=1, j), j=1, 2). */
|
|
for (int i = 1; i < ref->u.ar.dimen; i++)
|
|
{
|
|
if (iters[i])
|
|
{
|
|
gfc_expr *var = iters[i]->var;
|
|
for (int j = i - 1; j < i; j++)
|
|
{
|
|
if (iters[j]
|
|
&& (var_in_expr (var, iters[j]->start)
|
|
|| var_in_expr (var, iters[j]->end)
|
|
|| var_in_expr (var, iters[j]->step)))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Create new expr. */
|
|
new_e = gfc_copy_expr (curr->expr1);
|
|
new_e->expr_type = EXPR_VARIABLE;
|
|
new_e->rank = future_rank;
|
|
if (curr->expr1->shape)
|
|
new_e->shape = gfc_get_shape (new_e->rank);
|
|
|
|
/* Assign new starts, ends and strides if necessary. */
|
|
for (i = 0; i < ref->u.ar.dimen; i++)
|
|
{
|
|
if (!iters[i])
|
|
continue;
|
|
start = ref->u.ar.start[i];
|
|
switch (start->expr_type)
|
|
{
|
|
case EXPR_CONSTANT:
|
|
gfc_internal_error ("bad expression");
|
|
break;
|
|
case EXPR_VARIABLE:
|
|
new_e->ref->u.ar.dimen_type[i] = DIMEN_RANGE;
|
|
new_e->ref->u.ar.type = AR_SECTION;
|
|
gfc_free_expr (new_e->ref->u.ar.start[i]);
|
|
new_e->ref->u.ar.start[i] = gfc_copy_expr (iters[i]->start);
|
|
new_e->ref->u.ar.end[i] = gfc_copy_expr (iters[i]->end);
|
|
new_e->ref->u.ar.stride[i] = gfc_copy_expr (iters[i]->step);
|
|
break;
|
|
case EXPR_OP:
|
|
new_e->ref->u.ar.dimen_type[i] = DIMEN_RANGE;
|
|
new_e->ref->u.ar.type = AR_SECTION;
|
|
gfc_free_expr (new_e->ref->u.ar.start[i]);
|
|
expr = gfc_copy_expr (start);
|
|
expr->value.op.op1 = gfc_copy_expr (iters[i]->start);
|
|
new_e->ref->u.ar.start[i] = expr;
|
|
gfc_simplify_expr (new_e->ref->u.ar.start[i], 0);
|
|
expr = gfc_copy_expr (start);
|
|
expr->value.op.op1 = gfc_copy_expr (iters[i]->end);
|
|
new_e->ref->u.ar.end[i] = expr;
|
|
gfc_simplify_expr (new_e->ref->u.ar.end[i], 0);
|
|
switch (start->value.op.op)
|
|
{
|
|
case INTRINSIC_MINUS:
|
|
case INTRINSIC_PLUS:
|
|
new_e->ref->u.ar.stride[i] = gfc_copy_expr (iters[i]->step);
|
|
break;
|
|
case INTRINSIC_TIMES:
|
|
expr = gfc_copy_expr (start);
|
|
expr->value.op.op1 = gfc_copy_expr (iters[i]->step);
|
|
new_e->ref->u.ar.stride[i] = expr;
|
|
gfc_simplify_expr (new_e->ref->u.ar.stride[i], 0);
|
|
break;
|
|
default:
|
|
gfc_internal_error ("bad op");
|
|
}
|
|
break;
|
|
default:
|
|
gfc_internal_error ("bad expression");
|
|
}
|
|
}
|
|
curr->expr1 = new_e;
|
|
|
|
/* Insert modified statement. Check whether the statement needs to be
|
|
inserted at the lowest level. */
|
|
if (!stack_top->iter)
|
|
{
|
|
if (prev)
|
|
{
|
|
curr->next = prev->next->next;
|
|
prev->next = curr;
|
|
}
|
|
else
|
|
{
|
|
curr->next = stack_top->code->block->next->next->next;
|
|
stack_top->code->block->next = curr;
|
|
}
|
|
}
|
|
else
|
|
stack_top->code->block->next = curr;
|
|
return true;
|
|
}
|
|
|
|
/* Function for the gfc_code_walker. If code is a READ or WRITE statement, it
|
|
tries to optimize its block. */
|
|
|
|
static int
|
|
simplify_io_impl_do (gfc_code **code, int *walk_subtrees,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_code **curr, *prev = NULL;
|
|
struct do_stack write, first;
|
|
bool b = false;
|
|
*walk_subtrees = 1;
|
|
if (!(*code)->block
|
|
|| ((*code)->block->op != EXEC_WRITE
|
|
&& (*code)->block->op != EXEC_READ))
|
|
return 0;
|
|
|
|
*walk_subtrees = 0;
|
|
write.prev = NULL;
|
|
write.iter = NULL;
|
|
write.code = *code;
|
|
|
|
for (curr = &(*code)->block; *curr; curr = &(*curr)->next)
|
|
{
|
|
if ((*curr)->op == EXEC_DO)
|
|
{
|
|
first.prev = &write;
|
|
first.iter = (*curr)->ext.iterator;
|
|
first.code = *curr;
|
|
stack_top = &first;
|
|
traverse_io_block ((*curr)->block->next, &b, prev);
|
|
stack_top = NULL;
|
|
}
|
|
prev = *curr;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Optimize a namespace, including all contained namespaces.
|
|
flag_frontend_optimize and flag_fronend_loop_interchange are
|
|
handled separately. */
|
|
|
|
static void
|
|
optimize_namespace (gfc_namespace *ns)
|
|
{
|
|
gfc_namespace *saved_ns = gfc_current_ns;
|
|
current_ns = ns;
|
|
gfc_current_ns = ns;
|
|
forall_level = 0;
|
|
iterator_level = 0;
|
|
in_assoc_list = false;
|
|
in_omp_workshare = false;
|
|
in_omp_atomic = false;
|
|
|
|
if (flag_frontend_optimize)
|
|
{
|
|
gfc_code_walker (&ns->code, simplify_io_impl_do, dummy_expr_callback, NULL);
|
|
gfc_code_walker (&ns->code, convert_do_while, dummy_expr_callback, NULL);
|
|
gfc_code_walker (&ns->code, convert_elseif, dummy_expr_callback, NULL);
|
|
gfc_code_walker (&ns->code, cfe_code, cfe_expr_0, NULL);
|
|
gfc_code_walker (&ns->code, optimize_code, optimize_expr, NULL);
|
|
if (flag_inline_matmul_limit != 0 || flag_external_blas)
|
|
{
|
|
bool found;
|
|
do
|
|
{
|
|
found = false;
|
|
gfc_code_walker (&ns->code, matmul_to_var_code, matmul_to_var_expr,
|
|
(void *) &found);
|
|
}
|
|
while (found);
|
|
|
|
gfc_code_walker (&ns->code, matmul_temp_args, dummy_expr_callback,
|
|
NULL);
|
|
}
|
|
|
|
if (flag_external_blas)
|
|
gfc_code_walker (&ns->code, call_external_blas, dummy_expr_callback,
|
|
NULL);
|
|
|
|
if (flag_inline_matmul_limit != 0)
|
|
gfc_code_walker (&ns->code, inline_matmul_assign, dummy_expr_callback,
|
|
NULL);
|
|
}
|
|
|
|
if (flag_frontend_loop_interchange)
|
|
gfc_code_walker (&ns->code, index_interchange, dummy_expr_callback,
|
|
NULL);
|
|
|
|
/* BLOCKs are handled in the expression walker below. */
|
|
for (ns = ns->contained; ns; ns = ns->sibling)
|
|
{
|
|
if (ns->code == NULL || ns->code->op != EXEC_BLOCK)
|
|
optimize_namespace (ns);
|
|
}
|
|
gfc_current_ns = saved_ns;
|
|
}
|
|
|
|
/* Handle dependencies for allocatable strings which potentially redefine
|
|
themselves in an assignment. */
|
|
|
|
static void
|
|
realloc_strings (gfc_namespace *ns)
|
|
{
|
|
current_ns = ns;
|
|
gfc_code_walker (&ns->code, realloc_string_callback, dummy_expr_callback, NULL);
|
|
|
|
for (ns = ns->contained; ns; ns = ns->sibling)
|
|
{
|
|
if (ns->code == NULL || ns->code->op != EXEC_BLOCK)
|
|
realloc_strings (ns);
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
optimize_reduction (gfc_namespace *ns)
|
|
{
|
|
current_ns = ns;
|
|
gfc_code_walker (&ns->code, gfc_dummy_code_callback,
|
|
callback_reduction, NULL);
|
|
|
|
/* BLOCKs are handled in the expression walker below. */
|
|
for (ns = ns->contained; ns; ns = ns->sibling)
|
|
{
|
|
if (ns->code == NULL || ns->code->op != EXEC_BLOCK)
|
|
optimize_reduction (ns);
|
|
}
|
|
}
|
|
|
|
/* Replace code like
|
|
a = matmul(b,c) + d
|
|
with
|
|
a = matmul(b,c) ; a = a + d
|
|
where the array function is not elemental and not allocatable
|
|
and does not depend on the left-hand side.
|
|
*/
|
|
|
|
static bool
|
|
optimize_binop_array_assignment (gfc_code *c, gfc_expr **rhs, bool seen_op)
|
|
{
|
|
gfc_expr *e;
|
|
|
|
if (!*rhs)
|
|
return false;
|
|
|
|
e = *rhs;
|
|
if (e->expr_type == EXPR_OP)
|
|
{
|
|
switch (e->value.op.op)
|
|
{
|
|
/* Unary operators and exponentiation: Only look at a single
|
|
operand. */
|
|
case INTRINSIC_NOT:
|
|
case INTRINSIC_UPLUS:
|
|
case INTRINSIC_UMINUS:
|
|
case INTRINSIC_PARENTHESES:
|
|
case INTRINSIC_POWER:
|
|
if (optimize_binop_array_assignment (c, &e->value.op.op1, seen_op))
|
|
return true;
|
|
break;
|
|
|
|
case INTRINSIC_CONCAT:
|
|
/* Do not do string concatenations. */
|
|
break;
|
|
|
|
default:
|
|
/* Binary operators. */
|
|
if (optimize_binop_array_assignment (c, &e->value.op.op1, true))
|
|
return true;
|
|
|
|
if (optimize_binop_array_assignment (c, &e->value.op.op2, true))
|
|
return true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
else if (seen_op && e->expr_type == EXPR_FUNCTION && e->rank > 0
|
|
&& ! (e->value.function.esym
|
|
&& (e->value.function.esym->attr.elemental
|
|
|| e->value.function.esym->attr.allocatable
|
|
|| e->value.function.esym->ts.type != c->expr1->ts.type
|
|
|| e->value.function.esym->ts.kind != c->expr1->ts.kind))
|
|
&& ! (e->value.function.isym
|
|
&& (e->value.function.isym->elemental
|
|
|| e->ts.type != c->expr1->ts.type
|
|
|| e->ts.kind != c->expr1->ts.kind))
|
|
&& ! gfc_inline_intrinsic_function_p (e))
|
|
{
|
|
|
|
gfc_code *n;
|
|
gfc_expr *new_expr;
|
|
|
|
/* Insert a new assignment statement after the current one. */
|
|
n = XCNEW (gfc_code);
|
|
n->op = EXEC_ASSIGN;
|
|
n->loc = c->loc;
|
|
n->next = c->next;
|
|
c->next = n;
|
|
|
|
n->expr1 = gfc_copy_expr (c->expr1);
|
|
n->expr2 = c->expr2;
|
|
new_expr = gfc_copy_expr (c->expr1);
|
|
c->expr2 = e;
|
|
*rhs = new_expr;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
/* Nothing to optimize. */
|
|
return false;
|
|
}
|
|
|
|
/* Remove unneeded TRIMs at the end of expressions. */
|
|
|
|
static bool
|
|
remove_trim (gfc_expr *rhs)
|
|
{
|
|
bool ret;
|
|
|
|
ret = false;
|
|
if (!rhs)
|
|
return ret;
|
|
|
|
/* Check for a // b // trim(c). Looping is probably not
|
|
necessary because the parser usually generates
|
|
(// (// a b ) trim(c) ) , but better safe than sorry. */
|
|
|
|
while (rhs->expr_type == EXPR_OP
|
|
&& rhs->value.op.op == INTRINSIC_CONCAT)
|
|
rhs = rhs->value.op.op2;
|
|
|
|
while (rhs->expr_type == EXPR_FUNCTION && rhs->value.function.isym
|
|
&& rhs->value.function.isym->id == GFC_ISYM_TRIM)
|
|
{
|
|
strip_function_call (rhs);
|
|
/* Recursive call to catch silly stuff like trim ( a // trim(b)). */
|
|
remove_trim (rhs);
|
|
ret = true;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Optimizations for an assignment. */
|
|
|
|
static void
|
|
optimize_assignment (gfc_code * c)
|
|
{
|
|
gfc_expr *lhs, *rhs;
|
|
|
|
lhs = c->expr1;
|
|
rhs = c->expr2;
|
|
|
|
if (lhs->ts.type == BT_CHARACTER && !lhs->ts.deferred)
|
|
{
|
|
/* Optimize a = trim(b) to a = b. */
|
|
remove_trim (rhs);
|
|
|
|
/* Replace a = ' ' by a = '' to optimize away a memcpy. */
|
|
if (is_empty_string (rhs))
|
|
rhs->value.character.length = 0;
|
|
}
|
|
|
|
if (lhs->rank > 0 && gfc_check_dependency (lhs, rhs, true) == 0)
|
|
optimize_binop_array_assignment (c, &rhs, false);
|
|
}
|
|
|
|
|
|
/* Remove an unneeded function call, modifying the expression.
|
|
This replaces the function call with the value of its
|
|
first argument. The rest of the argument list is freed. */
|
|
|
|
static void
|
|
strip_function_call (gfc_expr *e)
|
|
{
|
|
gfc_expr *e1;
|
|
gfc_actual_arglist *a;
|
|
|
|
a = e->value.function.actual;
|
|
|
|
/* We should have at least one argument. */
|
|
gcc_assert (a->expr != NULL);
|
|
|
|
e1 = a->expr;
|
|
|
|
/* Free the remaining arglist, if any. */
|
|
if (a->next)
|
|
gfc_free_actual_arglist (a->next);
|
|
|
|
/* Graft the argument expression onto the original function. */
|
|
*e = *e1;
|
|
free (e1);
|
|
|
|
}
|
|
|
|
/* Optimization of lexical comparison functions. */
|
|
|
|
static bool
|
|
optimize_lexical_comparison (gfc_expr *e)
|
|
{
|
|
if (e->expr_type != EXPR_FUNCTION || e->value.function.isym == NULL)
|
|
return false;
|
|
|
|
switch (e->value.function.isym->id)
|
|
{
|
|
case GFC_ISYM_LLE:
|
|
return optimize_comparison (e, INTRINSIC_LE);
|
|
|
|
case GFC_ISYM_LGE:
|
|
return optimize_comparison (e, INTRINSIC_GE);
|
|
|
|
case GFC_ISYM_LGT:
|
|
return optimize_comparison (e, INTRINSIC_GT);
|
|
|
|
case GFC_ISYM_LLT:
|
|
return optimize_comparison (e, INTRINSIC_LT);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* Combine stuff like [a]>b into [a>b], for easier optimization later. Do not
|
|
do CHARACTER because of possible pessimization involving character
|
|
lengths. */
|
|
|
|
static bool
|
|
combine_array_constructor (gfc_expr *e)
|
|
{
|
|
|
|
gfc_expr *op1, *op2;
|
|
gfc_expr *scalar;
|
|
gfc_expr *new_expr;
|
|
gfc_constructor *c, *new_c;
|
|
gfc_constructor_base oldbase, newbase;
|
|
bool scalar_first;
|
|
int n_elem;
|
|
bool all_const;
|
|
|
|
/* Array constructors have rank one. */
|
|
if (e->rank != 1)
|
|
return false;
|
|
|
|
/* Don't try to combine association lists, this makes no sense
|
|
and leads to an ICE. */
|
|
if (in_assoc_list)
|
|
return false;
|
|
|
|
/* With FORALL, the BLOCKS created by create_var will cause an ICE. */
|
|
if (forall_level > 0)
|
|
return false;
|
|
|
|
/* Inside an iterator, things can get hairy; we are likely to create
|
|
an invalid temporary variable. */
|
|
if (iterator_level > 0)
|
|
return false;
|
|
|
|
/* WHERE also doesn't work. */
|
|
if (in_where > 0)
|
|
return false;
|
|
|
|
op1 = e->value.op.op1;
|
|
op2 = e->value.op.op2;
|
|
|
|
if (!op1 || !op2)
|
|
return false;
|
|
|
|
if (op1->expr_type == EXPR_ARRAY && op2->rank == 0)
|
|
scalar_first = false;
|
|
else if (op2->expr_type == EXPR_ARRAY && op1->rank == 0)
|
|
{
|
|
scalar_first = true;
|
|
op1 = e->value.op.op2;
|
|
op2 = e->value.op.op1;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
if (op2->ts.type == BT_CHARACTER)
|
|
return false;
|
|
|
|
/* This might be an expanded constructor with very many constant values. If
|
|
we perform the operation here, we might end up with a long compile time
|
|
and actually longer execution time, so a length bound is in order here.
|
|
If the constructor constains something which is not a constant, it did
|
|
not come from an expansion, so leave it alone. */
|
|
|
|
#define CONSTR_LEN_MAX 4
|
|
|
|
oldbase = op1->value.constructor;
|
|
|
|
n_elem = 0;
|
|
all_const = true;
|
|
for (c = gfc_constructor_first (oldbase); c; c = gfc_constructor_next(c))
|
|
{
|
|
if (c->expr->expr_type != EXPR_CONSTANT)
|
|
{
|
|
all_const = false;
|
|
break;
|
|
}
|
|
n_elem += 1;
|
|
}
|
|
|
|
if (all_const && n_elem > CONSTR_LEN_MAX)
|
|
return false;
|
|
|
|
#undef CONSTR_LEN_MAX
|
|
|
|
newbase = NULL;
|
|
e->expr_type = EXPR_ARRAY;
|
|
|
|
scalar = create_var (gfc_copy_expr (op2), "constr");
|
|
|
|
for (c = gfc_constructor_first (oldbase); c;
|
|
c = gfc_constructor_next (c))
|
|
{
|
|
new_expr = gfc_get_expr ();
|
|
new_expr->ts = e->ts;
|
|
new_expr->expr_type = EXPR_OP;
|
|
new_expr->rank = c->expr->rank;
|
|
new_expr->where = c->expr->where;
|
|
new_expr->value.op.op = e->value.op.op;
|
|
|
|
if (scalar_first)
|
|
{
|
|
new_expr->value.op.op1 = gfc_copy_expr (scalar);
|
|
new_expr->value.op.op2 = gfc_copy_expr (c->expr);
|
|
}
|
|
else
|
|
{
|
|
new_expr->value.op.op1 = gfc_copy_expr (c->expr);
|
|
new_expr->value.op.op2 = gfc_copy_expr (scalar);
|
|
}
|
|
|
|
new_c = gfc_constructor_append_expr (&newbase, new_expr, &(e->where));
|
|
new_c->iterator = c->iterator;
|
|
c->iterator = NULL;
|
|
}
|
|
|
|
gfc_free_expr (op1);
|
|
gfc_free_expr (op2);
|
|
gfc_free_expr (scalar);
|
|
|
|
e->value.constructor = newbase;
|
|
return true;
|
|
}
|
|
|
|
/* Recursive optimization of operators. */
|
|
|
|
static bool
|
|
optimize_op (gfc_expr *e)
|
|
{
|
|
bool changed;
|
|
|
|
gfc_intrinsic_op op = e->value.op.op;
|
|
|
|
changed = false;
|
|
|
|
/* Only use new-style comparisons. */
|
|
switch(op)
|
|
{
|
|
case INTRINSIC_EQ_OS:
|
|
op = INTRINSIC_EQ;
|
|
break;
|
|
|
|
case INTRINSIC_GE_OS:
|
|
op = INTRINSIC_GE;
|
|
break;
|
|
|
|
case INTRINSIC_LE_OS:
|
|
op = INTRINSIC_LE;
|
|
break;
|
|
|
|
case INTRINSIC_NE_OS:
|
|
op = INTRINSIC_NE;
|
|
break;
|
|
|
|
case INTRINSIC_GT_OS:
|
|
op = INTRINSIC_GT;
|
|
break;
|
|
|
|
case INTRINSIC_LT_OS:
|
|
op = INTRINSIC_LT;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (op)
|
|
{
|
|
case INTRINSIC_EQ:
|
|
case INTRINSIC_GE:
|
|
case INTRINSIC_LE:
|
|
case INTRINSIC_NE:
|
|
case INTRINSIC_GT:
|
|
case INTRINSIC_LT:
|
|
changed = optimize_comparison (e, op);
|
|
|
|
gcc_fallthrough ();
|
|
/* Look at array constructors. */
|
|
case INTRINSIC_PLUS:
|
|
case INTRINSIC_MINUS:
|
|
case INTRINSIC_TIMES:
|
|
case INTRINSIC_DIVIDE:
|
|
return combine_array_constructor (e) || changed;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
/* Return true if a constant string contains only blanks. */
|
|
|
|
static bool
|
|
is_empty_string (gfc_expr *e)
|
|
{
|
|
int i;
|
|
|
|
if (e->ts.type != BT_CHARACTER || e->expr_type != EXPR_CONSTANT)
|
|
return false;
|
|
|
|
for (i=0; i < e->value.character.length; i++)
|
|
{
|
|
if (e->value.character.string[i] != ' ')
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Insert a call to the intrinsic len_trim. Use a different name for
|
|
the symbol tree so we don't run into trouble when the user has
|
|
renamed len_trim for some reason. */
|
|
|
|
static gfc_expr*
|
|
get_len_trim_call (gfc_expr *str, int kind)
|
|
{
|
|
gfc_expr *fcn;
|
|
gfc_actual_arglist *actual_arglist, *next;
|
|
|
|
fcn = gfc_get_expr ();
|
|
fcn->expr_type = EXPR_FUNCTION;
|
|
fcn->value.function.isym = gfc_intrinsic_function_by_id (GFC_ISYM_LEN_TRIM);
|
|
actual_arglist = gfc_get_actual_arglist ();
|
|
actual_arglist->expr = str;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = gfc_get_int_expr (gfc_default_integer_kind, NULL, kind);
|
|
actual_arglist->next = next;
|
|
|
|
fcn->value.function.actual = actual_arglist;
|
|
fcn->where = str->where;
|
|
fcn->ts.type = BT_INTEGER;
|
|
fcn->ts.kind = gfc_charlen_int_kind;
|
|
|
|
gfc_get_sym_tree ("__internal_len_trim", current_ns, &fcn->symtree, false);
|
|
fcn->symtree->n.sym->ts = fcn->ts;
|
|
fcn->symtree->n.sym->attr.flavor = FL_PROCEDURE;
|
|
fcn->symtree->n.sym->attr.function = 1;
|
|
fcn->symtree->n.sym->attr.elemental = 1;
|
|
fcn->symtree->n.sym->attr.referenced = 1;
|
|
fcn->symtree->n.sym->attr.access = ACCESS_PRIVATE;
|
|
gfc_commit_symbol (fcn->symtree->n.sym);
|
|
|
|
return fcn;
|
|
}
|
|
|
|
|
|
/* Optimize expressions for equality. */
|
|
|
|
static bool
|
|
optimize_comparison (gfc_expr *e, gfc_intrinsic_op op)
|
|
{
|
|
gfc_expr *op1, *op2;
|
|
bool change;
|
|
int eq;
|
|
bool result;
|
|
gfc_actual_arglist *firstarg, *secondarg;
|
|
|
|
if (e->expr_type == EXPR_OP)
|
|
{
|
|
firstarg = NULL;
|
|
secondarg = NULL;
|
|
op1 = e->value.op.op1;
|
|
op2 = e->value.op.op2;
|
|
}
|
|
else if (e->expr_type == EXPR_FUNCTION)
|
|
{
|
|
/* One of the lexical comparison functions. */
|
|
firstarg = e->value.function.actual;
|
|
secondarg = firstarg->next;
|
|
op1 = firstarg->expr;
|
|
op2 = secondarg->expr;
|
|
}
|
|
else
|
|
gcc_unreachable ();
|
|
|
|
/* Strip off unneeded TRIM calls from string comparisons. */
|
|
|
|
change = remove_trim (op1);
|
|
|
|
if (remove_trim (op2))
|
|
change = true;
|
|
|
|
/* An expression of type EXPR_CONSTANT is only valid for scalars. */
|
|
/* TODO: A scalar constant may be acceptable in some cases (the scalarizer
|
|
handles them well). However, there are also cases that need a non-scalar
|
|
argument. For example the any intrinsic. See PR 45380. */
|
|
if (e->rank > 0)
|
|
return change;
|
|
|
|
/* Replace a == '' with len_trim(a) == 0 and a /= '' with
|
|
len_trim(a) != 0 */
|
|
if (op1->ts.type == BT_CHARACTER && op2->ts.type == BT_CHARACTER
|
|
&& (op == INTRINSIC_EQ || op == INTRINSIC_NE))
|
|
{
|
|
bool empty_op1, empty_op2;
|
|
empty_op1 = is_empty_string (op1);
|
|
empty_op2 = is_empty_string (op2);
|
|
|
|
if (empty_op1 || empty_op2)
|
|
{
|
|
gfc_expr *fcn;
|
|
gfc_expr *zero;
|
|
gfc_expr *str;
|
|
|
|
/* This can only happen when an error for comparing
|
|
characters of different kinds has already been issued. */
|
|
if (empty_op1 && empty_op2)
|
|
return false;
|
|
|
|
zero = gfc_get_int_expr (gfc_charlen_int_kind, &e->where, 0);
|
|
str = empty_op1 ? op2 : op1;
|
|
|
|
fcn = get_len_trim_call (str, gfc_charlen_int_kind);
|
|
|
|
|
|
if (empty_op1)
|
|
gfc_free_expr (op1);
|
|
else
|
|
gfc_free_expr (op2);
|
|
|
|
op1 = fcn;
|
|
op2 = zero;
|
|
e->value.op.op1 = fcn;
|
|
e->value.op.op2 = zero;
|
|
}
|
|
}
|
|
|
|
|
|
/* Don't compare REAL or COMPLEX expressions when honoring NaNs. */
|
|
|
|
if (flag_finite_math_only
|
|
|| (op1->ts.type != BT_REAL && op2->ts.type != BT_REAL
|
|
&& op1->ts.type != BT_COMPLEX && op2->ts.type != BT_COMPLEX))
|
|
{
|
|
eq = gfc_dep_compare_expr (op1, op2);
|
|
if (eq <= -2)
|
|
{
|
|
/* Replace A // B < A // C with B < C, and A // B < C // B
|
|
with A < C. */
|
|
if (op1->ts.type == BT_CHARACTER && op2->ts.type == BT_CHARACTER
|
|
&& op1->expr_type == EXPR_OP
|
|
&& op1->value.op.op == INTRINSIC_CONCAT
|
|
&& op2->expr_type == EXPR_OP
|
|
&& op2->value.op.op == INTRINSIC_CONCAT)
|
|
{
|
|
gfc_expr *op1_left = op1->value.op.op1;
|
|
gfc_expr *op2_left = op2->value.op.op1;
|
|
gfc_expr *op1_right = op1->value.op.op2;
|
|
gfc_expr *op2_right = op2->value.op.op2;
|
|
|
|
if (gfc_dep_compare_expr (op1_left, op2_left) == 0)
|
|
{
|
|
/* Watch out for 'A ' // x vs. 'A' // x. */
|
|
|
|
if (op1_left->expr_type == EXPR_CONSTANT
|
|
&& op2_left->expr_type == EXPR_CONSTANT
|
|
&& op1_left->value.character.length
|
|
!= op2_left->value.character.length)
|
|
return change;
|
|
else
|
|
{
|
|
free (op1_left);
|
|
free (op2_left);
|
|
if (firstarg)
|
|
{
|
|
firstarg->expr = op1_right;
|
|
secondarg->expr = op2_right;
|
|
}
|
|
else
|
|
{
|
|
e->value.op.op1 = op1_right;
|
|
e->value.op.op2 = op2_right;
|
|
}
|
|
optimize_comparison (e, op);
|
|
return true;
|
|
}
|
|
}
|
|
if (gfc_dep_compare_expr (op1_right, op2_right) == 0)
|
|
{
|
|
free (op1_right);
|
|
free (op2_right);
|
|
if (firstarg)
|
|
{
|
|
firstarg->expr = op1_left;
|
|
secondarg->expr = op2_left;
|
|
}
|
|
else
|
|
{
|
|
e->value.op.op1 = op1_left;
|
|
e->value.op.op2 = op2_left;
|
|
}
|
|
|
|
optimize_comparison (e, op);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* eq can only be -1, 0 or 1 at this point. */
|
|
switch (op)
|
|
{
|
|
case INTRINSIC_EQ:
|
|
result = eq == 0;
|
|
break;
|
|
|
|
case INTRINSIC_GE:
|
|
result = eq >= 0;
|
|
break;
|
|
|
|
case INTRINSIC_LE:
|
|
result = eq <= 0;
|
|
break;
|
|
|
|
case INTRINSIC_NE:
|
|
result = eq != 0;
|
|
break;
|
|
|
|
case INTRINSIC_GT:
|
|
result = eq > 0;
|
|
break;
|
|
|
|
case INTRINSIC_LT:
|
|
result = eq < 0;
|
|
break;
|
|
|
|
default:
|
|
gfc_internal_error ("illegal OP in optimize_comparison");
|
|
break;
|
|
}
|
|
|
|
/* Replace the expression by a constant expression. The typespec
|
|
and where remains the way it is. */
|
|
free (op1);
|
|
free (op2);
|
|
e->expr_type = EXPR_CONSTANT;
|
|
e->value.logical = result;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return change;
|
|
}
|
|
|
|
/* Optimize a trim function by replacing it with an equivalent substring
|
|
involving a call to len_trim. This only works for expressions where
|
|
variables are trimmed. Return true if anything was modified. */
|
|
|
|
static bool
|
|
optimize_trim (gfc_expr *e)
|
|
{
|
|
gfc_expr *a;
|
|
gfc_ref *ref;
|
|
gfc_expr *fcn;
|
|
gfc_ref **rr = NULL;
|
|
|
|
/* Don't do this optimization within an argument list, because
|
|
otherwise aliasing issues may occur. */
|
|
|
|
if (count_arglist != 1)
|
|
return false;
|
|
|
|
if (e->ts.type != BT_CHARACTER || e->expr_type != EXPR_FUNCTION
|
|
|| e->value.function.isym == NULL
|
|
|| e->value.function.isym->id != GFC_ISYM_TRIM)
|
|
return false;
|
|
|
|
a = e->value.function.actual->expr;
|
|
|
|
if (a->expr_type != EXPR_VARIABLE)
|
|
return false;
|
|
|
|
/* This would pessimize the idiom a = trim(a) for reallocatable strings. */
|
|
|
|
if (a->symtree->n.sym->attr.allocatable)
|
|
return false;
|
|
|
|
/* Follow all references to find the correct place to put the newly
|
|
created reference. FIXME: Also handle substring references and
|
|
array references. Array references cause strange regressions at
|
|
the moment. */
|
|
|
|
if (a->ref)
|
|
{
|
|
for (rr = &(a->ref); *rr; rr = &((*rr)->next))
|
|
{
|
|
if ((*rr)->type == REF_SUBSTRING || (*rr)->type == REF_ARRAY)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
strip_function_call (e);
|
|
|
|
if (e->ref == NULL)
|
|
rr = &(e->ref);
|
|
|
|
/* Create the reference. */
|
|
|
|
ref = gfc_get_ref ();
|
|
ref->type = REF_SUBSTRING;
|
|
|
|
/* Set the start of the reference. */
|
|
|
|
ref->u.ss.start = gfc_get_int_expr (gfc_charlen_int_kind, NULL, 1);
|
|
|
|
/* Build the function call to len_trim(x, gfc_default_integer_kind). */
|
|
|
|
fcn = get_len_trim_call (gfc_copy_expr (e), gfc_charlen_int_kind);
|
|
|
|
/* Set the end of the reference to the call to len_trim. */
|
|
|
|
ref->u.ss.end = fcn;
|
|
gcc_assert (rr != NULL && *rr == NULL);
|
|
*rr = ref;
|
|
return true;
|
|
}
|
|
|
|
/* Optimize minloc(b), where b is rank 1 array, into
|
|
(/ minloc(b, dim=1) /), and similarly for maxloc,
|
|
as the latter forms are expanded inline. */
|
|
|
|
static void
|
|
optimize_minmaxloc (gfc_expr **e)
|
|
{
|
|
gfc_expr *fn = *e;
|
|
gfc_actual_arglist *a;
|
|
char *name, *p;
|
|
|
|
if (fn->rank != 1
|
|
|| fn->value.function.actual == NULL
|
|
|| fn->value.function.actual->expr == NULL
|
|
|| fn->value.function.actual->expr->rank != 1)
|
|
return;
|
|
|
|
*e = gfc_get_array_expr (fn->ts.type, fn->ts.kind, &fn->where);
|
|
(*e)->shape = fn->shape;
|
|
fn->rank = 0;
|
|
fn->shape = NULL;
|
|
gfc_constructor_append_expr (&(*e)->value.constructor, fn, &fn->where);
|
|
|
|
name = XALLOCAVEC (char, strlen (fn->value.function.name) + 1);
|
|
strcpy (name, fn->value.function.name);
|
|
p = strstr (name, "loc0");
|
|
p[3] = '1';
|
|
fn->value.function.name = gfc_get_string ("%s", name);
|
|
if (fn->value.function.actual->next)
|
|
{
|
|
a = fn->value.function.actual->next;
|
|
gcc_assert (a->expr == NULL);
|
|
}
|
|
else
|
|
{
|
|
a = gfc_get_actual_arglist ();
|
|
fn->value.function.actual->next = a;
|
|
}
|
|
a->expr = gfc_get_constant_expr (BT_INTEGER, gfc_default_integer_kind,
|
|
&fn->where);
|
|
mpz_set_ui (a->expr->value.integer, 1);
|
|
}
|
|
|
|
/* Data package to hand down for DO loop checks in a contained
|
|
procedure. */
|
|
typedef struct contained_info
|
|
{
|
|
gfc_symbol *do_var;
|
|
gfc_symbol *procedure;
|
|
locus where_do;
|
|
} contained_info;
|
|
|
|
static enum gfc_exec_op last_io_op;
|
|
|
|
/* Callback function to check for INTENT(OUT) and INTENT(INOUT) in a
|
|
contained function call. */
|
|
|
|
static int
|
|
doloop_contained_function_call (gfc_expr **e,
|
|
int *walk_subtrees ATTRIBUTE_UNUSED, void *data)
|
|
{
|
|
gfc_expr *expr = *e;
|
|
gfc_formal_arglist *f;
|
|
gfc_actual_arglist *a;
|
|
gfc_symbol *sym, *do_var;
|
|
contained_info *info;
|
|
|
|
if (expr->expr_type != EXPR_FUNCTION || expr->value.function.isym
|
|
|| expr->value.function.esym == NULL)
|
|
return 0;
|
|
|
|
sym = expr->value.function.esym;
|
|
f = gfc_sym_get_dummy_args (sym);
|
|
if (f == NULL)
|
|
return 0;
|
|
|
|
info = (contained_info *) data;
|
|
do_var = info->do_var;
|
|
a = expr->value.function.actual;
|
|
|
|
while (a && f)
|
|
{
|
|
if (a->expr && a->expr->symtree && a->expr->symtree->n.sym == do_var)
|
|
{
|
|
if (f->sym->attr.intent == INTENT_OUT)
|
|
{
|
|
gfc_error_now ("Index variable %qs set to undefined as "
|
|
"INTENT(OUT) argument at %L in procedure %qs "
|
|
"called from within DO loop at %L", do_var->name,
|
|
&a->expr->where, info->procedure->name,
|
|
&info->where_do);
|
|
return 1;
|
|
}
|
|
else if (f->sym->attr.intent == INTENT_INOUT)
|
|
{
|
|
gfc_error_now ("Index variable %qs not definable as "
|
|
"INTENT(INOUT) argument at %L in procedure %qs "
|
|
"called from within DO loop at %L", do_var->name,
|
|
&a->expr->where, info->procedure->name,
|
|
&info->where_do);
|
|
return 1;
|
|
}
|
|
}
|
|
a = a->next;
|
|
f = f->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Callback function that goes through the code in a contained
|
|
procedure to make sure it does not change a variable in a DO
|
|
loop. */
|
|
|
|
static int
|
|
doloop_contained_procedure_code (gfc_code **c,
|
|
int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data)
|
|
{
|
|
gfc_code *co = *c;
|
|
contained_info *info = (contained_info *) data;
|
|
gfc_symbol *do_var = info->do_var;
|
|
const char *errmsg = _("Index variable %qs redefined at %L in procedure %qs "
|
|
"called from within DO loop at %L");
|
|
static enum gfc_exec_op saved_io_op;
|
|
|
|
switch (co->op)
|
|
{
|
|
case EXEC_ASSIGN:
|
|
if (co->expr1->symtree->n.sym == do_var)
|
|
gfc_error_now (errmsg, do_var->name, &co->loc, info->procedure->name,
|
|
&info->where_do);
|
|
break;
|
|
|
|
case EXEC_DO:
|
|
if (co->ext.iterator && co->ext.iterator->var
|
|
&& co->ext.iterator->var->symtree->n.sym == do_var)
|
|
gfc_error (errmsg, do_var->name, &co->loc, info->procedure->name,
|
|
&info->where_do);
|
|
break;
|
|
|
|
case EXEC_READ:
|
|
case EXEC_WRITE:
|
|
case EXEC_INQUIRE:
|
|
saved_io_op = last_io_op;
|
|
last_io_op = co->op;
|
|
break;
|
|
|
|
case EXEC_OPEN:
|
|
if (co->ext.open->iostat
|
|
&& co->ext.open->iostat->symtree->n.sym == do_var)
|
|
gfc_error_now (errmsg, do_var->name, &co->ext.open->iostat->where,
|
|
info->procedure->name, &info->where_do);
|
|
break;
|
|
|
|
case EXEC_CLOSE:
|
|
if (co->ext.close->iostat
|
|
&& co->ext.close->iostat->symtree->n.sym == do_var)
|
|
gfc_error_now (errmsg, do_var->name, &co->ext.close->iostat->where,
|
|
info->procedure->name, &info->where_do);
|
|
break;
|
|
|
|
case EXEC_TRANSFER:
|
|
switch (last_io_op)
|
|
{
|
|
|
|
case EXEC_INQUIRE:
|
|
#define CHECK_INQ(a) do { if (co->ext.inquire->a && \
|
|
co->ext.inquire->a->symtree->n.sym == do_var) \
|
|
gfc_error_now (errmsg, do_var->name, \
|
|
&co->ext.inquire->a->where, \
|
|
info->procedure->name, \
|
|
&info->where_do); \
|
|
} while (0)
|
|
|
|
CHECK_INQ(iostat);
|
|
CHECK_INQ(number);
|
|
CHECK_INQ(position);
|
|
CHECK_INQ(recl);
|
|
CHECK_INQ(position);
|
|
CHECK_INQ(iolength);
|
|
CHECK_INQ(strm_pos);
|
|
break;
|
|
#undef CHECK_INQ
|
|
|
|
case EXEC_READ:
|
|
if (co->expr1 && co->expr1->symtree->n.sym == do_var)
|
|
gfc_error_now (errmsg, do_var->name, &co->expr1->where,
|
|
info->procedure->name, &info->where_do);
|
|
|
|
/* Fallthrough. */
|
|
|
|
case EXEC_WRITE:
|
|
if (co->ext.dt->iostat
|
|
&& co->ext.dt->iostat->symtree->n.sym == do_var)
|
|
gfc_error_now (errmsg, do_var->name, &co->ext.dt->iostat->where,
|
|
info->procedure->name, &info->where_do);
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
break;
|
|
|
|
case EXEC_DT_END:
|
|
last_io_op = saved_io_op;
|
|
break;
|
|
|
|
case EXEC_CALL:
|
|
gfc_formal_arglist *f;
|
|
gfc_actual_arglist *a;
|
|
|
|
f = gfc_sym_get_dummy_args (co->resolved_sym);
|
|
if (f == NULL)
|
|
break;
|
|
a = co->ext.actual;
|
|
/* Slightly different error message here. If there is an error,
|
|
return 1 to avoid an infinite loop. */
|
|
while (a && f)
|
|
{
|
|
if (a->expr && a->expr->symtree && a->expr->symtree->n.sym == do_var)
|
|
{
|
|
if (f->sym->attr.intent == INTENT_OUT)
|
|
{
|
|
gfc_error_now ("Index variable %qs set to undefined as "
|
|
"INTENT(OUT) argument at %L in subroutine %qs "
|
|
"called from within DO loop at %L",
|
|
do_var->name, &a->expr->where,
|
|
info->procedure->name, &info->where_do);
|
|
return 1;
|
|
}
|
|
else if (f->sym->attr.intent == INTENT_INOUT)
|
|
{
|
|
gfc_error_now ("Index variable %qs not definable as "
|
|
"INTENT(INOUT) argument at %L in subroutine %qs "
|
|
"called from within DO loop at %L", do_var->name,
|
|
&a->expr->where, info->procedure->name,
|
|
&info->where_do);
|
|
return 1;
|
|
}
|
|
}
|
|
a = a->next;
|
|
f = f->next;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Callback function for code checking that we do not pass a DO variable to an
|
|
INTENT(OUT) or INTENT(INOUT) dummy variable. */
|
|
|
|
static int
|
|
doloop_code (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_code *co;
|
|
int i;
|
|
gfc_formal_arglist *f;
|
|
gfc_actual_arglist *a;
|
|
gfc_code *cl;
|
|
do_t loop, *lp;
|
|
bool seen_goto;
|
|
|
|
co = *c;
|
|
|
|
/* If the doloop_list grew, we have to truncate it here. */
|
|
|
|
if ((unsigned) doloop_level < doloop_list.length())
|
|
doloop_list.truncate (doloop_level);
|
|
|
|
seen_goto = false;
|
|
switch (co->op)
|
|
{
|
|
case EXEC_DO:
|
|
|
|
if (co->ext.iterator && co->ext.iterator->var)
|
|
loop.c = co;
|
|
else
|
|
loop.c = NULL;
|
|
|
|
loop.branch_level = if_level + select_level;
|
|
loop.seen_goto = false;
|
|
doloop_list.safe_push (loop);
|
|
break;
|
|
|
|
/* If anything could transfer control away from a suspicious
|
|
subscript, make sure to set seen_goto in the current DO loop
|
|
(if any). */
|
|
case EXEC_GOTO:
|
|
case EXEC_EXIT:
|
|
case EXEC_STOP:
|
|
case EXEC_ERROR_STOP:
|
|
case EXEC_CYCLE:
|
|
seen_goto = true;
|
|
break;
|
|
|
|
case EXEC_OPEN:
|
|
if (co->ext.open->err)
|
|
seen_goto = true;
|
|
break;
|
|
|
|
case EXEC_CLOSE:
|
|
if (co->ext.close->err)
|
|
seen_goto = true;
|
|
break;
|
|
|
|
case EXEC_BACKSPACE:
|
|
case EXEC_ENDFILE:
|
|
case EXEC_REWIND:
|
|
case EXEC_FLUSH:
|
|
|
|
if (co->ext.filepos->err)
|
|
seen_goto = true;
|
|
break;
|
|
|
|
case EXEC_INQUIRE:
|
|
if (co->ext.filepos->err)
|
|
seen_goto = true;
|
|
break;
|
|
|
|
case EXEC_READ:
|
|
case EXEC_WRITE:
|
|
if (co->ext.dt->err || co->ext.dt->end || co->ext.dt->eor)
|
|
seen_goto = true;
|
|
break;
|
|
|
|
case EXEC_WAIT:
|
|
if (co->ext.wait->err || co->ext.wait->end || co->ext.wait->eor)
|
|
loop.seen_goto = true;
|
|
break;
|
|
|
|
case EXEC_CALL:
|
|
if (co->resolved_sym == NULL)
|
|
break;
|
|
|
|
/* Test if somebody stealthily changes the DO variable from
|
|
under us by changing it in a host-associated procedure. */
|
|
if (co->resolved_sym->attr.contained)
|
|
{
|
|
FOR_EACH_VEC_ELT (doloop_list, i, lp)
|
|
{
|
|
gfc_symbol *sym = co->resolved_sym;
|
|
contained_info info;
|
|
gfc_namespace *ns;
|
|
|
|
cl = lp->c;
|
|
info.do_var = cl->ext.iterator->var->symtree->n.sym;
|
|
info.procedure = co->resolved_sym; /* sym? */
|
|
info.where_do = co->loc;
|
|
/* Look contained procedures under the namespace of the
|
|
variable. */
|
|
for (ns = info.do_var->ns->contained; ns; ns = ns->sibling)
|
|
if (ns->proc_name && ns->proc_name == sym)
|
|
gfc_code_walker (&ns->code, doloop_contained_procedure_code,
|
|
doloop_contained_function_call, &info);
|
|
}
|
|
}
|
|
|
|
f = gfc_sym_get_dummy_args (co->resolved_sym);
|
|
|
|
/* Withot a formal arglist, there is only unknown INTENT,
|
|
which we don't check for. */
|
|
if (f == NULL)
|
|
break;
|
|
|
|
a = co->ext.actual;
|
|
|
|
while (a && f)
|
|
{
|
|
FOR_EACH_VEC_ELT (doloop_list, i, lp)
|
|
{
|
|
gfc_symbol *do_sym;
|
|
cl = lp->c;
|
|
|
|
if (cl == NULL)
|
|
break;
|
|
|
|
do_sym = cl->ext.iterator->var->symtree->n.sym;
|
|
|
|
if (a->expr && a->expr->symtree
|
|
&& a->expr->symtree->n.sym == do_sym)
|
|
{
|
|
if (f->sym->attr.intent == INTENT_OUT)
|
|
gfc_error_now ("Variable %qs at %L set to undefined "
|
|
"value inside loop beginning at %L as "
|
|
"INTENT(OUT) argument to subroutine %qs",
|
|
do_sym->name, &a->expr->where,
|
|
&(doloop_list[i].c->loc),
|
|
co->symtree->n.sym->name);
|
|
else if (f->sym->attr.intent == INTENT_INOUT)
|
|
gfc_error_now ("Variable %qs at %L not definable inside "
|
|
"loop beginning at %L as INTENT(INOUT) "
|
|
"argument to subroutine %qs",
|
|
do_sym->name, &a->expr->where,
|
|
&(doloop_list[i].c->loc),
|
|
co->symtree->n.sym->name);
|
|
}
|
|
}
|
|
a = a->next;
|
|
f = f->next;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
if (seen_goto && doloop_level > 0)
|
|
doloop_list[doloop_level-1].seen_goto = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Callback function to warn about different things within DO loops. */
|
|
|
|
static int
|
|
do_function (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
do_t *last;
|
|
|
|
if (doloop_list.length () == 0)
|
|
return 0;
|
|
|
|
if ((*e)->expr_type == EXPR_FUNCTION)
|
|
do_intent (e);
|
|
|
|
last = &doloop_list.last();
|
|
if (last->seen_goto && !warn_do_subscript)
|
|
return 0;
|
|
|
|
if ((*e)->expr_type == EXPR_VARIABLE)
|
|
do_subscript (e);
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef struct
|
|
{
|
|
gfc_symbol *sym;
|
|
mpz_t val;
|
|
} insert_index_t;
|
|
|
|
/* Callback function - if the expression is the variable in data->sym,
|
|
replace it with a constant from data->val. */
|
|
|
|
static int
|
|
callback_insert_index (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data)
|
|
{
|
|
insert_index_t *d;
|
|
gfc_expr *ex, *n;
|
|
|
|
ex = (*e);
|
|
if (ex->expr_type != EXPR_VARIABLE)
|
|
return 0;
|
|
|
|
d = (insert_index_t *) data;
|
|
if (ex->symtree->n.sym != d->sym)
|
|
return 0;
|
|
|
|
n = gfc_get_constant_expr (BT_INTEGER, ex->ts.kind, &ex->where);
|
|
mpz_set (n->value.integer, d->val);
|
|
|
|
gfc_free_expr (ex);
|
|
*e = n;
|
|
return 0;
|
|
}
|
|
|
|
/* In the expression e, replace occurrences of the variable sym with
|
|
val. If this results in a constant expression, return true and
|
|
return the value in ret. Return false if the expression already
|
|
is a constant. Caller has to clear ret in that case. */
|
|
|
|
static bool
|
|
insert_index (gfc_expr *e, gfc_symbol *sym, mpz_t val, mpz_t ret)
|
|
{
|
|
gfc_expr *n;
|
|
insert_index_t data;
|
|
bool rc;
|
|
|
|
if (e->expr_type == EXPR_CONSTANT)
|
|
return false;
|
|
|
|
n = gfc_copy_expr (e);
|
|
data.sym = sym;
|
|
mpz_init_set (data.val, val);
|
|
gfc_expr_walker (&n, callback_insert_index, (void *) &data);
|
|
|
|
/* Suppress errors here - we could get errors here such as an
|
|
out of bounds access for arrays, see PR 90563. */
|
|
gfc_push_suppress_errors ();
|
|
gfc_simplify_expr (n, 0);
|
|
gfc_pop_suppress_errors ();
|
|
|
|
if (n->expr_type == EXPR_CONSTANT)
|
|
{
|
|
rc = true;
|
|
mpz_init_set (ret, n->value.integer);
|
|
}
|
|
else
|
|
rc = false;
|
|
|
|
mpz_clear (data.val);
|
|
gfc_free_expr (n);
|
|
return rc;
|
|
|
|
}
|
|
|
|
/* Check array subscripts for possible out-of-bounds accesses in DO
|
|
loops with constant bounds. */
|
|
|
|
static int
|
|
do_subscript (gfc_expr **e)
|
|
{
|
|
gfc_expr *v;
|
|
gfc_array_ref *ar;
|
|
gfc_ref *ref;
|
|
int i,j;
|
|
gfc_code *dl;
|
|
do_t *lp;
|
|
|
|
v = *e;
|
|
/* Constants are already checked. */
|
|
if (v->expr_type == EXPR_CONSTANT)
|
|
return 0;
|
|
|
|
/* Wrong warnings will be generated in an associate list. */
|
|
if (in_assoc_list)
|
|
return 0;
|
|
|
|
/* We already warned about this. */
|
|
if (v->do_not_warn)
|
|
return 0;
|
|
|
|
v->do_not_warn = 1;
|
|
|
|
for (ref = v->ref; ref; ref = ref->next)
|
|
{
|
|
if (ref->type == REF_ARRAY && ref->u.ar.type == AR_ELEMENT)
|
|
{
|
|
ar = & ref->u.ar;
|
|
FOR_EACH_VEC_ELT (doloop_list, j, lp)
|
|
{
|
|
gfc_symbol *do_sym;
|
|
mpz_t do_start, do_step, do_end;
|
|
bool have_do_start, have_do_end;
|
|
bool error_not_proven;
|
|
int warn;
|
|
int sgn;
|
|
|
|
dl = lp->c;
|
|
if (dl == NULL)
|
|
break;
|
|
|
|
/* If we are within a branch, or a goto or equivalent
|
|
was seen in the DO loop before, then we cannot prove that
|
|
this expression is actually evaluated. Don't do anything
|
|
unless we want to see it all. */
|
|
error_not_proven = lp->seen_goto
|
|
|| lp->branch_level < if_level + select_level;
|
|
|
|
if (error_not_proven && !warn_do_subscript)
|
|
break;
|
|
|
|
if (error_not_proven)
|
|
warn = OPT_Wdo_subscript;
|
|
else
|
|
warn = 0;
|
|
|
|
do_sym = dl->ext.iterator->var->symtree->n.sym;
|
|
if (do_sym->ts.type != BT_INTEGER)
|
|
continue;
|
|
|
|
/* If we do not know about the stepsize, the loop may be zero trip.
|
|
Do not warn in this case. */
|
|
|
|
if (dl->ext.iterator->step->expr_type == EXPR_CONSTANT)
|
|
{
|
|
sgn = mpz_cmp_ui (dl->ext.iterator->step->value.integer, 0);
|
|
/* This can happen, but then the error has been
|
|
reported previously. */
|
|
if (sgn == 0)
|
|
continue;
|
|
|
|
mpz_init_set (do_step, dl->ext.iterator->step->value.integer);
|
|
}
|
|
|
|
else
|
|
continue;
|
|
|
|
if (dl->ext.iterator->start->expr_type == EXPR_CONSTANT)
|
|
{
|
|
have_do_start = true;
|
|
mpz_init_set (do_start, dl->ext.iterator->start->value.integer);
|
|
}
|
|
else
|
|
have_do_start = false;
|
|
|
|
if (dl->ext.iterator->end->expr_type == EXPR_CONSTANT)
|
|
{
|
|
have_do_end = true;
|
|
mpz_init_set (do_end, dl->ext.iterator->end->value.integer);
|
|
}
|
|
else
|
|
have_do_end = false;
|
|
|
|
if (!have_do_start && !have_do_end)
|
|
return 0;
|
|
|
|
/* No warning inside a zero-trip loop. */
|
|
if (have_do_start && have_do_end)
|
|
{
|
|
int cmp;
|
|
|
|
cmp = mpz_cmp (do_end, do_start);
|
|
if ((sgn > 0 && cmp < 0) || (sgn < 0 && cmp > 0))
|
|
break;
|
|
}
|
|
|
|
/* May have to correct the end value if the step does not equal
|
|
one. */
|
|
if (have_do_start && have_do_end && mpz_cmp_ui (do_step, 1) != 0)
|
|
{
|
|
mpz_t diff, rem;
|
|
|
|
mpz_init (diff);
|
|
mpz_init (rem);
|
|
mpz_sub (diff, do_end, do_start);
|
|
mpz_tdiv_r (rem, diff, do_step);
|
|
mpz_sub (do_end, do_end, rem);
|
|
mpz_clear (diff);
|
|
mpz_clear (rem);
|
|
}
|
|
|
|
for (i = 0; i< ar->dimen; i++)
|
|
{
|
|
mpz_t val;
|
|
if (ar->dimen_type[i] == DIMEN_ELEMENT && have_do_start
|
|
&& insert_index (ar->start[i], do_sym, do_start, val))
|
|
{
|
|
if (ar->as->lower[i]
|
|
&& ar->as->lower[i]->expr_type == EXPR_CONSTANT
|
|
&& mpz_cmp (val, ar->as->lower[i]->value.integer) < 0)
|
|
gfc_warning (warn, "Array reference at %L out of bounds "
|
|
"(%ld < %ld) in loop beginning at %L",
|
|
&ar->start[i]->where, mpz_get_si (val),
|
|
mpz_get_si (ar->as->lower[i]->value.integer),
|
|
&doloop_list[j].c->loc);
|
|
|
|
if (ar->as->upper[i]
|
|
&& ar->as->upper[i]->expr_type == EXPR_CONSTANT
|
|
&& mpz_cmp (val, ar->as->upper[i]->value.integer) > 0)
|
|
gfc_warning (warn, "Array reference at %L out of bounds "
|
|
"(%ld > %ld) in loop beginning at %L",
|
|
&ar->start[i]->where, mpz_get_si (val),
|
|
mpz_get_si (ar->as->upper[i]->value.integer),
|
|
&doloop_list[j].c->loc);
|
|
|
|
mpz_clear (val);
|
|
}
|
|
|
|
if (ar->dimen_type[i] == DIMEN_ELEMENT && have_do_end
|
|
&& insert_index (ar->start[i], do_sym, do_end, val))
|
|
{
|
|
if (ar->as->lower[i]
|
|
&& ar->as->lower[i]->expr_type == EXPR_CONSTANT
|
|
&& mpz_cmp (val, ar->as->lower[i]->value.integer) < 0)
|
|
gfc_warning (warn, "Array reference at %L out of bounds "
|
|
"(%ld < %ld) in loop beginning at %L",
|
|
&ar->start[i]->where, mpz_get_si (val),
|
|
mpz_get_si (ar->as->lower[i]->value.integer),
|
|
&doloop_list[j].c->loc);
|
|
|
|
if (ar->as->upper[i]
|
|
&& ar->as->upper[i]->expr_type == EXPR_CONSTANT
|
|
&& mpz_cmp (val, ar->as->upper[i]->value.integer) > 0)
|
|
gfc_warning (warn, "Array reference at %L out of bounds "
|
|
"(%ld > %ld) in loop beginning at %L",
|
|
&ar->start[i]->where, mpz_get_si (val),
|
|
mpz_get_si (ar->as->upper[i]->value.integer),
|
|
&doloop_list[j].c->loc);
|
|
|
|
mpz_clear (val);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
/* Function for functions checking that we do not pass a DO variable
|
|
to an INTENT(OUT) or INTENT(INOUT) dummy variable. */
|
|
|
|
static int
|
|
do_intent (gfc_expr **e)
|
|
{
|
|
gfc_formal_arglist *f;
|
|
gfc_actual_arglist *a;
|
|
gfc_expr *expr;
|
|
gfc_code *dl;
|
|
do_t *lp;
|
|
int i;
|
|
gfc_symbol *sym;
|
|
|
|
expr = *e;
|
|
if (expr->expr_type != EXPR_FUNCTION)
|
|
return 0;
|
|
|
|
/* Intrinsic functions don't modify their arguments. */
|
|
|
|
if (expr->value.function.isym)
|
|
return 0;
|
|
|
|
sym = expr->value.function.esym;
|
|
if (sym == NULL)
|
|
return 0;
|
|
|
|
if (sym->attr.contained)
|
|
{
|
|
FOR_EACH_VEC_ELT (doloop_list, i, lp)
|
|
{
|
|
contained_info info;
|
|
gfc_namespace *ns;
|
|
|
|
dl = lp->c;
|
|
info.do_var = dl->ext.iterator->var->symtree->n.sym;
|
|
info.procedure = sym;
|
|
info.where_do = expr->where;
|
|
/* Look contained procedures under the namespace of the
|
|
variable. */
|
|
for (ns = info.do_var->ns->contained; ns; ns = ns->sibling)
|
|
if (ns->proc_name && ns->proc_name == sym)
|
|
gfc_code_walker (&ns->code, doloop_contained_procedure_code,
|
|
dummy_expr_callback, &info);
|
|
}
|
|
}
|
|
|
|
f = gfc_sym_get_dummy_args (sym);
|
|
|
|
/* Without a formal arglist, there is only unknown INTENT,
|
|
which we don't check for. */
|
|
if (f == NULL)
|
|
return 0;
|
|
|
|
a = expr->value.function.actual;
|
|
|
|
while (a && f)
|
|
{
|
|
FOR_EACH_VEC_ELT (doloop_list, i, lp)
|
|
{
|
|
gfc_symbol *do_sym;
|
|
dl = lp->c;
|
|
if (dl == NULL)
|
|
break;
|
|
|
|
do_sym = dl->ext.iterator->var->symtree->n.sym;
|
|
|
|
if (a->expr && a->expr->symtree
|
|
&& a->expr->symtree->n.sym == do_sym)
|
|
{
|
|
if (f->sym->attr.intent == INTENT_OUT)
|
|
gfc_error_now ("Variable %qs at %L set to undefined value "
|
|
"inside loop beginning at %L as INTENT(OUT) "
|
|
"argument to function %qs", do_sym->name,
|
|
&a->expr->where, &doloop_list[i].c->loc,
|
|
expr->symtree->n.sym->name);
|
|
else if (f->sym->attr.intent == INTENT_INOUT)
|
|
gfc_error_now ("Variable %qs at %L not definable inside loop"
|
|
" beginning at %L as INTENT(INOUT) argument to"
|
|
" function %qs", do_sym->name,
|
|
&a->expr->where, &doloop_list[i].c->loc,
|
|
expr->symtree->n.sym->name);
|
|
}
|
|
}
|
|
a = a->next;
|
|
f = f->next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
doloop_warn (gfc_namespace *ns)
|
|
{
|
|
gfc_code_walker (&ns->code, doloop_code, do_function, NULL);
|
|
|
|
for (ns = ns->contained; ns; ns = ns->sibling)
|
|
{
|
|
if (ns->code == NULL || ns->code->op != EXEC_BLOCK)
|
|
doloop_warn (ns);
|
|
}
|
|
}
|
|
|
|
/* This selction deals with inlining calls to MATMUL. */
|
|
|
|
/* Replace calls to matmul outside of straight assignments with a temporary
|
|
variable so that later inlining will work. */
|
|
|
|
static int
|
|
matmul_to_var_expr (gfc_expr **ep, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data)
|
|
{
|
|
gfc_expr *e, *n;
|
|
bool *found = (bool *) data;
|
|
|
|
e = *ep;
|
|
|
|
if (e->expr_type != EXPR_FUNCTION
|
|
|| e->value.function.isym == NULL
|
|
|| e->value.function.isym->id != GFC_ISYM_MATMUL)
|
|
return 0;
|
|
|
|
if (forall_level > 0 || iterator_level > 0 || in_omp_workshare
|
|
|| in_omp_atomic || in_where || in_assoc_list)
|
|
return 0;
|
|
|
|
/* Check if this is already in the form c = matmul(a,b). */
|
|
|
|
if ((*current_code)->expr2 == e)
|
|
return 0;
|
|
|
|
n = create_var (e, "matmul");
|
|
|
|
/* If create_var is unable to create a variable (for example if
|
|
-fno-realloc-lhs is in force with a variable that does not have bounds
|
|
known at compile-time), just return. */
|
|
|
|
if (n == NULL)
|
|
return 0;
|
|
|
|
*ep = n;
|
|
*found = true;
|
|
return 0;
|
|
}
|
|
|
|
/* Set current_code and associated variables so that matmul_to_var_expr can
|
|
work. */
|
|
|
|
static int
|
|
matmul_to_var_code (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
if (current_code != c)
|
|
{
|
|
current_code = c;
|
|
inserted_block = NULL;
|
|
changed_statement = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Take a statement of the shape c = matmul(a,b) and create temporaries
|
|
for a and b if there is a dependency between the arguments and the
|
|
result variable or if a or b are the result of calculations that cannot
|
|
be handled by the inliner. */
|
|
|
|
static int
|
|
matmul_temp_args (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_expr *expr1, *expr2;
|
|
gfc_code *co;
|
|
gfc_actual_arglist *a, *b;
|
|
bool a_tmp, b_tmp;
|
|
gfc_expr *matrix_a, *matrix_b;
|
|
bool conjg_a, conjg_b, transpose_a, transpose_b;
|
|
|
|
co = *c;
|
|
|
|
if (co->op != EXEC_ASSIGN)
|
|
return 0;
|
|
|
|
if (forall_level > 0 || iterator_level > 0 || in_omp_workshare
|
|
|| in_omp_atomic || in_where)
|
|
return 0;
|
|
|
|
/* This has some duplication with inline_matmul_assign. This
|
|
is because the creation of temporary variables could still fail,
|
|
and inline_matmul_assign still needs to be able to handle these
|
|
cases. */
|
|
expr1 = co->expr1;
|
|
expr2 = co->expr2;
|
|
|
|
if (expr2->expr_type != EXPR_FUNCTION
|
|
|| expr2->value.function.isym == NULL
|
|
|| expr2->value.function.isym->id != GFC_ISYM_MATMUL)
|
|
return 0;
|
|
|
|
a_tmp = false;
|
|
a = expr2->value.function.actual;
|
|
matrix_a = check_conjg_transpose_variable (a->expr, &conjg_a, &transpose_a);
|
|
if (matrix_a != NULL)
|
|
{
|
|
if (matrix_a->expr_type == EXPR_VARIABLE
|
|
&& (gfc_check_dependency (matrix_a, expr1, true)
|
|
|| gfc_has_dimen_vector_ref (matrix_a)))
|
|
a_tmp = true;
|
|
}
|
|
else
|
|
a_tmp = true;
|
|
|
|
b_tmp = false;
|
|
b = a->next;
|
|
matrix_b = check_conjg_transpose_variable (b->expr, &conjg_b, &transpose_b);
|
|
if (matrix_b != NULL)
|
|
{
|
|
if (matrix_b->expr_type == EXPR_VARIABLE
|
|
&& (gfc_check_dependency (matrix_b, expr1, true)
|
|
|| gfc_has_dimen_vector_ref (matrix_b)))
|
|
b_tmp = true;
|
|
}
|
|
else
|
|
b_tmp = true;
|
|
|
|
if (!a_tmp && !b_tmp)
|
|
return 0;
|
|
|
|
current_code = c;
|
|
inserted_block = NULL;
|
|
changed_statement = NULL;
|
|
if (a_tmp)
|
|
{
|
|
gfc_expr *at;
|
|
at = create_var (a->expr,"mma");
|
|
if (at)
|
|
a->expr = at;
|
|
}
|
|
if (b_tmp)
|
|
{
|
|
gfc_expr *bt;
|
|
bt = create_var (b->expr,"mmb");
|
|
if (bt)
|
|
b->expr = bt;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Auxiliary function to build and simplify an array inquiry function.
|
|
dim is zero-based. */
|
|
|
|
static gfc_expr *
|
|
get_array_inq_function (gfc_isym_id id, gfc_expr *e, int dim, int okind = 0)
|
|
{
|
|
gfc_expr *fcn;
|
|
gfc_expr *dim_arg, *kind;
|
|
const char *name;
|
|
gfc_expr *ec;
|
|
|
|
switch (id)
|
|
{
|
|
case GFC_ISYM_LBOUND:
|
|
name = "_gfortran_lbound";
|
|
break;
|
|
|
|
case GFC_ISYM_UBOUND:
|
|
name = "_gfortran_ubound";
|
|
break;
|
|
|
|
case GFC_ISYM_SIZE:
|
|
name = "_gfortran_size";
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
|
|
dim_arg = gfc_get_int_expr (gfc_default_integer_kind, &e->where, dim);
|
|
if (okind != 0)
|
|
kind = gfc_get_int_expr (gfc_default_integer_kind, &e->where,
|
|
okind);
|
|
else
|
|
kind = gfc_get_int_expr (gfc_default_integer_kind, &e->where,
|
|
gfc_index_integer_kind);
|
|
|
|
ec = gfc_copy_expr (e);
|
|
|
|
/* No bounds checking, this will be done before the loops if -fcheck=bounds
|
|
is in effect. */
|
|
ec->no_bounds_check = 1;
|
|
fcn = gfc_build_intrinsic_call (current_ns, id, name, e->where, 3,
|
|
ec, dim_arg, kind);
|
|
gfc_simplify_expr (fcn, 0);
|
|
fcn->no_bounds_check = 1;
|
|
return fcn;
|
|
}
|
|
|
|
/* Builds a logical expression. */
|
|
|
|
static gfc_expr*
|
|
build_logical_expr (gfc_intrinsic_op op, gfc_expr *e1, gfc_expr *e2)
|
|
{
|
|
gfc_typespec ts;
|
|
gfc_expr *res;
|
|
|
|
ts.type = BT_LOGICAL;
|
|
ts.kind = gfc_default_logical_kind;
|
|
res = gfc_get_expr ();
|
|
res->where = e1->where;
|
|
res->expr_type = EXPR_OP;
|
|
res->value.op.op = op;
|
|
res->value.op.op1 = e1;
|
|
res->value.op.op2 = e2;
|
|
res->ts = ts;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/* Return an operation of one two gfc_expr (one if e2 is NULL). This assumes
|
|
compatible typespecs. */
|
|
|
|
static gfc_expr *
|
|
get_operand (gfc_intrinsic_op op, gfc_expr *e1, gfc_expr *e2)
|
|
{
|
|
gfc_expr *res;
|
|
|
|
res = gfc_get_expr ();
|
|
res->ts = e1->ts;
|
|
res->where = e1->where;
|
|
res->expr_type = EXPR_OP;
|
|
res->value.op.op = op;
|
|
res->value.op.op1 = e1;
|
|
res->value.op.op2 = e2;
|
|
gfc_simplify_expr (res, 0);
|
|
return res;
|
|
}
|
|
|
|
/* Generate the IF statement for a runtime check if we want to do inlining or
|
|
not - putting in the code for both branches and putting it into the syntax
|
|
tree is the caller's responsibility. For fixed array sizes, this should be
|
|
removed by DCE. Only called for rank-two matrices A and B. */
|
|
|
|
static gfc_code *
|
|
inline_limit_check (gfc_expr *a, gfc_expr *b, int limit)
|
|
{
|
|
gfc_expr *inline_limit;
|
|
gfc_code *if_1, *if_2, *else_2;
|
|
gfc_expr *b2, *a2, *a1, *m1, *m2;
|
|
gfc_typespec ts;
|
|
gfc_expr *cond;
|
|
|
|
/* Calculation is done in real to avoid integer overflow. */
|
|
|
|
inline_limit = gfc_get_constant_expr (BT_REAL, gfc_default_real_kind,
|
|
&a->where);
|
|
mpfr_set_si (inline_limit->value.real, limit, GFC_RND_MODE);
|
|
mpfr_pow_ui (inline_limit->value.real, inline_limit->value.real, 3,
|
|
GFC_RND_MODE);
|
|
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, a, 1);
|
|
a2 = get_array_inq_function (GFC_ISYM_SIZE, a, 2);
|
|
b2 = get_array_inq_function (GFC_ISYM_SIZE, b, 2);
|
|
|
|
gfc_clear_ts (&ts);
|
|
ts.type = BT_REAL;
|
|
ts.kind = gfc_default_real_kind;
|
|
gfc_convert_type_warn (a1, &ts, 2, 0);
|
|
gfc_convert_type_warn (a2, &ts, 2, 0);
|
|
gfc_convert_type_warn (b2, &ts, 2, 0);
|
|
|
|
m1 = get_operand (INTRINSIC_TIMES, a1, a2);
|
|
m2 = get_operand (INTRINSIC_TIMES, m1, b2);
|
|
|
|
cond = build_logical_expr (INTRINSIC_LE, m2, inline_limit);
|
|
gfc_simplify_expr (cond, 0);
|
|
|
|
else_2 = XCNEW (gfc_code);
|
|
else_2->op = EXEC_IF;
|
|
else_2->loc = a->where;
|
|
|
|
if_2 = XCNEW (gfc_code);
|
|
if_2->op = EXEC_IF;
|
|
if_2->expr1 = cond;
|
|
if_2->loc = a->where;
|
|
if_2->block = else_2;
|
|
|
|
if_1 = XCNEW (gfc_code);
|
|
if_1->op = EXEC_IF;
|
|
if_1->block = if_2;
|
|
if_1->loc = a->where;
|
|
|
|
return if_1;
|
|
}
|
|
|
|
|
|
/* Insert code to issue a runtime error if the expressions are not equal. */
|
|
|
|
static gfc_code *
|
|
runtime_error_ne (gfc_expr *e1, gfc_expr *e2, const char *msg)
|
|
{
|
|
gfc_expr *cond;
|
|
gfc_code *if_1, *if_2;
|
|
gfc_code *c;
|
|
gfc_actual_arglist *a1, *a2, *a3;
|
|
|
|
gcc_assert (e1->where.lb);
|
|
/* Build the call to runtime_error. */
|
|
c = XCNEW (gfc_code);
|
|
c->op = EXEC_CALL;
|
|
c->loc = e1->where;
|
|
|
|
/* Get a null-terminated message string. */
|
|
|
|
a1 = gfc_get_actual_arglist ();
|
|
a1->expr = gfc_get_character_expr (gfc_default_character_kind, &e1->where,
|
|
msg, strlen(msg)+1);
|
|
c->ext.actual = a1;
|
|
|
|
/* Pass the value of the first expression. */
|
|
a2 = gfc_get_actual_arglist ();
|
|
a2->expr = gfc_copy_expr (e1);
|
|
a1->next = a2;
|
|
|
|
/* Pass the value of the second expression. */
|
|
a3 = gfc_get_actual_arglist ();
|
|
a3->expr = gfc_copy_expr (e2);
|
|
a2->next = a3;
|
|
|
|
gfc_check_fe_runtime_error (c->ext.actual);
|
|
gfc_resolve_fe_runtime_error (c);
|
|
|
|
if_2 = XCNEW (gfc_code);
|
|
if_2->op = EXEC_IF;
|
|
if_2->loc = e1->where;
|
|
if_2->next = c;
|
|
|
|
if_1 = XCNEW (gfc_code);
|
|
if_1->op = EXEC_IF;
|
|
if_1->block = if_2;
|
|
if_1->loc = e1->where;
|
|
|
|
cond = build_logical_expr (INTRINSIC_NE, e1, e2);
|
|
gfc_simplify_expr (cond, 0);
|
|
if_2->expr1 = cond;
|
|
|
|
return if_1;
|
|
}
|
|
|
|
/* Handle matrix reallocation. Caller is responsible to insert into
|
|
the code tree.
|
|
|
|
For the two-dimensional case, build
|
|
|
|
if (allocated(c)) then
|
|
if (size(c,1) /= size(a,1) .or. size(c,2) /= size(b,2)) then
|
|
deallocate(c)
|
|
allocate (c(size(a,1), size(b,2)))
|
|
end if
|
|
else
|
|
allocate (c(size(a,1),size(b,2)))
|
|
end if
|
|
|
|
and for the other cases correspondingly.
|
|
*/
|
|
|
|
static gfc_code *
|
|
matmul_lhs_realloc (gfc_expr *c, gfc_expr *a, gfc_expr *b,
|
|
enum matrix_case m_case)
|
|
{
|
|
|
|
gfc_expr *allocated, *alloc_expr;
|
|
gfc_code *if_alloc_1, *if_alloc_2, *if_size_1, *if_size_2;
|
|
gfc_code *else_alloc;
|
|
gfc_code *deallocate, *allocate1, *allocate_else;
|
|
gfc_array_ref *ar;
|
|
gfc_expr *cond, *ne1, *ne2;
|
|
|
|
if (warn_realloc_lhs)
|
|
gfc_warning (OPT_Wrealloc_lhs,
|
|
"Code for reallocating the allocatable array at %L will "
|
|
"be added", &c->where);
|
|
|
|
alloc_expr = gfc_copy_expr (c);
|
|
|
|
ar = gfc_find_array_ref (alloc_expr);
|
|
gcc_assert (ar && ar->type == AR_FULL);
|
|
|
|
/* c comes in as a full ref. Change it into a copy and make it into an
|
|
element ref so it has the right form for ALLOCATE. In the same
|
|
switch statement, also generate the size comparison for the secod IF
|
|
statement. */
|
|
|
|
ar->type = AR_ELEMENT;
|
|
|
|
switch (m_case)
|
|
{
|
|
case A2B2:
|
|
ar->start[0] = get_array_inq_function (GFC_ISYM_SIZE, a, 1);
|
|
ar->start[1] = get_array_inq_function (GFC_ISYM_SIZE, b, 2);
|
|
ne1 = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 1),
|
|
get_array_inq_function (GFC_ISYM_SIZE, a, 1));
|
|
ne2 = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 2),
|
|
get_array_inq_function (GFC_ISYM_SIZE, b, 2));
|
|
cond = build_logical_expr (INTRINSIC_OR, ne1, ne2);
|
|
break;
|
|
|
|
case A2B2T:
|
|
ar->start[0] = get_array_inq_function (GFC_ISYM_SIZE, a, 1);
|
|
ar->start[1] = get_array_inq_function (GFC_ISYM_SIZE, b, 1);
|
|
|
|
ne1 = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 1),
|
|
get_array_inq_function (GFC_ISYM_SIZE, a, 1));
|
|
ne2 = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 2),
|
|
get_array_inq_function (GFC_ISYM_SIZE, b, 1));
|
|
cond = build_logical_expr (INTRINSIC_OR, ne1, ne2);
|
|
break;
|
|
|
|
case A2TB2:
|
|
|
|
ar->start[0] = get_array_inq_function (GFC_ISYM_SIZE, a, 2);
|
|
ar->start[1] = get_array_inq_function (GFC_ISYM_SIZE, b, 2);
|
|
|
|
ne1 = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 1),
|
|
get_array_inq_function (GFC_ISYM_SIZE, a, 2));
|
|
ne2 = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 2),
|
|
get_array_inq_function (GFC_ISYM_SIZE, b, 2));
|
|
cond = build_logical_expr (INTRINSIC_OR, ne1, ne2);
|
|
break;
|
|
|
|
case A2B1:
|
|
ar->start[0] = get_array_inq_function (GFC_ISYM_SIZE, a, 1);
|
|
cond = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 1),
|
|
get_array_inq_function (GFC_ISYM_SIZE, a, 2));
|
|
break;
|
|
|
|
case A1B2:
|
|
ar->start[0] = get_array_inq_function (GFC_ISYM_SIZE, b, 2);
|
|
cond = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 1),
|
|
get_array_inq_function (GFC_ISYM_SIZE, b, 2));
|
|
break;
|
|
|
|
case A2TB2T:
|
|
/* This can only happen for BLAS, we do not handle that case in
|
|
inline mamtul. */
|
|
ar->start[0] = get_array_inq_function (GFC_ISYM_SIZE, a, 2);
|
|
ar->start[1] = get_array_inq_function (GFC_ISYM_SIZE, b, 1);
|
|
|
|
ne1 = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 1),
|
|
get_array_inq_function (GFC_ISYM_SIZE, a, 2));
|
|
ne2 = build_logical_expr (INTRINSIC_NE,
|
|
get_array_inq_function (GFC_ISYM_SIZE, c, 2),
|
|
get_array_inq_function (GFC_ISYM_SIZE, b, 1));
|
|
|
|
cond = build_logical_expr (INTRINSIC_OR, ne1, ne2);
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable();
|
|
|
|
}
|
|
|
|
gfc_simplify_expr (cond, 0);
|
|
|
|
/* We need two identical allocate statements in two
|
|
branches of the IF statement. */
|
|
|
|
allocate1 = XCNEW (gfc_code);
|
|
allocate1->op = EXEC_ALLOCATE;
|
|
allocate1->ext.alloc.list = gfc_get_alloc ();
|
|
allocate1->loc = c->where;
|
|
allocate1->ext.alloc.list->expr = gfc_copy_expr (alloc_expr);
|
|
|
|
allocate_else = XCNEW (gfc_code);
|
|
allocate_else->op = EXEC_ALLOCATE;
|
|
allocate_else->ext.alloc.list = gfc_get_alloc ();
|
|
allocate_else->loc = c->where;
|
|
allocate_else->ext.alloc.list->expr = alloc_expr;
|
|
|
|
allocated = gfc_build_intrinsic_call (current_ns, GFC_ISYM_ALLOCATED,
|
|
"_gfortran_allocated", c->where,
|
|
1, gfc_copy_expr (c));
|
|
|
|
deallocate = XCNEW (gfc_code);
|
|
deallocate->op = EXEC_DEALLOCATE;
|
|
deallocate->ext.alloc.list = gfc_get_alloc ();
|
|
deallocate->ext.alloc.list->expr = gfc_copy_expr (c);
|
|
deallocate->next = allocate1;
|
|
deallocate->loc = c->where;
|
|
|
|
if_size_2 = XCNEW (gfc_code);
|
|
if_size_2->op = EXEC_IF;
|
|
if_size_2->expr1 = cond;
|
|
if_size_2->loc = c->where;
|
|
if_size_2->next = deallocate;
|
|
|
|
if_size_1 = XCNEW (gfc_code);
|
|
if_size_1->op = EXEC_IF;
|
|
if_size_1->block = if_size_2;
|
|
if_size_1->loc = c->where;
|
|
|
|
else_alloc = XCNEW (gfc_code);
|
|
else_alloc->op = EXEC_IF;
|
|
else_alloc->loc = c->where;
|
|
else_alloc->next = allocate_else;
|
|
|
|
if_alloc_2 = XCNEW (gfc_code);
|
|
if_alloc_2->op = EXEC_IF;
|
|
if_alloc_2->expr1 = allocated;
|
|
if_alloc_2->loc = c->where;
|
|
if_alloc_2->next = if_size_1;
|
|
if_alloc_2->block = else_alloc;
|
|
|
|
if_alloc_1 = XCNEW (gfc_code);
|
|
if_alloc_1->op = EXEC_IF;
|
|
if_alloc_1->block = if_alloc_2;
|
|
if_alloc_1->loc = c->where;
|
|
|
|
return if_alloc_1;
|
|
}
|
|
|
|
/* Callback function for has_function_or_op. */
|
|
|
|
static int
|
|
is_function_or_op (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
if ((*e) == 0)
|
|
return 0;
|
|
else
|
|
return (*e)->expr_type == EXPR_FUNCTION
|
|
|| (*e)->expr_type == EXPR_OP;
|
|
}
|
|
|
|
/* Returns true if the expression contains a function. */
|
|
|
|
static bool
|
|
has_function_or_op (gfc_expr **e)
|
|
{
|
|
if (e == NULL)
|
|
return false;
|
|
else
|
|
return gfc_expr_walker (e, is_function_or_op, NULL);
|
|
}
|
|
|
|
/* Freeze (assign to a temporary variable) a single expression. */
|
|
|
|
static void
|
|
freeze_expr (gfc_expr **ep)
|
|
{
|
|
gfc_expr *ne;
|
|
if (has_function_or_op (ep))
|
|
{
|
|
ne = create_var (*ep, "freeze");
|
|
*ep = ne;
|
|
}
|
|
}
|
|
|
|
/* Go through an expression's references and assign them to temporary
|
|
variables if they contain functions. This is usually done prior to
|
|
front-end scalarization to avoid multiple invocations of functions. */
|
|
|
|
static void
|
|
freeze_references (gfc_expr *e)
|
|
{
|
|
gfc_ref *r;
|
|
gfc_array_ref *ar;
|
|
int i;
|
|
|
|
for (r=e->ref; r; r=r->next)
|
|
{
|
|
if (r->type == REF_SUBSTRING)
|
|
{
|
|
if (r->u.ss.start != NULL)
|
|
freeze_expr (&r->u.ss.start);
|
|
|
|
if (r->u.ss.end != NULL)
|
|
freeze_expr (&r->u.ss.end);
|
|
}
|
|
else if (r->type == REF_ARRAY)
|
|
{
|
|
ar = &r->u.ar;
|
|
switch (ar->type)
|
|
{
|
|
case AR_FULL:
|
|
break;
|
|
|
|
case AR_SECTION:
|
|
for (i=0; i<ar->dimen; i++)
|
|
{
|
|
if (ar->dimen_type[i] == DIMEN_RANGE)
|
|
{
|
|
freeze_expr (&ar->start[i]);
|
|
freeze_expr (&ar->end[i]);
|
|
freeze_expr (&ar->stride[i]);
|
|
}
|
|
else if (ar->dimen_type[i] == DIMEN_ELEMENT)
|
|
{
|
|
freeze_expr (&ar->start[i]);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AR_ELEMENT:
|
|
for (i=0; i<ar->dimen; i++)
|
|
freeze_expr (&ar->start[i]);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Convert to gfc_index_integer_kind if needed, just do a copy otherwise. */
|
|
|
|
static gfc_expr *
|
|
convert_to_index_kind (gfc_expr *e)
|
|
{
|
|
gfc_expr *res;
|
|
|
|
gcc_assert (e != NULL);
|
|
|
|
res = gfc_copy_expr (e);
|
|
|
|
gcc_assert (e->ts.type == BT_INTEGER);
|
|
|
|
if (res->ts.kind != gfc_index_integer_kind)
|
|
{
|
|
gfc_typespec ts;
|
|
gfc_clear_ts (&ts);
|
|
ts.type = BT_INTEGER;
|
|
ts.kind = gfc_index_integer_kind;
|
|
|
|
gfc_convert_type_warn (e, &ts, 2, 0);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Function to create a DO loop including creation of the
|
|
iteration variable. gfc_expr are copied.*/
|
|
|
|
static gfc_code *
|
|
create_do_loop (gfc_expr *start, gfc_expr *end, gfc_expr *step, locus *where,
|
|
gfc_namespace *ns, char *vname)
|
|
{
|
|
|
|
char name[GFC_MAX_SYMBOL_LEN +1];
|
|
gfc_symtree *symtree;
|
|
gfc_symbol *symbol;
|
|
gfc_expr *i;
|
|
gfc_code *n, *n2;
|
|
|
|
/* Create an expression for the iteration variable. */
|
|
if (vname)
|
|
sprintf (name, "__var_%d_do_%s", var_num++, vname);
|
|
else
|
|
sprintf (name, "__var_%d_do", var_num++);
|
|
|
|
|
|
if (gfc_get_sym_tree (name, ns, &symtree, false) != 0)
|
|
gcc_unreachable ();
|
|
|
|
/* Create the loop variable. */
|
|
|
|
symbol = symtree->n.sym;
|
|
symbol->ts.type = BT_INTEGER;
|
|
symbol->ts.kind = gfc_index_integer_kind;
|
|
symbol->attr.flavor = FL_VARIABLE;
|
|
symbol->attr.referenced = 1;
|
|
symbol->attr.dimension = 0;
|
|
symbol->attr.fe_temp = 1;
|
|
gfc_commit_symbol (symbol);
|
|
|
|
i = gfc_get_expr ();
|
|
i->expr_type = EXPR_VARIABLE;
|
|
i->ts = symbol->ts;
|
|
i->rank = 0;
|
|
i->where = *where;
|
|
i->symtree = symtree;
|
|
|
|
/* ... and the nested DO statements. */
|
|
n = XCNEW (gfc_code);
|
|
n->op = EXEC_DO;
|
|
n->loc = *where;
|
|
n->ext.iterator = gfc_get_iterator ();
|
|
n->ext.iterator->var = i;
|
|
n->ext.iterator->start = convert_to_index_kind (start);
|
|
n->ext.iterator->end = convert_to_index_kind (end);
|
|
if (step)
|
|
n->ext.iterator->step = convert_to_index_kind (step);
|
|
else
|
|
n->ext.iterator->step = gfc_get_int_expr (gfc_index_integer_kind,
|
|
where, 1);
|
|
|
|
n2 = XCNEW (gfc_code);
|
|
n2->op = EXEC_DO;
|
|
n2->loc = *where;
|
|
n2->next = NULL;
|
|
n->block = n2;
|
|
return n;
|
|
}
|
|
|
|
/* Get the upper bound of the DO loops for matmul along a dimension. This
|
|
is one-based. */
|
|
|
|
static gfc_expr*
|
|
get_size_m1 (gfc_expr *e, int dimen)
|
|
{
|
|
mpz_t size;
|
|
gfc_expr *res;
|
|
|
|
if (gfc_array_dimen_size (e, dimen - 1, &size))
|
|
{
|
|
res = gfc_get_constant_expr (BT_INTEGER,
|
|
gfc_index_integer_kind, &e->where);
|
|
mpz_sub_ui (res->value.integer, size, 1);
|
|
mpz_clear (size);
|
|
}
|
|
else
|
|
{
|
|
res = get_operand (INTRINSIC_MINUS,
|
|
get_array_inq_function (GFC_ISYM_SIZE, e, dimen),
|
|
gfc_get_int_expr (gfc_index_integer_kind,
|
|
&e->where, 1));
|
|
gfc_simplify_expr (res, 0);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* Function to return a scalarized expression. It is assumed that indices are
|
|
zero based to make generation of DO loops easier. A zero as index will
|
|
access the first element along a dimension. Single element references will
|
|
be skipped. A NULL as an expression will be replaced by a full reference.
|
|
This assumes that the index loops have gfc_index_integer_kind, and that all
|
|
references have been frozen. */
|
|
|
|
static gfc_expr*
|
|
scalarized_expr (gfc_expr *e_in, gfc_expr **index, int count_index)
|
|
{
|
|
gfc_array_ref *ar;
|
|
int i;
|
|
int rank;
|
|
gfc_expr *e;
|
|
int i_index;
|
|
bool was_fullref;
|
|
|
|
e = gfc_copy_expr(e_in);
|
|
|
|
rank = e->rank;
|
|
|
|
ar = gfc_find_array_ref (e);
|
|
|
|
/* We scalarize count_index variables, reducing the rank by count_index. */
|
|
|
|
e->rank = rank - count_index;
|
|
|
|
was_fullref = ar->type == AR_FULL;
|
|
|
|
if (e->rank == 0)
|
|
ar->type = AR_ELEMENT;
|
|
else
|
|
ar->type = AR_SECTION;
|
|
|
|
/* Loop over the indices. For each index, create the expression
|
|
index * stride + lbound(e, dim). */
|
|
|
|
i_index = 0;
|
|
for (i=0; i < ar->dimen; i++)
|
|
{
|
|
if (was_fullref || ar->dimen_type[i] == DIMEN_RANGE)
|
|
{
|
|
if (index[i_index] != NULL)
|
|
{
|
|
gfc_expr *lbound, *nindex;
|
|
gfc_expr *loopvar;
|
|
|
|
loopvar = gfc_copy_expr (index[i_index]);
|
|
|
|
if (ar->stride[i])
|
|
{
|
|
gfc_expr *tmp;
|
|
|
|
tmp = gfc_copy_expr(ar->stride[i]);
|
|
if (tmp->ts.kind != gfc_index_integer_kind)
|
|
{
|
|
gfc_typespec ts;
|
|
gfc_clear_ts (&ts);
|
|
ts.type = BT_INTEGER;
|
|
ts.kind = gfc_index_integer_kind;
|
|
gfc_convert_type (tmp, &ts, 2);
|
|
}
|
|
nindex = get_operand (INTRINSIC_TIMES, loopvar, tmp);
|
|
}
|
|
else
|
|
nindex = loopvar;
|
|
|
|
/* Calculate the lower bound of the expression. */
|
|
if (ar->start[i])
|
|
{
|
|
lbound = gfc_copy_expr (ar->start[i]);
|
|
if (lbound->ts.kind != gfc_index_integer_kind)
|
|
{
|
|
gfc_typespec ts;
|
|
gfc_clear_ts (&ts);
|
|
ts.type = BT_INTEGER;
|
|
ts.kind = gfc_index_integer_kind;
|
|
gfc_convert_type (lbound, &ts, 2);
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gfc_expr *lbound_e;
|
|
gfc_ref *ref;
|
|
|
|
lbound_e = gfc_copy_expr (e_in);
|
|
|
|
for (ref = lbound_e->ref; ref; ref = ref->next)
|
|
if (ref->type == REF_ARRAY
|
|
&& (ref->u.ar.type == AR_FULL
|
|
|| ref->u.ar.type == AR_SECTION))
|
|
break;
|
|
|
|
if (ref->next)
|
|
{
|
|
gfc_free_ref_list (ref->next);
|
|
ref->next = NULL;
|
|
}
|
|
|
|
if (!was_fullref)
|
|
{
|
|
/* Look at full individual sections, like a(:). The first index
|
|
is the lbound of a full ref. */
|
|
int j;
|
|
gfc_array_ref *ar;
|
|
int to;
|
|
|
|
ar = &ref->u.ar;
|
|
|
|
/* For assumed size, we need to keep around the final
|
|
reference in order not to get an error on resolution
|
|
below, and we cannot use AR_FULL. */
|
|
|
|
if (ar->as->type == AS_ASSUMED_SIZE)
|
|
{
|
|
ar->type = AR_SECTION;
|
|
to = ar->dimen - 1;
|
|
}
|
|
else
|
|
{
|
|
to = ar->dimen;
|
|
ar->type = AR_FULL;
|
|
}
|
|
|
|
for (j = 0; j < to; j++)
|
|
{
|
|
gfc_free_expr (ar->start[j]);
|
|
ar->start[j] = NULL;
|
|
gfc_free_expr (ar->end[j]);
|
|
ar->end[j] = NULL;
|
|
gfc_free_expr (ar->stride[j]);
|
|
ar->stride[j] = NULL;
|
|
}
|
|
|
|
/* We have to get rid of the shape, if there is one. Do
|
|
so by freeing it and calling gfc_resolve to rebuild
|
|
it, if necessary. */
|
|
|
|
if (lbound_e->shape)
|
|
gfc_free_shape (&(lbound_e->shape), lbound_e->rank);
|
|
|
|
lbound_e->rank = ar->dimen;
|
|
gfc_resolve_expr (lbound_e);
|
|
}
|
|
lbound = get_array_inq_function (GFC_ISYM_LBOUND, lbound_e,
|
|
i + 1);
|
|
gfc_free_expr (lbound_e);
|
|
}
|
|
|
|
ar->dimen_type[i] = DIMEN_ELEMENT;
|
|
|
|
gfc_free_expr (ar->start[i]);
|
|
ar->start[i] = get_operand (INTRINSIC_PLUS, nindex, lbound);
|
|
|
|
gfc_free_expr (ar->end[i]);
|
|
ar->end[i] = NULL;
|
|
gfc_free_expr (ar->stride[i]);
|
|
ar->stride[i] = NULL;
|
|
gfc_simplify_expr (ar->start[i], 0);
|
|
}
|
|
else if (was_fullref)
|
|
{
|
|
gfc_internal_error ("Scalarization using DIMEN_RANGE unimplemented");
|
|
}
|
|
i_index ++;
|
|
}
|
|
}
|
|
|
|
/* Bounds checking will be done before the loops if -fcheck=bounds
|
|
is in effect. */
|
|
e->no_bounds_check = 1;
|
|
return e;
|
|
}
|
|
|
|
/* Helper function to check for a dimen vector as subscript. */
|
|
|
|
bool
|
|
gfc_has_dimen_vector_ref (gfc_expr *e)
|
|
{
|
|
gfc_array_ref *ar;
|
|
int i;
|
|
|
|
ar = gfc_find_array_ref (e);
|
|
gcc_assert (ar);
|
|
if (ar->type == AR_FULL)
|
|
return false;
|
|
|
|
for (i=0; i<ar->dimen; i++)
|
|
if (ar->dimen_type[i] == DIMEN_VECTOR)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* If handed an expression of the form
|
|
|
|
TRANSPOSE(CONJG(A))
|
|
|
|
check if A can be handled by matmul and return if there is an uneven number
|
|
of CONJG calls. Return a pointer to the array when everything is OK, NULL
|
|
otherwise. The caller has to check for the correct rank. */
|
|
|
|
static gfc_expr*
|
|
check_conjg_transpose_variable (gfc_expr *e, bool *conjg, bool *transpose)
|
|
{
|
|
*conjg = false;
|
|
*transpose = false;
|
|
|
|
do
|
|
{
|
|
if (e->expr_type == EXPR_VARIABLE)
|
|
{
|
|
gcc_assert (e->rank == 1 || e->rank == 2);
|
|
return e;
|
|
}
|
|
else if (e->expr_type == EXPR_FUNCTION)
|
|
{
|
|
if (e->value.function.isym == NULL)
|
|
return NULL;
|
|
|
|
if (e->value.function.isym->id == GFC_ISYM_CONJG)
|
|
*conjg = !*conjg;
|
|
else if (e->value.function.isym->id == GFC_ISYM_TRANSPOSE)
|
|
*transpose = !*transpose;
|
|
else return NULL;
|
|
}
|
|
else
|
|
return NULL;
|
|
|
|
e = e->value.function.actual->expr;
|
|
}
|
|
while(1);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Macros for unified error messages. */
|
|
|
|
#define B_ERROR_1 _("Incorrect extent in argument B in MATMUL intrinsic in " \
|
|
"dimension 1: is %ld, should be %ld")
|
|
|
|
#define C_ERROR_1 _("Array bound mismatch for dimension 1 of array " \
|
|
"(%ld/%ld)")
|
|
|
|
#define C_ERROR_2 _("Array bound mismatch for dimension 2 of array " \
|
|
"(%ld/%ld)")
|
|
|
|
|
|
/* Inline assignments of the form c = matmul(a,b).
|
|
Handle only the cases currently where b and c are rank-two arrays.
|
|
|
|
This basically translates the code to
|
|
|
|
BLOCK
|
|
integer i,j,k
|
|
c = 0
|
|
do j=0, size(b,2)-1
|
|
do k=0, size(a, 2)-1
|
|
do i=0, size(a, 1)-1
|
|
c(i * stride(c,1) + lbound(c,1), j * stride(c,2) + lbound(c,2)) =
|
|
c(i * stride(c,1) + lbound(c,1), j * stride(c,2) + lbound(c,2)) +
|
|
a(i * stride(a,1) + lbound(a,1), k * stride(a,2) + lbound(a,2)) *
|
|
b(k * stride(b,1) + lbound(b,1), j * stride(b,2) + lbound(b,2))
|
|
end do
|
|
end do
|
|
end do
|
|
END BLOCK
|
|
|
|
*/
|
|
|
|
static int
|
|
inline_matmul_assign (gfc_code **c, int *walk_subtrees,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_code *co = *c;
|
|
gfc_expr *expr1, *expr2;
|
|
gfc_expr *matrix_a, *matrix_b;
|
|
gfc_actual_arglist *a, *b;
|
|
gfc_code *do_1, *do_2, *do_3, *assign_zero, *assign_matmul;
|
|
gfc_expr *zero_e;
|
|
gfc_expr *u1, *u2, *u3;
|
|
gfc_expr *list[2];
|
|
gfc_expr *ascalar, *bscalar, *cscalar;
|
|
gfc_expr *mult;
|
|
gfc_expr *var_1, *var_2, *var_3;
|
|
gfc_expr *zero;
|
|
gfc_namespace *ns;
|
|
gfc_intrinsic_op op_times, op_plus;
|
|
enum matrix_case m_case;
|
|
int i;
|
|
gfc_code *if_limit = NULL;
|
|
gfc_code **next_code_point;
|
|
bool conjg_a, conjg_b, transpose_a, transpose_b;
|
|
bool realloc_c;
|
|
|
|
if (co->op != EXEC_ASSIGN)
|
|
return 0;
|
|
|
|
if (in_where || in_assoc_list)
|
|
return 0;
|
|
|
|
/* The BLOCKS generated for the temporary variables and FORALL don't
|
|
mix. */
|
|
if (forall_level > 0)
|
|
return 0;
|
|
|
|
/* For now don't do anything in OpenMP workshare, it confuses
|
|
its translation, which expects only the allowed statements in there.
|
|
We should figure out how to parallelize this eventually. */
|
|
if (in_omp_workshare || in_omp_atomic)
|
|
return 0;
|
|
|
|
expr1 = co->expr1;
|
|
expr2 = co->expr2;
|
|
if (expr2->expr_type != EXPR_FUNCTION
|
|
|| expr2->value.function.isym == NULL
|
|
|| expr2->value.function.isym->id != GFC_ISYM_MATMUL)
|
|
return 0;
|
|
|
|
current_code = c;
|
|
inserted_block = NULL;
|
|
changed_statement = NULL;
|
|
|
|
a = expr2->value.function.actual;
|
|
matrix_a = check_conjg_transpose_variable (a->expr, &conjg_a, &transpose_a);
|
|
if (matrix_a == NULL)
|
|
return 0;
|
|
|
|
b = a->next;
|
|
matrix_b = check_conjg_transpose_variable (b->expr, &conjg_b, &transpose_b);
|
|
if (matrix_b == NULL)
|
|
return 0;
|
|
|
|
if (gfc_has_dimen_vector_ref (expr1) || gfc_has_dimen_vector_ref (matrix_a)
|
|
|| gfc_has_dimen_vector_ref (matrix_b))
|
|
return 0;
|
|
|
|
/* We do not handle data dependencies yet. */
|
|
if (gfc_check_dependency (expr1, matrix_a, true)
|
|
|| gfc_check_dependency (expr1, matrix_b, true))
|
|
return 0;
|
|
|
|
m_case = none;
|
|
if (matrix_a->rank == 2)
|
|
{
|
|
if (transpose_a)
|
|
{
|
|
if (matrix_b->rank == 2 && !transpose_b)
|
|
m_case = A2TB2;
|
|
}
|
|
else
|
|
{
|
|
if (matrix_b->rank == 1)
|
|
m_case = A2B1;
|
|
else /* matrix_b->rank == 2 */
|
|
{
|
|
if (transpose_b)
|
|
m_case = A2B2T;
|
|
else
|
|
m_case = A2B2;
|
|
}
|
|
}
|
|
}
|
|
else /* matrix_a->rank == 1 */
|
|
{
|
|
if (matrix_b->rank == 2)
|
|
{
|
|
if (!transpose_b)
|
|
m_case = A1B2;
|
|
}
|
|
}
|
|
|
|
if (m_case == none)
|
|
return 0;
|
|
|
|
ns = insert_block ();
|
|
|
|
/* Assign the type of the zero expression for initializing the resulting
|
|
array, and the expression (+ and * for real, integer and complex;
|
|
.and. and .or for logical. */
|
|
|
|
switch(expr1->ts.type)
|
|
{
|
|
case BT_INTEGER:
|
|
zero_e = gfc_get_int_expr (expr1->ts.kind, &expr1->where, 0);
|
|
op_times = INTRINSIC_TIMES;
|
|
op_plus = INTRINSIC_PLUS;
|
|
break;
|
|
|
|
case BT_LOGICAL:
|
|
op_times = INTRINSIC_AND;
|
|
op_plus = INTRINSIC_OR;
|
|
zero_e = gfc_get_logical_expr (expr1->ts.kind, &expr1->where,
|
|
0);
|
|
break;
|
|
case BT_REAL:
|
|
zero_e = gfc_get_constant_expr (BT_REAL, expr1->ts.kind,
|
|
&expr1->where);
|
|
mpfr_set_si (zero_e->value.real, 0, GFC_RND_MODE);
|
|
op_times = INTRINSIC_TIMES;
|
|
op_plus = INTRINSIC_PLUS;
|
|
break;
|
|
|
|
case BT_COMPLEX:
|
|
zero_e = gfc_get_constant_expr (BT_COMPLEX, expr1->ts.kind,
|
|
&expr1->where);
|
|
mpc_set_si_si (zero_e->value.complex, 0, 0, GFC_RND_MODE);
|
|
op_times = INTRINSIC_TIMES;
|
|
op_plus = INTRINSIC_PLUS;
|
|
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable();
|
|
}
|
|
|
|
current_code = &ns->code;
|
|
|
|
/* Freeze the references, keeping track of how many temporary variables were
|
|
created. */
|
|
n_vars = 0;
|
|
freeze_references (matrix_a);
|
|
freeze_references (matrix_b);
|
|
freeze_references (expr1);
|
|
|
|
if (n_vars == 0)
|
|
next_code_point = current_code;
|
|
else
|
|
{
|
|
next_code_point = &ns->code;
|
|
for (i=0; i<n_vars; i++)
|
|
next_code_point = &(*next_code_point)->next;
|
|
}
|
|
|
|
/* Take care of the inline flag. If the limit check evaluates to a
|
|
constant, dead code elimination will eliminate the unneeded branch. */
|
|
|
|
if (flag_inline_matmul_limit > 0 && matrix_a->rank == 2
|
|
&& matrix_b->rank == 2)
|
|
{
|
|
if_limit = inline_limit_check (matrix_a, matrix_b,
|
|
flag_inline_matmul_limit);
|
|
|
|
/* Insert the original statement into the else branch. */
|
|
if_limit->block->block->next = co;
|
|
co->next = NULL;
|
|
|
|
/* ... and the new ones go into the original one. */
|
|
*next_code_point = if_limit;
|
|
next_code_point = &if_limit->block->next;
|
|
}
|
|
|
|
zero_e->no_bounds_check = 1;
|
|
|
|
assign_zero = XCNEW (gfc_code);
|
|
assign_zero->op = EXEC_ASSIGN;
|
|
assign_zero->loc = co->loc;
|
|
assign_zero->expr1 = gfc_copy_expr (expr1);
|
|
assign_zero->expr1->no_bounds_check = 1;
|
|
assign_zero->expr2 = zero_e;
|
|
|
|
realloc_c = flag_realloc_lhs && gfc_is_reallocatable_lhs (expr1);
|
|
|
|
if (gfc_option.rtcheck & GFC_RTCHECK_BOUNDS)
|
|
{
|
|
gfc_code *test;
|
|
gfc_expr *a2, *b1, *c1, *c2, *a1, *b2;
|
|
|
|
switch (m_case)
|
|
{
|
|
case A2B1:
|
|
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 1);
|
|
a2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 2);
|
|
test = runtime_error_ne (b1, a2, B_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
if (!realloc_c)
|
|
{
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 1);
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 1);
|
|
test = runtime_error_ne (c1, a1, C_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
}
|
|
break;
|
|
|
|
case A1B2:
|
|
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 1);
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 1);
|
|
test = runtime_error_ne (b1, a1, B_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
if (!realloc_c)
|
|
{
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 1);
|
|
b2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 2);
|
|
test = runtime_error_ne (c1, b2, C_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
}
|
|
break;
|
|
|
|
case A2B2:
|
|
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 1);
|
|
a2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 2);
|
|
test = runtime_error_ne (b1, a2, B_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
if (!realloc_c)
|
|
{
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 1);
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 1);
|
|
test = runtime_error_ne (c1, a1, C_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
c2 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 2);
|
|
b2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 2);
|
|
test = runtime_error_ne (c2, b2, C_ERROR_2);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
}
|
|
break;
|
|
|
|
case A2B2T:
|
|
|
|
b2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 2);
|
|
a2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 2);
|
|
/* matrix_b is transposed, hence dimension 1 for the error message. */
|
|
test = runtime_error_ne (b2, a2, B_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
if (!realloc_c)
|
|
{
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 1);
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 1);
|
|
test = runtime_error_ne (c1, a1, C_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
c2 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 2);
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 1);
|
|
test = runtime_error_ne (c2, b1, C_ERROR_2);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
}
|
|
break;
|
|
|
|
case A2TB2:
|
|
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 1);
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 1);
|
|
test = runtime_error_ne (b1, a1, B_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
if (!realloc_c)
|
|
{
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 1);
|
|
a2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 2);
|
|
test = runtime_error_ne (c1, a2, C_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
c2 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 2);
|
|
b2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 2);
|
|
test = runtime_error_ne (c2, b2, C_ERROR_2);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
|
|
/* Handle the reallocation, if needed. */
|
|
|
|
if (realloc_c)
|
|
{
|
|
gfc_code *lhs_alloc;
|
|
|
|
lhs_alloc = matmul_lhs_realloc (expr1, matrix_a, matrix_b, m_case);
|
|
|
|
*next_code_point = lhs_alloc;
|
|
next_code_point = &lhs_alloc->next;
|
|
|
|
}
|
|
|
|
*next_code_point = assign_zero;
|
|
|
|
zero = gfc_get_int_expr (gfc_index_integer_kind, &co->loc, 0);
|
|
|
|
assign_matmul = XCNEW (gfc_code);
|
|
assign_matmul->op = EXEC_ASSIGN;
|
|
assign_matmul->loc = co->loc;
|
|
|
|
/* Get the bounds for the loops, create them and create the scalarized
|
|
expressions. */
|
|
|
|
switch (m_case)
|
|
{
|
|
case A2B2:
|
|
|
|
u1 = get_size_m1 (matrix_b, 2);
|
|
u2 = get_size_m1 (matrix_a, 2);
|
|
u3 = get_size_m1 (matrix_a, 1);
|
|
|
|
do_1 = create_do_loop (gfc_copy_expr (zero), u1, NULL, &co->loc, ns);
|
|
do_2 = create_do_loop (gfc_copy_expr (zero), u2, NULL, &co->loc, ns);
|
|
do_3 = create_do_loop (gfc_copy_expr (zero), u3, NULL, &co->loc, ns);
|
|
|
|
do_1->block->next = do_2;
|
|
do_2->block->next = do_3;
|
|
do_3->block->next = assign_matmul;
|
|
|
|
var_1 = do_1->ext.iterator->var;
|
|
var_2 = do_2->ext.iterator->var;
|
|
var_3 = do_3->ext.iterator->var;
|
|
|
|
list[0] = var_3;
|
|
list[1] = var_1;
|
|
cscalar = scalarized_expr (co->expr1, list, 2);
|
|
|
|
list[0] = var_3;
|
|
list[1] = var_2;
|
|
ascalar = scalarized_expr (matrix_a, list, 2);
|
|
|
|
list[0] = var_2;
|
|
list[1] = var_1;
|
|
bscalar = scalarized_expr (matrix_b, list, 2);
|
|
|
|
break;
|
|
|
|
case A2B2T:
|
|
|
|
u1 = get_size_m1 (matrix_b, 1);
|
|
u2 = get_size_m1 (matrix_a, 2);
|
|
u3 = get_size_m1 (matrix_a, 1);
|
|
|
|
do_1 = create_do_loop (gfc_copy_expr (zero), u1, NULL, &co->loc, ns);
|
|
do_2 = create_do_loop (gfc_copy_expr (zero), u2, NULL, &co->loc, ns);
|
|
do_3 = create_do_loop (gfc_copy_expr (zero), u3, NULL, &co->loc, ns);
|
|
|
|
do_1->block->next = do_2;
|
|
do_2->block->next = do_3;
|
|
do_3->block->next = assign_matmul;
|
|
|
|
var_1 = do_1->ext.iterator->var;
|
|
var_2 = do_2->ext.iterator->var;
|
|
var_3 = do_3->ext.iterator->var;
|
|
|
|
list[0] = var_3;
|
|
list[1] = var_1;
|
|
cscalar = scalarized_expr (co->expr1, list, 2);
|
|
|
|
list[0] = var_3;
|
|
list[1] = var_2;
|
|
ascalar = scalarized_expr (matrix_a, list, 2);
|
|
|
|
list[0] = var_1;
|
|
list[1] = var_2;
|
|
bscalar = scalarized_expr (matrix_b, list, 2);
|
|
|
|
break;
|
|
|
|
case A2TB2:
|
|
|
|
u1 = get_size_m1 (matrix_a, 2);
|
|
u2 = get_size_m1 (matrix_b, 2);
|
|
u3 = get_size_m1 (matrix_a, 1);
|
|
|
|
do_1 = create_do_loop (gfc_copy_expr (zero), u1, NULL, &co->loc, ns);
|
|
do_2 = create_do_loop (gfc_copy_expr (zero), u2, NULL, &co->loc, ns);
|
|
do_3 = create_do_loop (gfc_copy_expr (zero), u3, NULL, &co->loc, ns);
|
|
|
|
do_1->block->next = do_2;
|
|
do_2->block->next = do_3;
|
|
do_3->block->next = assign_matmul;
|
|
|
|
var_1 = do_1->ext.iterator->var;
|
|
var_2 = do_2->ext.iterator->var;
|
|
var_3 = do_3->ext.iterator->var;
|
|
|
|
list[0] = var_1;
|
|
list[1] = var_2;
|
|
cscalar = scalarized_expr (co->expr1, list, 2);
|
|
|
|
list[0] = var_3;
|
|
list[1] = var_1;
|
|
ascalar = scalarized_expr (matrix_a, list, 2);
|
|
|
|
list[0] = var_3;
|
|
list[1] = var_2;
|
|
bscalar = scalarized_expr (matrix_b, list, 2);
|
|
|
|
break;
|
|
|
|
case A2B1:
|
|
u1 = get_size_m1 (matrix_b, 1);
|
|
u2 = get_size_m1 (matrix_a, 1);
|
|
|
|
do_1 = create_do_loop (gfc_copy_expr (zero), u1, NULL, &co->loc, ns);
|
|
do_2 = create_do_loop (gfc_copy_expr (zero), u2, NULL, &co->loc, ns);
|
|
|
|
do_1->block->next = do_2;
|
|
do_2->block->next = assign_matmul;
|
|
|
|
var_1 = do_1->ext.iterator->var;
|
|
var_2 = do_2->ext.iterator->var;
|
|
|
|
list[0] = var_2;
|
|
cscalar = scalarized_expr (co->expr1, list, 1);
|
|
|
|
list[0] = var_2;
|
|
list[1] = var_1;
|
|
ascalar = scalarized_expr (matrix_a, list, 2);
|
|
|
|
list[0] = var_1;
|
|
bscalar = scalarized_expr (matrix_b, list, 1);
|
|
|
|
break;
|
|
|
|
case A1B2:
|
|
u1 = get_size_m1 (matrix_b, 2);
|
|
u2 = get_size_m1 (matrix_a, 1);
|
|
|
|
do_1 = create_do_loop (gfc_copy_expr (zero), u1, NULL, &co->loc, ns);
|
|
do_2 = create_do_loop (gfc_copy_expr (zero), u2, NULL, &co->loc, ns);
|
|
|
|
do_1->block->next = do_2;
|
|
do_2->block->next = assign_matmul;
|
|
|
|
var_1 = do_1->ext.iterator->var;
|
|
var_2 = do_2->ext.iterator->var;
|
|
|
|
list[0] = var_1;
|
|
cscalar = scalarized_expr (co->expr1, list, 1);
|
|
|
|
list[0] = var_2;
|
|
ascalar = scalarized_expr (matrix_a, list, 1);
|
|
|
|
list[0] = var_2;
|
|
list[1] = var_1;
|
|
bscalar = scalarized_expr (matrix_b, list, 2);
|
|
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable();
|
|
}
|
|
|
|
/* Build the conjg call around the variables. Set the typespec manually
|
|
because gfc_build_intrinsic_call sometimes gets this wrong. */
|
|
if (conjg_a)
|
|
{
|
|
gfc_typespec ts;
|
|
ts = matrix_a->ts;
|
|
ascalar = gfc_build_intrinsic_call (ns, GFC_ISYM_CONJG, "conjg",
|
|
matrix_a->where, 1, ascalar);
|
|
ascalar->ts = ts;
|
|
}
|
|
|
|
if (conjg_b)
|
|
{
|
|
gfc_typespec ts;
|
|
ts = matrix_b->ts;
|
|
bscalar = gfc_build_intrinsic_call (ns, GFC_ISYM_CONJG, "conjg",
|
|
matrix_b->where, 1, bscalar);
|
|
bscalar->ts = ts;
|
|
}
|
|
/* First loop comes after the zero assignment. */
|
|
assign_zero->next = do_1;
|
|
|
|
/* Build the assignment expression in the loop. */
|
|
assign_matmul->expr1 = gfc_copy_expr (cscalar);
|
|
|
|
mult = get_operand (op_times, ascalar, bscalar);
|
|
assign_matmul->expr2 = get_operand (op_plus, cscalar, mult);
|
|
|
|
/* If we don't want to keep the original statement around in
|
|
the else branch, we can free it. */
|
|
|
|
if (if_limit == NULL)
|
|
gfc_free_statements(co);
|
|
else
|
|
co->next = NULL;
|
|
|
|
gfc_free_expr (zero);
|
|
*walk_subtrees = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Change matmul function calls in the form of
|
|
|
|
c = matmul(a,b)
|
|
|
|
to the corresponding call to a BLAS routine, if applicable. */
|
|
|
|
static int
|
|
call_external_blas (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_code *co, *co_next;
|
|
gfc_expr *expr1, *expr2;
|
|
gfc_expr *matrix_a, *matrix_b;
|
|
gfc_code *if_limit = NULL;
|
|
gfc_actual_arglist *a, *b;
|
|
bool conjg_a, conjg_b, transpose_a, transpose_b;
|
|
gfc_code *call;
|
|
const char *blas_name;
|
|
const char *transa, *transb;
|
|
gfc_expr *c1, *c2, *b1;
|
|
gfc_actual_arglist *actual, *next;
|
|
bt type;
|
|
int kind;
|
|
enum matrix_case m_case;
|
|
bool realloc_c;
|
|
gfc_code **next_code_point;
|
|
|
|
/* Many of the tests for inline matmul also apply here. */
|
|
|
|
co = *c;
|
|
|
|
if (co->op != EXEC_ASSIGN)
|
|
return 0;
|
|
|
|
if (in_where || in_assoc_list)
|
|
return 0;
|
|
|
|
/* The BLOCKS generated for the temporary variables and FORALL don't
|
|
mix. */
|
|
if (forall_level > 0)
|
|
return 0;
|
|
|
|
/* For now don't do anything in OpenMP workshare, it confuses
|
|
its translation, which expects only the allowed statements in there. */
|
|
|
|
if (in_omp_workshare || in_omp_atomic)
|
|
return 0;
|
|
|
|
expr1 = co->expr1;
|
|
expr2 = co->expr2;
|
|
if (expr2->expr_type != EXPR_FUNCTION
|
|
|| expr2->value.function.isym == NULL
|
|
|| expr2->value.function.isym->id != GFC_ISYM_MATMUL)
|
|
return 0;
|
|
|
|
type = expr2->ts.type;
|
|
kind = expr2->ts.kind;
|
|
|
|
/* Guard against recursion. */
|
|
|
|
if (expr2->external_blas)
|
|
return 0;
|
|
|
|
if (type != expr1->ts.type || kind != expr1->ts.kind)
|
|
return 0;
|
|
|
|
if (type == BT_REAL)
|
|
{
|
|
if (kind == 4)
|
|
blas_name = "sgemm";
|
|
else if (kind == 8)
|
|
blas_name = "dgemm";
|
|
else
|
|
return 0;
|
|
}
|
|
else if (type == BT_COMPLEX)
|
|
{
|
|
if (kind == 4)
|
|
blas_name = "cgemm";
|
|
else if (kind == 8)
|
|
blas_name = "zgemm";
|
|
else
|
|
return 0;
|
|
}
|
|
else
|
|
return 0;
|
|
|
|
a = expr2->value.function.actual;
|
|
if (a->expr->rank != 2)
|
|
return 0;
|
|
|
|
b = a->next;
|
|
if (b->expr->rank != 2)
|
|
return 0;
|
|
|
|
matrix_a = check_conjg_transpose_variable (a->expr, &conjg_a, &transpose_a);
|
|
if (matrix_a == NULL)
|
|
return 0;
|
|
|
|
if (transpose_a)
|
|
{
|
|
if (conjg_a)
|
|
transa = "C";
|
|
else
|
|
transa = "T";
|
|
}
|
|
else
|
|
transa = "N";
|
|
|
|
matrix_b = check_conjg_transpose_variable (b->expr, &conjg_b, &transpose_b);
|
|
if (matrix_b == NULL)
|
|
return 0;
|
|
|
|
if (transpose_b)
|
|
{
|
|
if (conjg_b)
|
|
transb = "C";
|
|
else
|
|
transb = "T";
|
|
}
|
|
else
|
|
transb = "N";
|
|
|
|
if (transpose_a)
|
|
{
|
|
if (transpose_b)
|
|
m_case = A2TB2T;
|
|
else
|
|
m_case = A2TB2;
|
|
}
|
|
else
|
|
{
|
|
if (transpose_b)
|
|
m_case = A2B2T;
|
|
else
|
|
m_case = A2B2;
|
|
}
|
|
|
|
current_code = c;
|
|
inserted_block = NULL;
|
|
changed_statement = NULL;
|
|
|
|
expr2->external_blas = 1;
|
|
|
|
/* We do not handle data dependencies yet. */
|
|
if (gfc_check_dependency (expr1, matrix_a, true)
|
|
|| gfc_check_dependency (expr1, matrix_b, true))
|
|
return 0;
|
|
|
|
/* Generate the if statement and hang it into the tree. */
|
|
if_limit = inline_limit_check (matrix_a, matrix_b, flag_blas_matmul_limit);
|
|
co_next = co->next;
|
|
(*current_code) = if_limit;
|
|
co->next = NULL;
|
|
if_limit->block->next = co;
|
|
|
|
call = XCNEW (gfc_code);
|
|
call->loc = co->loc;
|
|
|
|
/* Bounds checking - a bit simpler than for inlining since we only
|
|
have to take care of two-dimensional arrays here. */
|
|
|
|
realloc_c = flag_realloc_lhs && gfc_is_reallocatable_lhs (expr1);
|
|
next_code_point = &(if_limit->block->block->next);
|
|
|
|
if (gfc_option.rtcheck & GFC_RTCHECK_BOUNDS)
|
|
{
|
|
gfc_code *test;
|
|
// gfc_expr *a2, *b1, *c1, *c2, *a1, *b2;
|
|
gfc_expr *c1, *a1, *c2, *b2, *a2;
|
|
switch (m_case)
|
|
{
|
|
case A2B2:
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 1);
|
|
a2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 2);
|
|
test = runtime_error_ne (b1, a2, B_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
if (!realloc_c)
|
|
{
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 1);
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 1);
|
|
test = runtime_error_ne (c1, a1, C_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
c2 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 2);
|
|
b2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 2);
|
|
test = runtime_error_ne (c2, b2, C_ERROR_2);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
}
|
|
break;
|
|
|
|
case A2B2T:
|
|
|
|
b2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 2);
|
|
a2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 2);
|
|
/* matrix_b is transposed, hence dimension 1 for the error message. */
|
|
test = runtime_error_ne (b2, a2, B_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
if (!realloc_c)
|
|
{
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 1);
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 1);
|
|
test = runtime_error_ne (c1, a1, C_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
c2 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 2);
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 1);
|
|
test = runtime_error_ne (c2, b1, C_ERROR_2);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
}
|
|
break;
|
|
|
|
case A2TB2:
|
|
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 1);
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 1);
|
|
test = runtime_error_ne (b1, a1, B_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
if (!realloc_c)
|
|
{
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 1);
|
|
a2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 2);
|
|
test = runtime_error_ne (c1, a2, C_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
c2 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 2);
|
|
b2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 2);
|
|
test = runtime_error_ne (c2, b2, C_ERROR_2);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
}
|
|
break;
|
|
|
|
case A2TB2T:
|
|
b2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 2);
|
|
a1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 1);
|
|
test = runtime_error_ne (b2, a1, B_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
if (!realloc_c)
|
|
{
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 1);
|
|
a2 = get_array_inq_function (GFC_ISYM_SIZE, matrix_a, 2);
|
|
test = runtime_error_ne (c1, a2, C_ERROR_1);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
|
|
c2 = get_array_inq_function (GFC_ISYM_SIZE, expr1, 2);
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, matrix_b, 1);
|
|
test = runtime_error_ne (c2, b1, C_ERROR_2);
|
|
*next_code_point = test;
|
|
next_code_point = &test->next;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
gcc_unreachable ();
|
|
}
|
|
}
|
|
|
|
/* Handle the reallocation, if needed. */
|
|
|
|
if (realloc_c)
|
|
{
|
|
gfc_code *lhs_alloc;
|
|
|
|
lhs_alloc = matmul_lhs_realloc (expr1, matrix_a, matrix_b, m_case);
|
|
*next_code_point = lhs_alloc;
|
|
next_code_point = &lhs_alloc->next;
|
|
}
|
|
|
|
*next_code_point = call;
|
|
if_limit->next = co_next;
|
|
|
|
/* Set up the BLAS call. */
|
|
|
|
call->op = EXEC_CALL;
|
|
|
|
gfc_get_sym_tree (blas_name, current_ns, &(call->symtree), true);
|
|
call->symtree->n.sym->attr.subroutine = 1;
|
|
call->symtree->n.sym->attr.procedure = 1;
|
|
call->symtree->n.sym->attr.flavor = FL_PROCEDURE;
|
|
call->resolved_sym = call->symtree->n.sym;
|
|
gfc_commit_symbol (call->resolved_sym);
|
|
|
|
/* Argument TRANSA. */
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = gfc_get_character_expr (gfc_default_character_kind, &co->loc,
|
|
transa, 1);
|
|
|
|
call->ext.actual = next;
|
|
|
|
/* Argument TRANSB. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = gfc_get_character_expr (gfc_default_character_kind, &co->loc,
|
|
transb, 1);
|
|
actual->next = next;
|
|
|
|
c1 = get_array_inq_function (GFC_ISYM_SIZE, gfc_copy_expr (a->expr), 1,
|
|
gfc_integer_4_kind);
|
|
c2 = get_array_inq_function (GFC_ISYM_SIZE, gfc_copy_expr (b->expr), 2,
|
|
gfc_integer_4_kind);
|
|
|
|
b1 = get_array_inq_function (GFC_ISYM_SIZE, gfc_copy_expr (b->expr), 1,
|
|
gfc_integer_4_kind);
|
|
|
|
/* Argument M. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = c1;
|
|
actual->next = next;
|
|
|
|
/* Argument N. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = c2;
|
|
actual->next = next;
|
|
|
|
/* Argument K. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = b1;
|
|
actual->next = next;
|
|
|
|
/* Argument ALPHA - set to one. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = gfc_get_constant_expr (type, kind, &co->loc);
|
|
if (type == BT_REAL)
|
|
mpfr_set_ui (next->expr->value.real, 1, GFC_RND_MODE);
|
|
else
|
|
mpc_set_ui (next->expr->value.complex, 1, GFC_MPC_RND_MODE);
|
|
actual->next = next;
|
|
|
|
/* Argument A. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = gfc_copy_expr (matrix_a);
|
|
actual->next = next;
|
|
|
|
/* Argument LDA. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = get_array_inq_function (GFC_ISYM_SIZE, gfc_copy_expr (matrix_a),
|
|
1, gfc_integer_4_kind);
|
|
actual->next = next;
|
|
|
|
/* Argument B. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = gfc_copy_expr (matrix_b);
|
|
actual->next = next;
|
|
|
|
/* Argument LDB. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = get_array_inq_function (GFC_ISYM_SIZE, gfc_copy_expr (matrix_b),
|
|
1, gfc_integer_4_kind);
|
|
actual->next = next;
|
|
|
|
/* Argument BETA - set to zero. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = gfc_get_constant_expr (type, kind, &co->loc);
|
|
if (type == BT_REAL)
|
|
mpfr_set_ui (next->expr->value.real, 0, GFC_RND_MODE);
|
|
else
|
|
mpc_set_ui (next->expr->value.complex, 0, GFC_MPC_RND_MODE);
|
|
actual->next = next;
|
|
|
|
/* Argument C. */
|
|
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = gfc_copy_expr (expr1);
|
|
actual->next = next;
|
|
|
|
/* Argument LDC. */
|
|
actual = next;
|
|
next = gfc_get_actual_arglist ();
|
|
next->expr = get_array_inq_function (GFC_ISYM_SIZE, gfc_copy_expr (expr1),
|
|
1, gfc_integer_4_kind);
|
|
actual->next = next;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Code for index interchange for loops which are grouped together in DO
|
|
CONCURRENT or FORALL statements. This is currently only applied if the
|
|
iterations are grouped together in a single statement.
|
|
|
|
For this transformation, it is assumed that memory access in strides is
|
|
expensive, and that loops which access later indices (which access memory
|
|
in bigger strides) should be moved to the first loops.
|
|
|
|
For this, a loop over all the statements is executed, counting the times
|
|
that the loop iteration values are accessed in each index. The loop
|
|
indices are then sorted to minimize access to later indices from inner
|
|
loops. */
|
|
|
|
/* Type for holding index information. */
|
|
|
|
typedef struct {
|
|
gfc_symbol *sym;
|
|
gfc_forall_iterator *fa;
|
|
int num;
|
|
int n[GFC_MAX_DIMENSIONS];
|
|
} ind_type;
|
|
|
|
/* Callback function to determine if an expression is the
|
|
corresponding variable. */
|
|
|
|
static int
|
|
has_var (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED, void *data)
|
|
{
|
|
gfc_expr *expr = *e;
|
|
gfc_symbol *sym;
|
|
|
|
if (expr->expr_type != EXPR_VARIABLE)
|
|
return 0;
|
|
|
|
sym = (gfc_symbol *) data;
|
|
return sym == expr->symtree->n.sym;
|
|
}
|
|
|
|
/* Callback function to calculate the cost of a certain index. */
|
|
|
|
static int
|
|
index_cost (gfc_expr **e, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data)
|
|
{
|
|
ind_type *ind;
|
|
gfc_expr *expr;
|
|
gfc_array_ref *ar;
|
|
gfc_ref *ref;
|
|
int i,j;
|
|
|
|
expr = *e;
|
|
if (expr->expr_type != EXPR_VARIABLE)
|
|
return 0;
|
|
|
|
ar = NULL;
|
|
for (ref = expr->ref; ref; ref = ref->next)
|
|
{
|
|
if (ref->type == REF_ARRAY)
|
|
{
|
|
ar = &ref->u.ar;
|
|
break;
|
|
}
|
|
}
|
|
if (ar == NULL || ar->type != AR_ELEMENT)
|
|
return 0;
|
|
|
|
ind = (ind_type *) data;
|
|
for (i = 0; i < ar->dimen; i++)
|
|
{
|
|
for (j=0; ind[j].sym != NULL; j++)
|
|
{
|
|
if (gfc_expr_walker (&ar->start[i], has_var, (void *) (ind[j].sym)))
|
|
ind[j].n[i]++;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Callback function for qsort, to sort the loop indices. */
|
|
|
|
static int
|
|
loop_comp (const void *e1, const void *e2)
|
|
{
|
|
const ind_type *i1 = (const ind_type *) e1;
|
|
const ind_type *i2 = (const ind_type *) e2;
|
|
int i;
|
|
|
|
for (i=GFC_MAX_DIMENSIONS-1; i >= 0; i--)
|
|
{
|
|
if (i1->n[i] != i2->n[i])
|
|
return i1->n[i] - i2->n[i];
|
|
}
|
|
/* All other things being equal, let's not change the ordering. */
|
|
return i2->num - i1->num;
|
|
}
|
|
|
|
/* Main function to do the index interchange. */
|
|
|
|
static int
|
|
index_interchange (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_code *co;
|
|
co = *c;
|
|
int n_iter;
|
|
gfc_forall_iterator *fa;
|
|
ind_type *ind;
|
|
int i, j;
|
|
|
|
if (co->op != EXEC_FORALL && co->op != EXEC_DO_CONCURRENT)
|
|
return 0;
|
|
|
|
n_iter = 0;
|
|
for (fa = co->ext.forall_iterator; fa; fa = fa->next)
|
|
n_iter ++;
|
|
|
|
/* Nothing to reorder. */
|
|
if (n_iter < 2)
|
|
return 0;
|
|
|
|
ind = XALLOCAVEC (ind_type, n_iter + 1);
|
|
|
|
i = 0;
|
|
for (fa = co->ext.forall_iterator; fa; fa = fa->next)
|
|
{
|
|
ind[i].sym = fa->var->symtree->n.sym;
|
|
ind[i].fa = fa;
|
|
for (j=0; j<GFC_MAX_DIMENSIONS; j++)
|
|
ind[i].n[j] = 0;
|
|
ind[i].num = i;
|
|
i++;
|
|
}
|
|
ind[n_iter].sym = NULL;
|
|
ind[n_iter].fa = NULL;
|
|
|
|
gfc_code_walker (c, gfc_dummy_code_callback, index_cost, (void *) ind);
|
|
qsort ((void *) ind, n_iter, sizeof (ind_type), loop_comp);
|
|
|
|
/* Do the actual index interchange. */
|
|
co->ext.forall_iterator = fa = ind[0].fa;
|
|
for (i=1; i<n_iter; i++)
|
|
{
|
|
fa->next = ind[i].fa;
|
|
fa = fa->next;
|
|
}
|
|
fa->next = NULL;
|
|
|
|
if (flag_warn_frontend_loop_interchange)
|
|
{
|
|
for (i=1; i<n_iter; i++)
|
|
{
|
|
if (ind[i-1].num > ind[i].num)
|
|
{
|
|
gfc_warning (OPT_Wfrontend_loop_interchange,
|
|
"Interchanging loops at %L", &co->loc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define WALK_SUBEXPR(NODE) \
|
|
do \
|
|
{ \
|
|
result = gfc_expr_walker (&(NODE), exprfn, data); \
|
|
if (result) \
|
|
return result; \
|
|
} \
|
|
while (0)
|
|
#define WALK_SUBEXPR_TAIL(NODE) e = &(NODE); continue
|
|
|
|
/* Walk expression *E, calling EXPRFN on each expression in it. */
|
|
|
|
int
|
|
gfc_expr_walker (gfc_expr **e, walk_expr_fn_t exprfn, void *data)
|
|
{
|
|
while (*e)
|
|
{
|
|
int walk_subtrees = 1;
|
|
gfc_actual_arglist *a;
|
|
gfc_ref *r;
|
|
gfc_constructor *c;
|
|
|
|
int result = exprfn (e, &walk_subtrees, data);
|
|
if (result)
|
|
return result;
|
|
if (walk_subtrees)
|
|
switch ((*e)->expr_type)
|
|
{
|
|
case EXPR_OP:
|
|
WALK_SUBEXPR ((*e)->value.op.op1);
|
|
WALK_SUBEXPR_TAIL ((*e)->value.op.op2);
|
|
break;
|
|
case EXPR_FUNCTION:
|
|
for (a = (*e)->value.function.actual; a; a = a->next)
|
|
WALK_SUBEXPR (a->expr);
|
|
break;
|
|
case EXPR_COMPCALL:
|
|
case EXPR_PPC:
|
|
WALK_SUBEXPR ((*e)->value.compcall.base_object);
|
|
for (a = (*e)->value.compcall.actual; a; a = a->next)
|
|
WALK_SUBEXPR (a->expr);
|
|
break;
|
|
|
|
case EXPR_STRUCTURE:
|
|
case EXPR_ARRAY:
|
|
for (c = gfc_constructor_first ((*e)->value.constructor); c;
|
|
c = gfc_constructor_next (c))
|
|
{
|
|
if (c->iterator == NULL)
|
|
WALK_SUBEXPR (c->expr);
|
|
else
|
|
{
|
|
iterator_level ++;
|
|
WALK_SUBEXPR (c->expr);
|
|
iterator_level --;
|
|
WALK_SUBEXPR (c->iterator->var);
|
|
WALK_SUBEXPR (c->iterator->start);
|
|
WALK_SUBEXPR (c->iterator->end);
|
|
WALK_SUBEXPR (c->iterator->step);
|
|
}
|
|
}
|
|
|
|
if ((*e)->expr_type != EXPR_ARRAY)
|
|
break;
|
|
|
|
/* Fall through to the variable case in order to walk the
|
|
reference. */
|
|
gcc_fallthrough ();
|
|
|
|
case EXPR_SUBSTRING:
|
|
case EXPR_VARIABLE:
|
|
for (r = (*e)->ref; r; r = r->next)
|
|
{
|
|
gfc_array_ref *ar;
|
|
int i;
|
|
|
|
switch (r->type)
|
|
{
|
|
case REF_ARRAY:
|
|
ar = &r->u.ar;
|
|
if (ar->type == AR_SECTION || ar->type == AR_ELEMENT)
|
|
{
|
|
for (i=0; i< ar->dimen; i++)
|
|
{
|
|
WALK_SUBEXPR (ar->start[i]);
|
|
WALK_SUBEXPR (ar->end[i]);
|
|
WALK_SUBEXPR (ar->stride[i]);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case REF_SUBSTRING:
|
|
WALK_SUBEXPR (r->u.ss.start);
|
|
WALK_SUBEXPR (r->u.ss.end);
|
|
break;
|
|
|
|
case REF_COMPONENT:
|
|
case REF_INQUIRY:
|
|
break;
|
|
}
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define WALK_SUBCODE(NODE) \
|
|
do \
|
|
{ \
|
|
result = gfc_code_walker (&(NODE), codefn, exprfn, data); \
|
|
if (result) \
|
|
return result; \
|
|
} \
|
|
while (0)
|
|
|
|
/* Walk code *C, calling CODEFN on each gfc_code node in it and calling EXPRFN
|
|
on each expression in it. If any of the hooks returns non-zero, that
|
|
value is immediately returned. If the hook sets *WALK_SUBTREES to 0,
|
|
no subcodes or subexpressions are traversed. */
|
|
|
|
int
|
|
gfc_code_walker (gfc_code **c, walk_code_fn_t codefn, walk_expr_fn_t exprfn,
|
|
void *data)
|
|
{
|
|
for (; *c; c = &(*c)->next)
|
|
{
|
|
int walk_subtrees = 1;
|
|
int result = codefn (c, &walk_subtrees, data);
|
|
if (result)
|
|
return result;
|
|
|
|
if (walk_subtrees)
|
|
{
|
|
gfc_code *b;
|
|
gfc_actual_arglist *a;
|
|
gfc_code *co;
|
|
gfc_association_list *alist;
|
|
bool saved_in_omp_workshare;
|
|
bool saved_in_omp_atomic;
|
|
bool saved_in_where;
|
|
|
|
/* There might be statement insertions before the current code,
|
|
which must not affect the expression walker. */
|
|
|
|
co = *c;
|
|
saved_in_omp_workshare = in_omp_workshare;
|
|
saved_in_omp_atomic = in_omp_atomic;
|
|
saved_in_where = in_where;
|
|
|
|
switch (co->op)
|
|
{
|
|
|
|
case EXEC_BLOCK:
|
|
WALK_SUBCODE (co->ext.block.ns->code);
|
|
if (co->ext.block.assoc)
|
|
{
|
|
bool saved_in_assoc_list = in_assoc_list;
|
|
|
|
in_assoc_list = true;
|
|
for (alist = co->ext.block.assoc; alist; alist = alist->next)
|
|
WALK_SUBEXPR (alist->target);
|
|
|
|
in_assoc_list = saved_in_assoc_list;
|
|
}
|
|
|
|
break;
|
|
|
|
case EXEC_DO:
|
|
doloop_level ++;
|
|
WALK_SUBEXPR (co->ext.iterator->var);
|
|
WALK_SUBEXPR (co->ext.iterator->start);
|
|
WALK_SUBEXPR (co->ext.iterator->end);
|
|
WALK_SUBEXPR (co->ext.iterator->step);
|
|
break;
|
|
|
|
case EXEC_IF:
|
|
if_level ++;
|
|
break;
|
|
|
|
case EXEC_WHERE:
|
|
in_where = true;
|
|
break;
|
|
|
|
case EXEC_CALL:
|
|
case EXEC_ASSIGN_CALL:
|
|
for (a = co->ext.actual; a; a = a->next)
|
|
WALK_SUBEXPR (a->expr);
|
|
break;
|
|
|
|
case EXEC_CALL_PPC:
|
|
WALK_SUBEXPR (co->expr1);
|
|
for (a = co->ext.actual; a; a = a->next)
|
|
WALK_SUBEXPR (a->expr);
|
|
break;
|
|
|
|
case EXEC_SELECT:
|
|
WALK_SUBEXPR (co->expr1);
|
|
select_level ++;
|
|
for (b = co->block; b; b = b->block)
|
|
{
|
|
gfc_case *cp;
|
|
for (cp = b->ext.block.case_list; cp; cp = cp->next)
|
|
{
|
|
WALK_SUBEXPR (cp->low);
|
|
WALK_SUBEXPR (cp->high);
|
|
}
|
|
WALK_SUBCODE (b->next);
|
|
}
|
|
continue;
|
|
|
|
case EXEC_ALLOCATE:
|
|
case EXEC_DEALLOCATE:
|
|
{
|
|
gfc_alloc *a;
|
|
for (a = co->ext.alloc.list; a; a = a->next)
|
|
WALK_SUBEXPR (a->expr);
|
|
break;
|
|
}
|
|
|
|
case EXEC_FORALL:
|
|
case EXEC_DO_CONCURRENT:
|
|
{
|
|
gfc_forall_iterator *fa;
|
|
for (fa = co->ext.forall_iterator; fa; fa = fa->next)
|
|
{
|
|
WALK_SUBEXPR (fa->var);
|
|
WALK_SUBEXPR (fa->start);
|
|
WALK_SUBEXPR (fa->end);
|
|
WALK_SUBEXPR (fa->stride);
|
|
}
|
|
if (co->op == EXEC_FORALL)
|
|
forall_level ++;
|
|
break;
|
|
}
|
|
|
|
case EXEC_OPEN:
|
|
WALK_SUBEXPR (co->ext.open->unit);
|
|
WALK_SUBEXPR (co->ext.open->file);
|
|
WALK_SUBEXPR (co->ext.open->status);
|
|
WALK_SUBEXPR (co->ext.open->access);
|
|
WALK_SUBEXPR (co->ext.open->form);
|
|
WALK_SUBEXPR (co->ext.open->recl);
|
|
WALK_SUBEXPR (co->ext.open->blank);
|
|
WALK_SUBEXPR (co->ext.open->position);
|
|
WALK_SUBEXPR (co->ext.open->action);
|
|
WALK_SUBEXPR (co->ext.open->delim);
|
|
WALK_SUBEXPR (co->ext.open->pad);
|
|
WALK_SUBEXPR (co->ext.open->iostat);
|
|
WALK_SUBEXPR (co->ext.open->iomsg);
|
|
WALK_SUBEXPR (co->ext.open->convert);
|
|
WALK_SUBEXPR (co->ext.open->decimal);
|
|
WALK_SUBEXPR (co->ext.open->encoding);
|
|
WALK_SUBEXPR (co->ext.open->round);
|
|
WALK_SUBEXPR (co->ext.open->sign);
|
|
WALK_SUBEXPR (co->ext.open->asynchronous);
|
|
WALK_SUBEXPR (co->ext.open->id);
|
|
WALK_SUBEXPR (co->ext.open->newunit);
|
|
WALK_SUBEXPR (co->ext.open->share);
|
|
WALK_SUBEXPR (co->ext.open->cc);
|
|
break;
|
|
|
|
case EXEC_CLOSE:
|
|
WALK_SUBEXPR (co->ext.close->unit);
|
|
WALK_SUBEXPR (co->ext.close->status);
|
|
WALK_SUBEXPR (co->ext.close->iostat);
|
|
WALK_SUBEXPR (co->ext.close->iomsg);
|
|
break;
|
|
|
|
case EXEC_BACKSPACE:
|
|
case EXEC_ENDFILE:
|
|
case EXEC_REWIND:
|
|
case EXEC_FLUSH:
|
|
WALK_SUBEXPR (co->ext.filepos->unit);
|
|
WALK_SUBEXPR (co->ext.filepos->iostat);
|
|
WALK_SUBEXPR (co->ext.filepos->iomsg);
|
|
break;
|
|
|
|
case EXEC_INQUIRE:
|
|
WALK_SUBEXPR (co->ext.inquire->unit);
|
|
WALK_SUBEXPR (co->ext.inquire->file);
|
|
WALK_SUBEXPR (co->ext.inquire->iomsg);
|
|
WALK_SUBEXPR (co->ext.inquire->iostat);
|
|
WALK_SUBEXPR (co->ext.inquire->exist);
|
|
WALK_SUBEXPR (co->ext.inquire->opened);
|
|
WALK_SUBEXPR (co->ext.inquire->number);
|
|
WALK_SUBEXPR (co->ext.inquire->named);
|
|
WALK_SUBEXPR (co->ext.inquire->name);
|
|
WALK_SUBEXPR (co->ext.inquire->access);
|
|
WALK_SUBEXPR (co->ext.inquire->sequential);
|
|
WALK_SUBEXPR (co->ext.inquire->direct);
|
|
WALK_SUBEXPR (co->ext.inquire->form);
|
|
WALK_SUBEXPR (co->ext.inquire->formatted);
|
|
WALK_SUBEXPR (co->ext.inquire->unformatted);
|
|
WALK_SUBEXPR (co->ext.inquire->recl);
|
|
WALK_SUBEXPR (co->ext.inquire->nextrec);
|
|
WALK_SUBEXPR (co->ext.inquire->blank);
|
|
WALK_SUBEXPR (co->ext.inquire->position);
|
|
WALK_SUBEXPR (co->ext.inquire->action);
|
|
WALK_SUBEXPR (co->ext.inquire->read);
|
|
WALK_SUBEXPR (co->ext.inquire->write);
|
|
WALK_SUBEXPR (co->ext.inquire->readwrite);
|
|
WALK_SUBEXPR (co->ext.inquire->delim);
|
|
WALK_SUBEXPR (co->ext.inquire->encoding);
|
|
WALK_SUBEXPR (co->ext.inquire->pad);
|
|
WALK_SUBEXPR (co->ext.inquire->iolength);
|
|
WALK_SUBEXPR (co->ext.inquire->convert);
|
|
WALK_SUBEXPR (co->ext.inquire->strm_pos);
|
|
WALK_SUBEXPR (co->ext.inquire->asynchronous);
|
|
WALK_SUBEXPR (co->ext.inquire->decimal);
|
|
WALK_SUBEXPR (co->ext.inquire->pending);
|
|
WALK_SUBEXPR (co->ext.inquire->id);
|
|
WALK_SUBEXPR (co->ext.inquire->sign);
|
|
WALK_SUBEXPR (co->ext.inquire->size);
|
|
WALK_SUBEXPR (co->ext.inquire->round);
|
|
break;
|
|
|
|
case EXEC_WAIT:
|
|
WALK_SUBEXPR (co->ext.wait->unit);
|
|
WALK_SUBEXPR (co->ext.wait->iostat);
|
|
WALK_SUBEXPR (co->ext.wait->iomsg);
|
|
WALK_SUBEXPR (co->ext.wait->id);
|
|
break;
|
|
|
|
case EXEC_READ:
|
|
case EXEC_WRITE:
|
|
WALK_SUBEXPR (co->ext.dt->io_unit);
|
|
WALK_SUBEXPR (co->ext.dt->format_expr);
|
|
WALK_SUBEXPR (co->ext.dt->rec);
|
|
WALK_SUBEXPR (co->ext.dt->advance);
|
|
WALK_SUBEXPR (co->ext.dt->iostat);
|
|
WALK_SUBEXPR (co->ext.dt->size);
|
|
WALK_SUBEXPR (co->ext.dt->iomsg);
|
|
WALK_SUBEXPR (co->ext.dt->id);
|
|
WALK_SUBEXPR (co->ext.dt->pos);
|
|
WALK_SUBEXPR (co->ext.dt->asynchronous);
|
|
WALK_SUBEXPR (co->ext.dt->blank);
|
|
WALK_SUBEXPR (co->ext.dt->decimal);
|
|
WALK_SUBEXPR (co->ext.dt->delim);
|
|
WALK_SUBEXPR (co->ext.dt->pad);
|
|
WALK_SUBEXPR (co->ext.dt->round);
|
|
WALK_SUBEXPR (co->ext.dt->sign);
|
|
WALK_SUBEXPR (co->ext.dt->extra_comma);
|
|
break;
|
|
|
|
case EXEC_OACC_ATOMIC:
|
|
case EXEC_OMP_ATOMIC:
|
|
in_omp_atomic = true;
|
|
break;
|
|
|
|
case EXEC_OMP_PARALLEL:
|
|
case EXEC_OMP_PARALLEL_DO:
|
|
case EXEC_OMP_PARALLEL_DO_SIMD:
|
|
case EXEC_OMP_PARALLEL_SECTIONS:
|
|
|
|
in_omp_workshare = false;
|
|
|
|
/* This goto serves as a shortcut to avoid code
|
|
duplication or a larger if or switch statement. */
|
|
goto check_omp_clauses;
|
|
|
|
case EXEC_OMP_WORKSHARE:
|
|
case EXEC_OMP_PARALLEL_WORKSHARE:
|
|
|
|
in_omp_workshare = true;
|
|
|
|
/* Fall through */
|
|
|
|
case EXEC_OMP_CRITICAL:
|
|
case EXEC_OMP_DISTRIBUTE:
|
|
case EXEC_OMP_DISTRIBUTE_PARALLEL_DO:
|
|
case EXEC_OMP_DISTRIBUTE_PARALLEL_DO_SIMD:
|
|
case EXEC_OMP_DISTRIBUTE_SIMD:
|
|
case EXEC_OMP_DO:
|
|
case EXEC_OMP_DO_SIMD:
|
|
case EXEC_OMP_ORDERED:
|
|
case EXEC_OMP_SECTIONS:
|
|
case EXEC_OMP_SINGLE:
|
|
case EXEC_OMP_END_SINGLE:
|
|
case EXEC_OMP_SIMD:
|
|
case EXEC_OMP_TASKLOOP:
|
|
case EXEC_OMP_TASKLOOP_SIMD:
|
|
case EXEC_OMP_TARGET:
|
|
case EXEC_OMP_TARGET_DATA:
|
|
case EXEC_OMP_TARGET_ENTER_DATA:
|
|
case EXEC_OMP_TARGET_EXIT_DATA:
|
|
case EXEC_OMP_TARGET_PARALLEL:
|
|
case EXEC_OMP_TARGET_PARALLEL_DO:
|
|
case EXEC_OMP_TARGET_PARALLEL_DO_SIMD:
|
|
case EXEC_OMP_TARGET_SIMD:
|
|
case EXEC_OMP_TARGET_TEAMS:
|
|
case EXEC_OMP_TARGET_TEAMS_DISTRIBUTE:
|
|
case EXEC_OMP_TARGET_TEAMS_DISTRIBUTE_PARALLEL_DO:
|
|
case EXEC_OMP_TARGET_TEAMS_DISTRIBUTE_PARALLEL_DO_SIMD:
|
|
case EXEC_OMP_TARGET_TEAMS_DISTRIBUTE_SIMD:
|
|
case EXEC_OMP_TARGET_UPDATE:
|
|
case EXEC_OMP_TASK:
|
|
case EXEC_OMP_TEAMS:
|
|
case EXEC_OMP_TEAMS_DISTRIBUTE:
|
|
case EXEC_OMP_TEAMS_DISTRIBUTE_PARALLEL_DO:
|
|
case EXEC_OMP_TEAMS_DISTRIBUTE_PARALLEL_DO_SIMD:
|
|
case EXEC_OMP_TEAMS_DISTRIBUTE_SIMD:
|
|
|
|
/* Come to this label only from the
|
|
EXEC_OMP_PARALLEL_* cases above. */
|
|
|
|
check_omp_clauses:
|
|
|
|
if (co->ext.omp_clauses)
|
|
{
|
|
gfc_omp_namelist *n;
|
|
static int list_types[]
|
|
= { OMP_LIST_ALIGNED, OMP_LIST_LINEAR, OMP_LIST_DEPEND,
|
|
OMP_LIST_MAP, OMP_LIST_TO, OMP_LIST_FROM };
|
|
size_t idx;
|
|
WALK_SUBEXPR (co->ext.omp_clauses->if_expr);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->final_expr);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->num_threads);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->chunk_size);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->safelen_expr);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->simdlen_expr);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->num_teams);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->device);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->thread_limit);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->dist_chunk_size);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->grainsize);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->hint);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->num_tasks);
|
|
WALK_SUBEXPR (co->ext.omp_clauses->priority);
|
|
for (idx = 0; idx < OMP_IF_LAST; idx++)
|
|
WALK_SUBEXPR (co->ext.omp_clauses->if_exprs[idx]);
|
|
for (idx = 0;
|
|
idx < sizeof (list_types) / sizeof (list_types[0]);
|
|
idx++)
|
|
for (n = co->ext.omp_clauses->lists[list_types[idx]];
|
|
n; n = n->next)
|
|
WALK_SUBEXPR (n->expr);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
WALK_SUBEXPR (co->expr1);
|
|
WALK_SUBEXPR (co->expr2);
|
|
WALK_SUBEXPR (co->expr3);
|
|
WALK_SUBEXPR (co->expr4);
|
|
for (b = co->block; b; b = b->block)
|
|
{
|
|
WALK_SUBEXPR (b->expr1);
|
|
WALK_SUBEXPR (b->expr2);
|
|
WALK_SUBCODE (b->next);
|
|
}
|
|
|
|
if (co->op == EXEC_FORALL)
|
|
forall_level --;
|
|
|
|
if (co->op == EXEC_DO)
|
|
doloop_level --;
|
|
|
|
if (co->op == EXEC_IF)
|
|
if_level --;
|
|
|
|
if (co->op == EXEC_SELECT)
|
|
select_level --;
|
|
|
|
in_omp_workshare = saved_in_omp_workshare;
|
|
in_omp_atomic = saved_in_omp_atomic;
|
|
in_where = saved_in_where;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* As a post-resolution step, check that all global symbols which are
|
|
not declared in the source file match in their call signatures.
|
|
We do this by looping over the code (and expressions). The first call
|
|
we happen to find is assumed to be canonical. */
|
|
|
|
|
|
/* Common tests for argument checking for both functions and subroutines. */
|
|
|
|
static int
|
|
check_externals_procedure (gfc_symbol *sym, locus *loc,
|
|
gfc_actual_arglist *actual)
|
|
{
|
|
gfc_gsymbol *gsym;
|
|
gfc_symbol *def_sym = NULL;
|
|
|
|
if (sym == NULL || sym->attr.is_bind_c)
|
|
return 0;
|
|
|
|
if (sym->attr.proc != PROC_EXTERNAL && sym->attr.proc != PROC_UNKNOWN)
|
|
return 0;
|
|
|
|
if (sym->attr.if_source == IFSRC_IFBODY || sym->attr.if_source == IFSRC_DECL)
|
|
return 0;
|
|
|
|
gsym = gfc_find_gsymbol (gfc_gsym_root, sym->name);
|
|
if (gsym == NULL)
|
|
return 0;
|
|
|
|
if (gsym->ns)
|
|
gfc_find_symbol (sym->name, gsym->ns, 0, &def_sym);
|
|
|
|
if (def_sym)
|
|
{
|
|
gfc_compare_actual_formal (&actual, def_sym->formal, 0, 0, 0, loc);
|
|
return 0;
|
|
}
|
|
|
|
/* First time we have seen this procedure called. Let's create an
|
|
"interface" from the call and put it into a new namespace. */
|
|
gfc_namespace *save_ns;
|
|
gfc_symbol *new_sym;
|
|
|
|
gsym->where = *loc;
|
|
save_ns = gfc_current_ns;
|
|
gsym->ns = gfc_get_namespace (gfc_current_ns, 0);
|
|
gsym->ns->proc_name = sym;
|
|
|
|
gfc_get_symbol (sym->name, gsym->ns, &new_sym);
|
|
gcc_assert (new_sym);
|
|
new_sym->attr = sym->attr;
|
|
new_sym->attr.if_source = IFSRC_DECL;
|
|
gfc_current_ns = gsym->ns;
|
|
|
|
gfc_get_formal_from_actual_arglist (new_sym, actual);
|
|
new_sym->declared_at = *loc;
|
|
gfc_current_ns = save_ns;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
/* Callback for calls of external routines. */
|
|
|
|
static int
|
|
check_externals_code (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_code *co = *c;
|
|
gfc_symbol *sym;
|
|
locus *loc;
|
|
gfc_actual_arglist *actual;
|
|
|
|
if (co->op != EXEC_CALL)
|
|
return 0;
|
|
|
|
sym = co->resolved_sym;
|
|
loc = &co->loc;
|
|
actual = co->ext.actual;
|
|
|
|
return check_externals_procedure (sym, loc, actual);
|
|
|
|
}
|
|
|
|
/* Callback for external functions. */
|
|
|
|
static int
|
|
check_externals_expr (gfc_expr **ep, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *data ATTRIBUTE_UNUSED)
|
|
{
|
|
gfc_expr *e = *ep;
|
|
gfc_symbol *sym;
|
|
locus *loc;
|
|
gfc_actual_arglist *actual;
|
|
|
|
if (e->expr_type != EXPR_FUNCTION)
|
|
return 0;
|
|
|
|
sym = e->value.function.esym;
|
|
if (sym == NULL)
|
|
return 0;
|
|
|
|
loc = &e->where;
|
|
actual = e->value.function.actual;
|
|
|
|
return check_externals_procedure (sym, loc, actual);
|
|
}
|
|
|
|
/* Function to check if any interface clashes with a global
|
|
identifier, to be invoked via gfc_traverse_ns. */
|
|
|
|
static void
|
|
check_against_globals (gfc_symbol *sym)
|
|
{
|
|
gfc_gsymbol *gsym;
|
|
gfc_symbol *def_sym = NULL;
|
|
const char *sym_name;
|
|
char buf [200];
|
|
|
|
if (sym->attr.if_source != IFSRC_IFBODY || sym->attr.flavor != FL_PROCEDURE
|
|
|| sym->attr.generic || sym->error)
|
|
return;
|
|
|
|
if (sym->binding_label)
|
|
sym_name = sym->binding_label;
|
|
else
|
|
sym_name = sym->name;
|
|
|
|
gsym = gfc_find_gsymbol (gfc_gsym_root, sym_name);
|
|
if (gsym && gsym->ns)
|
|
gfc_find_symbol (sym->name, gsym->ns, 0, &def_sym);
|
|
|
|
if (!def_sym || def_sym->error || def_sym->attr.generic)
|
|
return;
|
|
|
|
buf[0] = 0;
|
|
gfc_compare_interfaces (sym, def_sym, sym->name, 0, 1, buf, sizeof(buf),
|
|
NULL, NULL, NULL);
|
|
if (buf[0] != 0)
|
|
{
|
|
gfc_warning (0, "%s between %L and %L", buf, &def_sym->declared_at,
|
|
&sym->declared_at);
|
|
sym->error = 1;
|
|
def_sym->error = 1;
|
|
}
|
|
|
|
}
|
|
|
|
/* Do the code-walkling part for gfc_check_externals. */
|
|
|
|
static void
|
|
gfc_check_externals0 (gfc_namespace *ns)
|
|
{
|
|
gfc_code_walker (&ns->code, check_externals_code, check_externals_expr, NULL);
|
|
|
|
for (ns = ns->contained; ns; ns = ns->sibling)
|
|
{
|
|
if (ns->code == NULL || ns->code->op != EXEC_BLOCK)
|
|
gfc_check_externals0 (ns);
|
|
}
|
|
|
|
}
|
|
|
|
/* Called routine. */
|
|
|
|
void
|
|
gfc_check_externals (gfc_namespace *ns)
|
|
{
|
|
gfc_clear_error ();
|
|
|
|
/* Turn errors into warnings if the user indicated this. */
|
|
|
|
if (!pedantic && flag_allow_argument_mismatch)
|
|
gfc_errors_to_warnings (true);
|
|
|
|
gfc_check_externals0 (ns);
|
|
gfc_traverse_ns (ns, check_against_globals);
|
|
|
|
gfc_errors_to_warnings (false);
|
|
}
|
|
|
|
/* Callback function. If there is a call to a subroutine which is
|
|
neither pure nor implicit_pure, unset the implicit_pure flag for
|
|
the caller and return -1. */
|
|
|
|
static int
|
|
implicit_pure_call (gfc_code **c, int *walk_subtrees ATTRIBUTE_UNUSED,
|
|
void *sym_data)
|
|
{
|
|
gfc_code *co = *c;
|
|
gfc_symbol *caller_sym;
|
|
symbol_attribute *a;
|
|
|
|
if (co->op != EXEC_CALL || co->resolved_sym == NULL)
|
|
return 0;
|
|
|
|
a = &co->resolved_sym->attr;
|
|
if (a->intrinsic || a->pure || a->implicit_pure)
|
|
return 0;
|
|
|
|
caller_sym = (gfc_symbol *) sym_data;
|
|
gfc_unset_implicit_pure (caller_sym);
|
|
return 1;
|
|
}
|
|
|
|
/* Callback function. If there is a call to a function which is
|
|
neither pure nor implicit_pure, unset the implicit_pure flag for
|
|
the caller and return 1. */
|
|
|
|
static int
|
|
implicit_pure_expr (gfc_expr **e, int *walk ATTRIBUTE_UNUSED, void *sym_data)
|
|
{
|
|
gfc_expr *expr = *e;
|
|
gfc_symbol *caller_sym;
|
|
gfc_symbol *sym;
|
|
symbol_attribute *a;
|
|
|
|
if (expr->expr_type != EXPR_FUNCTION || expr->value.function.isym)
|
|
return 0;
|
|
|
|
sym = expr->symtree->n.sym;
|
|
a = &sym->attr;
|
|
if (a->pure || a->implicit_pure)
|
|
return 0;
|
|
|
|
caller_sym = (gfc_symbol *) sym_data;
|
|
gfc_unset_implicit_pure (caller_sym);
|
|
return 1;
|
|
}
|
|
|
|
/* Go through all procedures in the namespace and unset the
|
|
implicit_pure attribute for any procedure that calls something not
|
|
pure or implicit pure. */
|
|
|
|
bool
|
|
gfc_fix_implicit_pure (gfc_namespace *ns)
|
|
{
|
|
bool changed = false;
|
|
gfc_symbol *proc = ns->proc_name;
|
|
|
|
if (proc && proc->attr.flavor == FL_PROCEDURE && proc->attr.implicit_pure
|
|
&& ns->code
|
|
&& gfc_code_walker (&ns->code, implicit_pure_call, implicit_pure_expr,
|
|
(void *) ns->proc_name))
|
|
changed = true;
|
|
|
|
for (ns = ns->contained; ns; ns = ns->sibling)
|
|
{
|
|
if (gfc_fix_implicit_pure (ns))
|
|
changed = true;
|
|
}
|
|
|
|
return changed;
|
|
}
|