https://gcc.gnu.org/g:94fa9f4d27bac577ecab43379a31fa28b146d6d9
commit r15-8650-g94fa9f4d27bac577ecab43379a31fa28b146d6d9 Author: Paul Thomas <pa...@gcc.gnu.org> Date: Fri Mar 21 16:20:21 2025 +0000 Fortran: Implement the F2018 reduce intrinsic [PR85836] 2025-03-21 Paul Thomas <pa...@gcc.gnu.org> gcc/fortran PR fortran/85836 * check.cc (get_ul_from_cst_cl): New function used in check_operation. (check_operation): New function used in check_reduce and check_co_reduce. (gfc_check_co_reduce): Use it. (gfc_check_reduce): New function. (gfc_check_rename): Add prototype for intrinsic with 6 arguments. * gfortran.h : Add isym id for reduce and prototype for f6. * intrinsic.cc (do_check): Add another argument expression and use it in the call to the six argument specific check. (add_sym_6): New function. (add_functions): Add the discription of the reduce intrinsic and add it to the intrinsic list. * intrinsic.h : Add prototypes for gfc_check_reduce and gfc_resolve_reduce. * iresolve.cc (generate_reduce_op_wrapper): Generate a wrapper subroutine for the 'operation' function to enable the library implementation to be type agnostic and use pointer arithmetic throughout. (gfc_resolve_reduce): New function. * trans-expr.cc (gfc_conv_procedure_call): Add flag for scalar reduce. Generate a return variable 'sr' for scalar reduce, pass its address to the library function and return it as the scalar result. * trans-intrinsic.cc (gfc_conv_intrinsic_function): Array valued reduce is called in same way as reshape. Fall through for call to the scalar version. gcc/testsuite/ PR fortran/85836 * gfortran.dg/reduce_1.f90: New test * gfortran.dg/reduce_2.f90: New test libgfortran/ PR libfortran/85836 * Makefile.am : Add reduce.c * Makefile.in : Regenerated * gfortran.map : Add _gfortran_reduce, _gfortran_reduce_scalar, _gfortran_reduce_c and _gfortran_reduce_scalar_c to the list. * intrinsics/reduce.c (reduce, reduce_scalar, reduce_c, reduce_scalar_c): New functions and prototypes Diff: --- gcc/fortran/check.cc | 159 ++++++++++++------ gcc/fortran/gfortran.h | 3 + gcc/fortran/intrinsic.cc | 65 +++++++- gcc/fortran/intrinsic.h | 4 + gcc/fortran/iresolve.cc | 214 ++++++++++++++++++++++++ gcc/fortran/trans-expr.cc | 30 +++- gcc/fortran/trans-intrinsic.cc | 3 + gcc/testsuite/gfortran.dg/reduce_1.f90 | 202 +++++++++++++++++++++++ gcc/testsuite/gfortran.dg/reduce_2.f90 | 145 +++++++++++++++++ libgfortran/Makefile.am | 1 + libgfortran/Makefile.in | 7 +- libgfortran/gfortran.map | 4 + libgfortran/intrinsics/reduce.c | 286 +++++++++++++++++++++++++++++++++ 13 files changed, 1070 insertions(+), 53 deletions(-) diff --git a/gcc/fortran/check.cc b/gcc/fortran/check.cc index 35458643835c..d2c8816da2b4 100644 --- a/gcc/fortran/check.cc +++ b/gcc/fortran/check.cc @@ -2442,31 +2442,24 @@ gfc_check_co_broadcast (gfc_expr *a, gfc_expr *source_image, gfc_expr *stat, } -bool -gfc_check_co_reduce (gfc_expr *a, gfc_expr *op, gfc_expr *result_image, - gfc_expr *stat, gfc_expr *errmsg) +/* Helper function for character arguments in gfc_check_[co_]reduce. */ + +static unsigned long +get_ul_from_cst_cl (const gfc_charlen *cl) +{ + return cl && cl->length && cl->length->expr_type == EXPR_CONSTANT + ? mpz_get_ui (cl->length->value.integer) : 0; +}; + + +/* Checks shared between co_reduce and reduce. */ +static bool +check_operation (gfc_expr *op, gfc_expr *a, bool is_co_reduce) { symbol_attribute attr; gfc_formal_arglist *formal; gfc_symbol *sym; - if (a->ts.type == BT_CLASS) - { - gfc_error ("The A argument at %L of CO_REDUCE shall not be polymorphic", - &a->where); - return false; - } - - if (gfc_expr_attr (a).alloc_comp) - { - gfc_error ("Support for the A argument at %L with allocatable components" - " is not yet implemented", &a->where); - return false; - } - - if (!check_co_collective (a, result_image, stat, errmsg, true)) - return false; - if (!gfc_resolve_expr (op)) return false; @@ -2483,8 +2476,9 @@ gfc_check_co_reduce (gfc_expr *a, gfc_expr *op, gfc_expr *result_image, /* None of the intrinsics fulfills the criteria of taking two arguments, returning the same type and kind as the arguments and being permitted as actual argument. */ - gfc_error ("Intrinsic function %s at %L is not permitted for CO_REDUCE", - op->symtree->n.sym->name, &op->where); + gfc_error ("Intrinsic function %s at %L is not permitted for %s", + op->symtree->n.sym->name, &op->where, + is_co_reduce ? "CO_REDUCE" : "REDUCE"); return false; } @@ -2510,12 +2504,14 @@ gfc_check_co_reduce (gfc_expr *a, gfc_expr *op, gfc_expr *result_image, if (!gfc_compare_types (&a->ts, &sym->result->ts)) { - gfc_error ("The A argument at %L has type %s but the function passed as " - "OPERATION at %L returns %s", + gfc_error ("The %s argument at %L has type %s but the function passed " + "as OPERATION at %L returns %s", + is_co_reduce ? "A" : "ARRAY", &a->where, gfc_typename (a), &op->where, gfc_typename (&sym->result->ts)); return false; } + if (!gfc_compare_types (&a->ts, &formal->sym->ts) || !gfc_compare_types (&a->ts, &formal->next->sym->ts)) { @@ -2567,42 +2563,59 @@ gfc_check_co_reduce (gfc_expr *a, gfc_expr *op, gfc_expr *result_image, if (a->ts.type == BT_CHARACTER) { - gfc_charlen *cl; unsigned long actual_size, formal_size1, formal_size2, result_size; - cl = a->ts.u.cl; - actual_size = cl && cl->length && cl->length->expr_type == EXPR_CONSTANT - ? mpz_get_ui (cl->length->value.integer) : 0; - - cl = formal->sym->ts.u.cl; - formal_size1 = cl && cl->length && cl->length->expr_type == EXPR_CONSTANT - ? mpz_get_ui (cl->length->value.integer) : 0; - - cl = formal->next->sym->ts.u.cl; - formal_size2 = cl && cl->length && cl->length->expr_type == EXPR_CONSTANT - ? mpz_get_ui (cl->length->value.integer) : 0; - - cl = sym->ts.u.cl; - result_size = cl && cl->length && cl->length->expr_type == EXPR_CONSTANT - ? mpz_get_ui (cl->length->value.integer) : 0; + actual_size = get_ul_from_cst_cl (a->ts.u.cl); + formal_size1 = get_ul_from_cst_cl (formal->sym->ts.u.cl); + formal_size2 = get_ul_from_cst_cl (formal->next->sym->ts.u.cl); + result_size = get_ul_from_cst_cl (sym->ts.u.cl); if (actual_size && ((formal_size1 && actual_size != formal_size1) || (formal_size2 && actual_size != formal_size2))) { - gfc_error ("The character length of the A argument at %L and of the " - "arguments of the OPERATION at %L shall be the same", - &a->where, &op->where); + gfc_error ("The character length of the %s argument at %L and of " + "the arguments of the OPERATION at %L shall be the same", + is_co_reduce ? "A" : "ARRAY", &a->where, &op->where); return false; } + if (actual_size && result_size && actual_size != result_size) { - gfc_error ("The character length of the A argument at %L and of the " - "function result of the OPERATION at %L shall be the same", + gfc_error ("The character length of the %s argument at %L and of " + "the function result of the OPERATION at %L shall be the " + "same", is_co_reduce ? "A" : "ARRAY", &a->where, &op->where); return false; } } + return true; +} + + +bool +gfc_check_co_reduce (gfc_expr *a, gfc_expr *op, gfc_expr *result_image, + gfc_expr *stat, gfc_expr *errmsg) +{ + if (a->ts.type == BT_CLASS) + { + gfc_error ("The A argument at %L of CO_REDUCE shall not be polymorphic", + &a->where); + return false; + } + + if (gfc_expr_attr (a).alloc_comp) + { + gfc_error ("Support for the A argument at %L with allocatable components" + " is not yet implemented", &a->where); + return false; + } + + if (!check_co_collective (a, result_image, stat, errmsg, true)) + return false; + + if (!check_operation (op, a, true)) + return false; return true; } @@ -5135,6 +5148,62 @@ gfc_check_real (gfc_expr *a, gfc_expr *kind) } +bool +gfc_check_reduce (gfc_expr *array, gfc_expr *operation, gfc_expr *dim, + gfc_expr *mask, gfc_expr *identity, gfc_expr *ordered) +{ + if (array->ts.type == BT_CLASS) + { + gfc_error ("The ARRAY argument at %L of REDUCE shall not be polymorphic", + &array->where); + return false; + } + + if (!check_operation (operation, array, false)) + return false; + + if (dim && (dim->rank || dim->ts.type != BT_INTEGER)) + { + gfc_error ("The DIM argument at %L, if present, must be an integer " + "scalar", &dim->where); + return false; + } + + if (mask && (array->rank != mask->rank || mask->ts.type != BT_LOGICAL)) + { + gfc_error ("The MASK argument at %L, if present, must be a logical " + "array with the same rank as ARRAY", &mask->where); + return false; + } + + if (mask + && !gfc_check_conformance (array, mask, + _("arguments '%s' and '%s' for intrinsic %s"), + "ARRAY", "MASK", "REDUCE")) + return false; + + if (mask && !identity) + gfc_warning (0, "MASK present at %L without IDENTITY", &mask->where); + + if (ordered && (ordered->rank || ordered->ts.type != BT_LOGICAL)) + { + gfc_error ("The ORDERED argument at %L, if present, must be a logical " + "scalar", &ordered->where); + return false; + } + + if (identity && (identity->rank + || !gfc_compare_types (&array->ts, &identity->ts))) + { + gfc_error ("The IDENTITY argument at %L, if present, must be a scalar " + "with the same type as ARRAY", &identity->where); + return false; + } + + return true; +} + + bool gfc_check_rename (gfc_expr *path1, gfc_expr *path2) { diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h index 7c6e9b637db3..5ef70378b1b5 100644 --- a/gcc/fortran/gfortran.h +++ b/gcc/fortran/gfortran.h @@ -647,6 +647,7 @@ enum gfc_isym_id GFC_ISYM_RANK, GFC_ISYM_REAL, GFC_ISYM_REALPART, + GFC_ISYM_REDUCE, GFC_ISYM_RENAME, GFC_ISYM_REPEAT, GFC_ISYM_RESHAPE, @@ -2543,6 +2544,8 @@ typedef union struct gfc_expr *); bool (*f5)(struct gfc_expr *, struct gfc_expr *, struct gfc_expr *, struct gfc_expr *, struct gfc_expr *); + bool (*f6)(struct gfc_expr *, struct gfc_expr *, struct gfc_expr *, + struct gfc_expr *, struct gfc_expr *, struct gfc_expr *); } gfc_check_f; diff --git a/gcc/fortran/intrinsic.cc b/gcc/fortran/intrinsic.cc index 30f532b5766b..d2ce74f16eb5 100644 --- a/gcc/fortran/intrinsic.cc +++ b/gcc/fortran/intrinsic.cc @@ -331,7 +331,7 @@ do_ts29113_check (gfc_intrinsic_sym *specific, gfc_actual_arglist *arg) static bool do_check (gfc_intrinsic_sym *specific, gfc_actual_arglist *arg) { - gfc_expr *a1, *a2, *a3, *a4, *a5; + gfc_expr *a1, *a2, *a3, *a4, *a5, *a6; if (arg == NULL) return (*specific->check.f0) (); @@ -361,6 +361,11 @@ do_check (gfc_intrinsic_sym *specific, gfc_actual_arglist *arg) if (arg == NULL) return (*specific->check.f5) (a1, a2, a3, a4, a5); + a6 = arg->expr; + arg = arg->next; + if (arg == NULL) + return (*specific->check.f6) (a1, a2, a3, a4, a5, a6); + gfc_internal_error ("do_check(): too many args"); } @@ -838,6 +843,44 @@ add_sym_6fl (const char *name, gfc_isym_id id, enum klass cl, int actual_ok, } +/* Add a symbol to the function list where the function takes + 6 arguments. */ + +static void +add_sym_6 (const char *name, gfc_isym_id id, enum klass cl, int actual_ok, + bt type, int kind, int standard, + bool (*check) (gfc_expr *, gfc_expr *, gfc_expr *, + gfc_expr *, gfc_expr *, gfc_expr *), + gfc_expr *(*simplify) (gfc_expr *, gfc_expr *, gfc_expr *, + gfc_expr *, gfc_expr *, gfc_expr *), + void (*resolve) (gfc_expr *, gfc_expr *, gfc_expr *, gfc_expr *, + gfc_expr *, gfc_expr *, gfc_expr *), + const char *a1, bt type1, int kind1, int optional1, + const char *a2, bt type2, int kind2, int optional2, + const char *a3, bt type3, int kind3, int optional3, + const char *a4, bt type4, int kind4, int optional4, + const char *a5, bt type5, int kind5, int optional5, + const char *a6, bt type6, int kind6, int optional6) +{ + gfc_check_f cf; + gfc_simplify_f sf; + gfc_resolve_f rf; + + cf.f6 = check; + sf.f6 = simplify; + rf.f6 = resolve; + + add_sym (name, id, cl, actual_ok, type, kind, standard, cf, sf, rf, + a1, type1, kind1, optional1, INTENT_IN, + a2, type2, kind2, optional2, INTENT_IN, + a3, type3, kind3, optional3, INTENT_IN, + a4, type4, kind4, optional4, INTENT_IN, + a5, type5, kind5, optional5, INTENT_IN, + a6, type6, kind6, optional6, INTENT_IN, + (void *) 0); +} + + /* MINVAL, MAXVAL, PRODUCT, and SUM also get special treatment because their argument also might have to be reordered. */ @@ -1358,13 +1401,13 @@ add_functions (void) *c_ptr_2 = "c_ptr_2", *ca = "coarray", *com = "command", *dist = "distance", *dm = "dim", *f = "field", *failed="failed", *fs = "fsource", *han = "handler", *i = "i", - *image = "image", *j = "j", *kind = "kind", + *idy = "identity", *image = "image", *j = "j", *kind = "kind", *l = "l", *ln = "len", *level = "level", *m = "matrix", *ma = "matrix_a", *mb = "matrix_b", *md = "mode", *mo = "mold", *msk = "mask", *n = "n", *ncopies= "ncopies", *nm = "name", *num = "number", - *ord = "order", *p = "p", *p1 = "path1", *p2 = "path2", - *pad = "pad", *pid = "pid", *pos = "pos", *pt = "pointer", - *r = "r", *rd = "round", + *op = "operation", *ord = "order", *odd = "ordered", *p = "p", + *p1 = "path1", *p2 = "path2", *pad = "pad", *pid = "pid", *pos = "pos", + *pt = "pointer", *r = "r", *rd = "round", *s = "s", *set = "set", *sh = "shift", *shp = "shape", *sig = "sig", *src = "source", *ssg = "substring", *sta = "string_a", *stb = "string_b", *stg = "string", @@ -2936,6 +2979,18 @@ add_functions (void) make_generic ("sngl", GFC_ISYM_SNGL, GFC_STD_F77); + add_sym_6 ("reduce", GFC_ISYM_REDUCE, CLASS_TRANSFORMATIONAL, ACTUAL_NO, + BT_REAL, dr, GFC_STD_F2018, + gfc_check_reduce, NULL, gfc_resolve_reduce, + ar, BT_REAL, dr, REQUIRED, + op, BT_REAL, dr, REQUIRED, + dm, BT_INTEGER, di, OPTIONAL, + msk, BT_LOGICAL, dl, OPTIONAL, + idy, BT_REAL, dr, OPTIONAL, + odd, BT_LOGICAL, dl, OPTIONAL); + + make_generic ("reduce", GFC_ISYM_REDUCE, GFC_STD_F2018); + add_sym_2 ("rename", GFC_ISYM_RENAME, CLASS_IMPURE, ACTUAL_NO, BT_INTEGER, di, GFC_STD_GNU, gfc_check_rename, NULL, gfc_resolve_rename, p1, BT_CHARACTER, dc, REQUIRED, p2, BT_CHARACTER, dc, REQUIRED); diff --git a/gcc/fortran/intrinsic.h b/gcc/fortran/intrinsic.h index 34a0248adbdc..fec1c24a0995 100644 --- a/gcc/fortran/intrinsic.h +++ b/gcc/fortran/intrinsic.h @@ -144,6 +144,8 @@ bool gfc_check_rand (gfc_expr *); bool gfc_check_range (gfc_expr *); bool gfc_check_rank (gfc_expr *); bool gfc_check_real (gfc_expr *, gfc_expr *); +bool gfc_check_reduce (gfc_expr *, gfc_expr *, gfc_expr *, gfc_expr *, + gfc_expr *, gfc_expr *); bool gfc_check_rename (gfc_expr *, gfc_expr *); bool gfc_check_repeat (gfc_expr *, gfc_expr *); bool gfc_check_reshape (gfc_expr *, gfc_expr *, gfc_expr *, gfc_expr *); @@ -589,6 +591,8 @@ void gfc_resolve_parity (gfc_expr *, gfc_expr *, gfc_expr *); void gfc_resolve_product (gfc_expr *, gfc_expr *, gfc_expr *, gfc_expr *); void gfc_resolve_real (gfc_expr *, gfc_expr *, gfc_expr *); void gfc_resolve_realpart (gfc_expr *, gfc_expr *); +void gfc_resolve_reduce (gfc_expr *, gfc_expr *, gfc_expr *, gfc_expr *, + gfc_expr *, gfc_expr *, gfc_expr *); void gfc_resolve_rename (gfc_expr *, gfc_expr *, gfc_expr *); void gfc_resolve_repeat (gfc_expr *, gfc_expr *, gfc_expr *); void gfc_resolve_reshape (gfc_expr *, gfc_expr *, gfc_expr *, gfc_expr *, diff --git a/gcc/fortran/iresolve.cc b/gcc/fortran/iresolve.cc index 55f7e1911ec6..8189d7a1c6f6 100644 --- a/gcc/fortran/iresolve.cc +++ b/gcc/fortran/iresolve.cc @@ -2408,6 +2408,220 @@ gfc_resolve_realpart (gfc_expr *f, gfc_expr *a) } +/* Generate a wrapper subroutine for the operation so that the library REDUCE + function can use pointer arithmetic for OPERATION and not be dependent on + knowledge of its type. */ +static gfc_symtree * +generate_reduce_op_wrapper (gfc_expr *op) +{ + gfc_symbol *operation = op->symtree->n.sym; + gfc_symbol *wrapper, *a, *b, *c; + gfc_symtree *st; + char tname[GFC_MAX_SYMBOL_LEN+1]; + char *name; + gfc_namespace *ns; + gfc_expr *e; + + /* Find the top-level namespace. */ + for (ns = gfc_current_ns; ns; ns = ns->parent) + if (!ns->parent) + break; + + sprintf (tname, "%s_%s", operation->name, + ns->proc_name ? ns->proc_name->name : "noname"); + name = xasprintf ("__reduce_wrapper_%s", tname); + + gfc_find_sym_tree (name, ns, 0, &st); + + if (st && !strcmp (name, st->name)) + { + free (name); + return st; + } + + /* Create the wrapper namespace and contain it in 'ns'. */ + gfc_namespace *sub_ns = gfc_get_namespace (ns, 0); + sub_ns->sibling = ns->contained; + ns->contained = sub_ns; + sub_ns->resolved = 1; + + /* Set up procedure symbol. */ + gfc_get_symbol (name, ns, &wrapper); + sub_ns->proc_name = wrapper; + wrapper->attr.flavor = FL_PROCEDURE; + wrapper->attr.subroutine = 1; + wrapper->attr.artificial = 1; + wrapper->attr.if_source = IFSRC_DECL; + if (ns->proc_name->attr.flavor == FL_MODULE) + wrapper->module = ns->proc_name->name; + gfc_set_sym_referenced (wrapper); + + /* Set up formal argument for the argument 'a'. */ + gfc_get_symbol ("a", sub_ns, &a); + a->ts = operation->ts; + a->attr.flavor = FL_VARIABLE; + a->attr.dummy = 1; + a->attr.artificial = 1; + a->attr.intent = INTENT_INOUT; + wrapper->formal = gfc_get_formal_arglist (); + wrapper->formal->sym = a; + gfc_set_sym_referenced (a); + + /* Set up formal argument for the argument 'b'. This is optional. When + present, the wrapped function is called, otherwise 'a' is assigned + to 'c'. This way, deep copies are effected in the library. */ + gfc_get_symbol ("b", sub_ns, &b); + b->ts = operation->ts; + b->attr.flavor = FL_VARIABLE; + b->attr.dummy = 1; + b->attr.optional= 1; + b->attr.artificial = 1; + b->attr.intent = INTENT_INOUT; + wrapper->formal->next = gfc_get_formal_arglist (); + wrapper->formal->next->sym = b; + gfc_set_sym_referenced (b); + + /* Set up formal argument for the argument 'c'. */ + gfc_get_symbol ("c", sub_ns, &c); + c->ts = operation->ts; + c->attr.flavor = FL_VARIABLE; + c->attr.dummy = 1; + c->attr.artificial = 1; + c->attr.intent = INTENT_INOUT; + wrapper->formal->next->next = gfc_get_formal_arglist (); + wrapper->formal->next->next->sym = c; + gfc_set_sym_referenced (c); + +/* The only code is: + if (present (b)) + c = operation (a, b) + else + c = a + endif + A call with 'b' missing provides a convenient way for the library to do + an intrinsic assignment instead of a call to memcpy and, where allocatable + components are present, a deep copy. + + Code for if (present (b)) */ + sub_ns->code = gfc_get_code (EXEC_IF); + gfc_code *if_block = sub_ns->code; + if_block->block = gfc_get_code (EXEC_IF); + if_block->block->expr1 = gfc_get_expr (); + e = if_block->block->expr1; + e->expr_type = EXPR_FUNCTION; + e->where = gfc_current_locus; + gfc_get_sym_tree ("present", sub_ns, &e->symtree, false); + e->symtree->n.sym->attr.flavor = FL_PROCEDURE; + e->symtree->n.sym->attr.intrinsic = 1; + e->ts.type = BT_LOGICAL; + e->ts.kind = gfc_default_logical_kind; + e->value.function.isym = gfc_intrinsic_function_by_id (GFC_ISYM_PRESENT); + e->value.function.actual = gfc_get_actual_arglist (); + e->value.function.actual->expr = gfc_lval_expr_from_sym (b); + +/* Code for c = operation (a, b) */ + if_block->block->next = gfc_get_code (EXEC_ASSIGN); + if_block->block->next->expr1 = gfc_lval_expr_from_sym (c); + if_block->block->next->expr2 = gfc_get_expr (); + e = if_block->block->next->expr2; + e->expr_type = EXPR_FUNCTION; + e->where = gfc_current_locus; + if_block->block->next->expr2->ts = operation->ts; + gfc_get_sym_tree (operation->name, ns, &e->symtree, false); + e->value.function.esym = if_block->block->next->expr2->symtree->n.sym; + e->value.function.actual = gfc_get_actual_arglist (); + e->value.function.actual->expr = gfc_lval_expr_from_sym (a); + e->value.function.actual->next = gfc_get_actual_arglist (); + e->value.function.actual->next->expr = gfc_lval_expr_from_sym (b); + + if_block->block->block = gfc_get_code (EXEC_IF); + if_block->block->block->next = gfc_get_code (EXEC_ASSIGN); + if_block->block->block->next->expr1 = gfc_lval_expr_from_sym (c); + if_block->block->block->next->expr2 = gfc_lval_expr_from_sym (a); + + /* It is unexpected to have some symbols added at resolution. Commit the + changes in order to keep a clean state. */ + gfc_commit_symbol (if_block->block->expr1->symtree->n.sym); + gfc_commit_symbol (wrapper); + gfc_commit_symbol (a); + gfc_commit_symbol (b); + gfc_commit_symbol (c); + + gfc_find_sym_tree (name, ns, 0, &st); + free (name); + + return st; +} + +void +gfc_resolve_reduce (gfc_expr *f, gfc_expr *array, + gfc_expr *operation, + gfc_expr *dim, + gfc_expr *mask, + gfc_expr *identity ATTRIBUTE_UNUSED, + gfc_expr *ordered ATTRIBUTE_UNUSED) +{ + gfc_symtree *wrapper_symtree; + gfc_typespec ts; + + gfc_resolve_expr (array); + if (array->ts.type == BT_CHARACTER && array->ref) + gfc_resolve_substring_charlen (array); + + f->ts = array->ts; + + /* Replace 'operation' with its subroutine wrapper so that pointers may be + used throughout the library function. */ + wrapper_symtree = generate_reduce_op_wrapper (operation); + gcc_assert (wrapper_symtree && wrapper_symtree->n.sym); + operation->symtree = wrapper_symtree; + operation->ts = operation->symtree->n.sym->ts; + + /* The scalar library function converts the scalar result to a dimension + zero descriptor and then returns the data after the call. */ + if (f->ts.type == BT_CHARACTER) + { + if (dim && array->rank > 1) + { + f->value.function.name = gfc_get_string (PREFIX ("reduce_c")); + f->rank = array->rank - 1; + } + else + { + f->value.function.name = gfc_get_string (PREFIX ("reduce_scalar_c")); + f->rank = 0; + } + } + else + { + if (dim && array->rank > 1) + { + f->value.function.name = gfc_get_string (PREFIX ("reduce")); + f->rank = array->rank - 1; + } + else + { + f->value.function.name = gfc_get_string (PREFIX ("reduce_scalar")); + f->rank = 0; + } + } + + if (dim) + { + ts = dim->ts; + ts.kind = 4; + gfc_convert_type_warn (dim, &ts, 1, 0); + } + + if (mask) + { + ts = mask->ts; + ts.kind = 4; + gfc_convert_type_warn (mask, &ts, 1, 0); + } +} + + void gfc_resolve_rename (gfc_expr *f, gfc_expr *p1 ATTRIBUTE_UNUSED, gfc_expr *p2 ATTRIBUTE_UNUSED) diff --git a/gcc/fortran/trans-expr.cc b/gcc/fortran/trans-expr.cc index 923d46cb47c9..4b90b06fa0a0 100644 --- a/gcc/fortran/trans-expr.cc +++ b/gcc/fortran/trans-expr.cc @@ -6753,6 +6753,12 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym, gfc_intrinsic_sym *isym = expr && expr->rank ? expr->value.function.isym : NULL; + /* In order that the library function for intrinsic REDUCE be type and kind + agnostic, the result is passed by reference. Allocatable components are + handled within the OPERATION wrapper. */ + bool reduce_scalar = expr && !expr->rank && expr->value.function.isym + && expr->value.function.isym->id == GFC_ISYM_REDUCE; + comp = gfc_get_proc_ptr_comp (expr); bool elemental_proc = (comp @@ -8405,6 +8411,7 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym, byref = (comp && (comp->attr.dimension || (comp->ts.type == BT_CHARACTER && !sym->attr.is_bind_c))) || (!comp && gfc_return_by_reference (sym)); + if (byref) { if (se->direct_byref) @@ -8589,6 +8596,17 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym, else if (ts.type == BT_CHARACTER) vec_safe_push (retargs, len); } + else if (reduce_scalar) + { + /* In order that the library function for intrinsic REDUCE be type and + kind agnostic, the result is passed by reference. Allocatable + components are handled within the OPERATION wrapper. */ + type = gfc_typenode_for_spec (&expr->ts); + result = gfc_create_var (type, "sr"); + tmp = gfc_build_addr_expr (pvoid_type_node, result); + vec_safe_push (retargs, tmp); + } + gfc_free_interface_mapping (&mapping); /* We need to glom RETARGS + ARGLIST + STRINGARGS + APPEND_ARGS. */ @@ -8773,10 +8791,12 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym, /* Transformational functions of derived types with allocatable components must have the result allocatable components copied when the - argument is actually given. */ + argument is actually given. This is unnecessry for REDUCE because the + wrapper for the OPERATION function takes care of this. */ arg = expr->value.function.actual; if (result && arg && expr->rank && isym && isym->transformational + && isym->id != GFC_ISYM_REDUCE && arg->expr && arg->expr->ts.type == BT_DERIVED && arg->expr->ts.u.derived->attr.alloc_comp) @@ -8801,6 +8821,14 @@ gfc_conv_procedure_call (gfc_se * se, gfc_symbol * sym, gfc_add_expr_to_block (&se->pre, tmp); } } + else if (reduce_scalar) + { + /* Even though the REDUCE intrinsic library function returns the result + by reference, the scalar call passes the result as se->expr. */ + gfc_add_expr_to_block (&se->pre, se->expr); + se->expr = result; + gfc_add_block_to_block (&se->post, &post); + } else { /* For a function with a class array result, save the result as diff --git a/gcc/fortran/trans-intrinsic.cc b/gcc/fortran/trans-intrinsic.cc index 373a0678a2e5..6b55017bb897 100644 --- a/gcc/fortran/trans-intrinsic.cc +++ b/gcc/fortran/trans-intrinsic.cc @@ -10806,6 +10806,7 @@ gfc_conv_intrinsic_function (gfc_se * se, gfc_expr * expr) case GFC_ISYM_EOSHIFT: case GFC_ISYM_PACK: case GFC_ISYM_RESHAPE: + case GFC_ISYM_REDUCE: /* For all of those the first argument specifies the type and the third is optional. */ conv_generic_with_optional_char_arg (se, expr, 1, 3); @@ -11478,6 +11479,7 @@ gfc_conv_intrinsic_function (gfc_se * se, gfc_expr * expr) case GFC_ISYM_MCLOCK: case GFC_ISYM_MCLOCK8: case GFC_ISYM_RAND: + case GFC_ISYM_REDUCE: case GFC_ISYM_RENAME: case GFC_ISYM_SECOND: case GFC_ISYM_SECNDS: @@ -11934,6 +11936,7 @@ gfc_is_intrinsic_libcall (gfc_expr * expr) case GFC_ISYM_FAILED_IMAGES: case GFC_ISYM_STOPPED_IMAGES: case GFC_ISYM_PACK: + case GFC_ISYM_REDUCE: case GFC_ISYM_RESHAPE: case GFC_ISYM_UNPACK: /* Pass absent optional parameters. */ diff --git a/gcc/testsuite/gfortran.dg/reduce_1.f90 b/gcc/testsuite/gfortran.dg/reduce_1.f90 new file mode 100644 index 000000000000..585cad79ca7a --- /dev/null +++ b/gcc/testsuite/gfortran.dg/reduce_1.f90 @@ -0,0 +1,202 @@ +! { dg-do run } +! +! Test results from the F2018 intrinsic REDUCE +! +! Contributed by Paul Thomas <pa...@gcc.gnu.org> +! + +module operations + type :: s + integer, allocatable :: i + integer :: j + end type s + +contains + + pure function add(i,j) result(sum_ij) + integer, intent(in) :: i, j + integer :: sum_ij + sum_ij = i + j + end function add +! + pure function mult(i,j) result(prod_ij) + integer, intent(in) :: i, j + integer :: prod_ij + prod_ij = i * j + end function mult + + pure function mult_by_val(i,j) result(prod_ij) + integer, intent(in), value :: i, j + integer :: prod_ij + prod_ij = i * j + end function mult_by_val + + pure function non_com(i,j) result(nc_ij) + integer, intent(in) :: i, j + integer :: nc_ij + if (i > j) then + nc_ij = i - j + else + nc_ij = i + j + endif + end function non_com + + pure function c_op (i, j) result (ij) + character(8), intent(in) :: i, j + character(8) :: ij + integer :: n + ij = i + do n = 1, 8 + if (i(n:n) .ne. j(n:n)) ij(n:n) = '!' + end do + end function c_op + + pure function t_op (i, j) result (ij) + type(s), intent(in) :: i, j + type(s) :: ij + ij%i = non_com (i%i, j%i) + ij%j = non_com (j%j, i%j) + end function t_op + + pure function t_add (i, j) result (ij) + type(s), intent(in) :: i, j + type(s) :: ij + ij%i = i%i + j%i + ij%j = j%j + i%j + end function t_add +end module operations + +program test_reduce + use operations + implicit none + integer :: i + integer, parameter :: n = 3 + integer, parameter :: vec(n) = [2, 5, 10] + integer, parameter :: mat(n,2) = reshape([vec,2*vec],shape=[size(vec),2]) + integer :: res0 + integer, dimension(:), allocatable :: res1 + integer, dimension(:,:), allocatable :: res2 + logical, parameter :: t = .true., f = .false. + LOGICAL, PARAMETER :: keep(n) = [t,f,t] + logical, parameter :: keepM(n,2) = reshape([keep,keep],shape=[n,2]) + logical, parameter :: all_false(n,2) = reshape ([(f, i = 1,2*n)],[n,2]) + character(*), parameter :: carray (4) = ['abctefgh', 'atcdefgh', & + 'abcdefth', 'abcdtfgh'] + character(:), allocatable :: cres0, cres1(:) + type(s), allocatable :: tres1(:) + type(s), allocatable :: tres2(:,:) + type(s) :: tres2_na(2, 4) + type(s), allocatable :: tarray(:,:,:) + type(s), allocatable :: tvec(:) + type(s), allocatable :: tres0 + integer, allocatable :: ires(:) + +! Simple cases with and without DIM + res0 = reduce (vec, add, dim=1) + if (res0 /= 17) stop 1 + res0 = reduce (vec, mult, 1) + if (res0 /= 100) stop 2 + res1 = reduce (mat, add, 1) + if (any (res1 /= [17, 34])) stop 3 + res1 = reduce (mat, mult, 1) + if (any (res1 /= [100, 800])) stop 4 + res1 = reduce (mat, add, 2) + if (any (res1 /= [6, 15, 30])) stop 5 + res1 = reduce (mat, mult, 2) + if (any (res1 /= [8, 50, 200])) stop 6 + res0 = reduce (mat, add) + if (res0 /= 51) stop 7 + res0 = reduce (mat, mult) + if (res0 /= 80000) stop 8 +! Repeat previous test with arguments passed by value to operation + res0 = reduce (mat, mult_by_val) + if (res0 /= 80000) stop 9 + +! Using MASK and IDENTITY + res0 = reduce (vec,add, mask=keep, identity = 1) + if (res0 /= 12) stop 10 + res0 = reduce (vec,mult, mask=keep, identity = 1) + if (res0 /= 20) stop 11 + res0 = reduce (mat, add, mask=keepM, identity = 1) + if (res0 /= 36) stop 12 + res0 = reduce (mat, mult, mask=keepM, identity = 1) + if (res0 /= 1600) stop 13 + res0 = reduce (mat, mult, mask=all_false, identity = -1) + if (res0 /= -1) stop 14 + +! 3-D ARRAYs with and without DIM and MASK + res0 = reduce (reshape ([(i, i=1,8)], [2,2,2]),mult) + if (res0 /= 40320) stop 15 + res2 = reduce (reshape ([(i, i=1,8)], [2,2,2]),mult,dim=2) + if (any (res2 /= reshape ([3,8,35,48], [2,2]))) stop 16 + res2 = reduce (reshape ([(i, i=1,8)], [2,2,2]),mult,dim=2, & + mask=reshape ([t,f,t,f,t,f,t,f],[2,2,2]), identity=-1) + if (any (res2 /= reshape ([3,-1,35,-1], [2,2]))) stop 17 + res2 = reduce (reshape([(i, i=1,16)], [2,4,2]), add, dim = 3, & + mask=reshape([f,t,t,f,t,t,t,t,t,t,t,t,t,t,t,t],[2,4,2]), & + identity=-1) + if (any (res2 /= reshape ([9,12,14,12,18,20,22,24], [2,4]))) stop 18 + res1 = reduce (reshape([(i, i=1,16)], [4,4]),add, dim = 2, & + mask=reshape([f,t,t,f,t,t,t,t,t,t,t,t,t,t,t,t],[4,4]), & + identity=-1) + if (any (res1 /= [27,32,36,36])) stop 19 + +! Verify that the library function treats non-comutative OPERATION in the +! correct order. If this were incorrect,the result would be [9,8,8,12,8,8,8,8]. + res2 = reduce (reshape([(i, i=1,16)], [2,4,2]), non_com, dim = 3, & + mask=reshape([f,t,t,f,t,t,t,t,t,t,t,t,t,t,t,t],[2,4,2]), & + identity=-1) + if (any (res2 /= reshape([9,12,14,12,18,20,22,24],shape(res2)))) stop 20 + +! Character ARRAY and OPERATION + cres0 = reduce (carray, c_op); if (cres0 /= 'a!c!!f!h') stop 21 + cres1 = reduce (reshape (carray, [2,2]), c_op, dim = 1) + if (any (cres1 /= ['a!c!efgh','abcd!f!h'])) stop 22 + +! Derived type ARRAY and OPERATION - was checked for memory leaks of the +! allocatable component. +! tarray = reshape([(s(i, i), i = 1, 16)], [2,4,2]) leaks memory! + allocate (tvec(16)) + do i = 1, 16 + tvec(i)%i = i + tvec(i)%j = i + enddo + tarray = reshape(tvec, [2,4,2]) + + tres2 = reduce (tarray, t_op, dim = 3, & + mask=reshape([t,t,t,f,t,t,t,t,t,f,t,t,t,t,t,t],[2,4,2]), & + identity = s(NULL(),1)) + ires = [10,2,14,12,18,20,22,24] + tres1 = reshape (tres2, [size (tres2, 1)* size (tres2, 2)]) + do i = 1, size (tres2, 1)* size (tres2, 2) + if (tres1(i)%i /= ires(i)) stop 23 + end do + if (any (tres2%j /= reshape([8,2,8,12,8,8,8,8],shape(tres2)))) stop 24 + +! Check that the non-allocatable result with an allocatable component does not +! leak memory from the allocatable component + tres2_na = reduce (tarray, t_op, dim = 3, & + mask=reshape([t,t,t,f,t,t,t,t,t,f,t,t,t,t,t,t],[2,4,2]), & + identity = s(NULL(),1)) + tres1 = reshape (tres2_na, [size (tres2_na, 1)* size (tres2, 2)]) + do i = 1, size (tres2_na, 1)* size (tres2_na, 2) + if (tres1(i)%i /= ires(i)) stop 25 + end do + if (any (tres2_na%j /= reshape([8,2,8,12,8,8,8,8],shape(tres2_na)))) stop 26 + + + tres0 = reduce (tarray, t_add) + if (tres0%i /= 136) stop 27 + if (tres0%j /= 136) stop 28 + +! Test array being a component of an array of derived types + i = reduce (tarray%j, add, & + mask=reshape([t,t,t,f,t,t,t,t,t,f,t,t,t,t,f,t],[2,4,2]), & + identity = 0) + if (i /= 107) stop 29 + + +! Deallocate the allocatable components and then the allocatable variables + tres2_na = reshape ([(s(NULL (), 0), i = 1, size (tres2_na))], shape (tres2_na)) + deallocate (res1, res2, cres0, cres1, tarray, ires, tres0, tres1, tres2, tvec) +end diff --git a/gcc/testsuite/gfortran.dg/reduce_2.f90 b/gcc/testsuite/gfortran.dg/reduce_2.f90 new file mode 100644 index 000000000000..52d7c682a853 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/reduce_2.f90 @@ -0,0 +1,145 @@ +! { dg-do compile } +! +! Test argument compliance for the F2018 intrinsic REDUCE +! +! Contributed by Paul Thomas <pa...@gcc.gnu.org> +! + class (*), allocatable :: cstar (:) + integer, allocatable :: i(:,:,:) + integer :: n(2,2) + Logical :: l1(4), l2(2,3), l3(2,2) + +! The ARRAY argument at (1) of REDUCE shall not be polymorphic + print *, reduce (cstar, add) ! { dg-error "shall not be polymorphic" } + +! OPERATION argument at %L must be a PURE function + print *, reduce (i, iadd) ! { dg-error "must be a PURE function" } + print *, reduce (i, foo) ! { dg-error "must be a PURE function" } + +! The function passed as OPERATION at (1) shall have scalar nonallocatable +! nonpointer arguments and return a nonallocatable nonpointer scalar + print *, reduce (i, vadd) ! { dg-error "return a nonallocatable nonpointer scalar" } + +! The function passed as OPERATION at (1) shall have two arguments + print *, reduce (i, add_1a) ! { dg-error "shall have two arguments" } + print *, reduce (i, add_3a) ! { dg-error "shall have two arguments" } + +!The ARRAY argument at (1) has type INTEGER(4) but the function passed as OPERATION at +! (2) returns REAL(4) + print *, reduce (i, add_r) ! { dg-error "returns REAL" } + +! The function passed as OPERATION at (1) shall have scalar nonallocatable nonpointer +! arguments and return a nonallocatable nonpointer scalar + print *, reduce (i, add_a) ! { dg-error "return a nonallocatable nonpointer scalar" } + +! The function passed as OPERATION at (1) shall have scalar nonallocatable nonpointer arguments and +! return a nonallocatable nonpointer scalar + print *, reduce (i, add_array) ! { dg-error "scalar nonallocatable nonpointer arguments" } + +! The function passed as OPERATION at (1) shall not have the OPTIONAL attribute for either of the arguments + print *, reduce (i, add_optional) ! { dg-error "shall not have the OPTIONAL attribute" } + +! The function passed as OPERATION at (1) shall have the VALUE attribute either for none or both arguments + print *, reduce (i, add_one_value) ! { dg-error "VALUE attribute either for none or both arguments" } + +! The character length of the ARRAY argument at (1) and of the arguments of the OPERATION at (2) +! shall be the same + print *, reduce ([character(4) :: 'abcd','efgh'], char_one) ! { dg-error "The character length of the ARRAY" } + +! The character length of the ARRAY argument at (1) and of the function result of the OPERATION +! at (2) shall be the same + print *, reduce ([character(4) :: 'abcd','efgh'], char_two) ! { dg-error "function result of the OPERATION" } + +! The character length of the ARRAY argument at (1) and of the arguments of the OPERATION at +! (2) shall be the same + print *, reduce ([character(4) :: 'abcd','efgh'], char_three) ! { dg-error "arguments of the OPERATION" } + +! The DIM argument at (1), if present, must be an integer scalar + print *, reduce (i, add, dim = 2.0) ! { dg-error "must be an integer scalar" } + +! The DIM argument at (1), if present, must be an integer scalar + print *, reduce (i, add, dim = [2]) ! { dg-error "must be an integer scalar" } + +! The MASK argument at (1), if present, must be a logical array with the same rank as ARRAY + print *, reduce (n, add, mask = l1) ! { dg-error "same rank as ARRAY" } + print *, reduce (n, add, mask = n) ! { dg-error "must be a logical array" } + +! Different shape for arguments 'ARRAY' and 'MASK' for intrinsic REDUCE at (1) on +! dimension 2 (2 and 3) + print *, reduce (n, add, mask = l2) ! { dg-error "Different shape" } + +! The IDENTITY argument at (1), if present, must be a scalar with the same type as ARRAY + print *, reduce (n, add, mask = l3, identity = 1.0) ! { dg-error "same type as ARRAY" } + print *, reduce (n, add, mask = l3, identity = [1]) ! { dg-error "must be a scalar" } + +! MASK present at (1) without IDENTITY + print *, reduce (n, add, mask = l3) ! { dg-warning "without IDENTITY" } + +contains + pure function add(i,j) result(sum_ij) + integer, intent(in) :: i, j + integer :: sum_ij + sum_ij = i + j + end function add + function iadd(i,j) result(sum_ij) + integer, intent(in) :: i, j + integer :: sum_ij + sum_ij = i + j + end function iadd + pure function vadd(i,j) result(sum_ij) + integer, intent(in) :: i, j + integer :: sum_ij(6) + sum_ij = i + j + end function vadd + pure function add_1a(i) result(sum_ij) + integer, intent(in) :: i + integer :: sum_ij + sum_ij = 0 + end function add_1a + pure function add_3a(i) result(sum_ij) + integer, intent(in) :: i + integer :: sum_ij + sum_ij = 0 + end function add_3a + pure function add_r(i, j) result(sum_ij) + integer, intent(in) :: i, j + real :: sum_ij + sum_ij = 0.0 + end function add_r + pure function add_a(i, j) result(sum_ij) + integer, intent(in) :: i, j + integer, allocatable :: sum_ij + sum_ij = 0 + end function add_a + pure function add_array(i, j) result(sum_ij) + integer, intent(in), dimension(:) :: i, j + integer :: sum_ij + sum_ij = 0 + end function add_array + pure function add_optional(i, j) result(sum_ij) + integer, intent(in), optional :: i, j + integer :: sum_ij + sum_ij = 0 + end function add_optional + pure function add_one_value(i, j) result(sum_ij) + integer, intent(in), value :: i + integer, intent(in) :: j + integer :: sum_ij + sum_ij = 0 + end function add_one_value + pure function char_one(i, j) result(sum_ij) + character(8), intent(in) :: i, j + character(8) :: sum_ij + end function char_one + pure function char_two(i, j) result(sum_ij) + character(4), intent(in) :: i, j + character(8) :: sum_ij + end function char_two + pure function char_three(i, j) result(sum_ij) + character(8), intent(in) :: i + character(4), intent(in) :: j + character(4) :: sum_ij + end function char_three + subroutine foo + end subroutine foo +end diff --git a/libgfortran/Makefile.am b/libgfortran/Makefile.am index 52bd6ea641f0..21b35c76a06d 100644 --- a/libgfortran/Makefile.am +++ b/libgfortran/Makefile.am @@ -149,6 +149,7 @@ intrinsics/spread_generic.c \ intrinsics/string_intrinsics.c \ intrinsics/rand.c \ intrinsics/random.c \ +intrinsics/reduce.c \ intrinsics/reshape_generic.c \ intrinsics/reshape_packed.c \ intrinsics/selected_int_kind.f90 \ diff --git a/libgfortran/Makefile.in b/libgfortran/Makefile.in index 9b9ed2f86ba4..6a63d8876b18 100644 --- a/libgfortran/Makefile.in +++ b/libgfortran/Makefile.in @@ -612,7 +612,7 @@ am__objects_59 = intrinsics/associated.lo intrinsics/abort.lo \ intrinsics/move_alloc.lo intrinsics/pack_generic.lo \ intrinsics/selected_char_kind.lo intrinsics/size.lo \ intrinsics/spread_generic.lo intrinsics/string_intrinsics.lo \ - intrinsics/rand.lo intrinsics/random.lo \ + intrinsics/rand.lo intrinsics/random.lo intrinsics/reduce.lo \ intrinsics/reshape_generic.lo intrinsics/reshape_packed.lo \ intrinsics/selected_int_kind.lo \ intrinsics/selected_real_kind.lo intrinsics/trigd.lo \ @@ -1028,7 +1028,7 @@ gfor_helper_src = intrinsics/associated.c intrinsics/abort.c \ intrinsics/move_alloc.c intrinsics/pack_generic.c \ intrinsics/selected_char_kind.c intrinsics/size.c \ intrinsics/spread_generic.c intrinsics/string_intrinsics.c \ - intrinsics/rand.c intrinsics/random.c \ + intrinsics/rand.c intrinsics/random.c intrinsics/reduce.c \ intrinsics/reshape_generic.c intrinsics/reshape_packed.c \ intrinsics/selected_int_kind.f90 \ intrinsics/selected_real_kind.f90 intrinsics/trigd.c \ @@ -3476,6 +3476,8 @@ intrinsics/rand.lo: intrinsics/$(am__dirstamp) \ intrinsics/$(DEPDIR)/$(am__dirstamp) intrinsics/random.lo: intrinsics/$(am__dirstamp) \ intrinsics/$(DEPDIR)/$(am__dirstamp) +intrinsics/reduce.lo: intrinsics/$(am__dirstamp) \ + intrinsics/$(DEPDIR)/$(am__dirstamp) intrinsics/reshape_generic.lo: intrinsics/$(am__dirstamp) \ intrinsics/$(DEPDIR)/$(am__dirstamp) intrinsics/reshape_packed.lo: intrinsics/$(am__dirstamp) \ @@ -4591,6 +4593,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@intrinsics/$(DEPDIR)/perror.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@intrinsics/$(DEPDIR)/rand.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@intrinsics/$(DEPDIR)/random.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@intrinsics/$(DEPDIR)/reduce.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@intrinsics/$(DEPDIR)/rename.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@intrinsics/$(DEPDIR)/reshape_generic.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@intrinsics/$(DEPDIR)/reshape_packed.Plo@am__quote@ diff --git a/libgfortran/gfortran.map b/libgfortran/gfortran.map index 159a862e90a5..7725e1258823 100644 --- a/libgfortran/gfortran.map +++ b/libgfortran/gfortran.map @@ -2023,4 +2023,8 @@ GFORTRAN_15 { _gfortran_pow_m16_m4; _gfortran_pow_m16_m8; _gfortran_pow_m16_m16; + _gfortran_reduce; + _gfortran_reduce_scalar; + _gfortran_reduce_c; + _gfortran_reduce_scalar_c; } GFORTRAN_14; diff --git a/libgfortran/intrinsics/reduce.c b/libgfortran/intrinsics/reduce.c new file mode 100644 index 000000000000..63997d874baa --- /dev/null +++ b/libgfortran/intrinsics/reduce.c @@ -0,0 +1,286 @@ +/* Generic implementation of the reduce intrinsic + Copyright (C) 2002-2025 Free Software Foundation, Inc. + Contributed by Paul Thomas <pa...@gcc.gnu.org> + +This file is part of the GNU Fortran runtime library (libgfortran). + +Libgfortran 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 of the License, or (at your option) any later version. + +Ligbfortran is distributed in the hope that it will be useful, +but WITHOUT ANY WarrayANTY; without even the implied warrayanty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +Under Section 7 of GPL version 3, you are granted additional +permissions described in the GCC Runtime Library Exception, version +3.1, as published by the Free Software Foundation. + +You should have received a copy of the GNU General Public License and +a copy of the GCC Runtime Library Exception along with this program; +see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +<http://www.gnu.org/licenses/>. */ + +#include "libgfortran.h" +#include <string.h> +#include <stdio.h> + +typedef GFC_FULL_ARRAY_DESCRIPTOR (GFC_MAX_DIMENSIONS, char) parray; + +extern void reduce (parray *, parray *, + void (*operation) (void *, void *, void *), + GFC_INTEGER_4 *, gfc_array_l4 *, void *, void *); +export_proto (reduce); + +void +reduce (parray *ret, + parray *array, + void (*operation) (void *, void *, void *), + GFC_INTEGER_4 *dim, + gfc_array_l4 *mask, + void *identity, + void *ordered __attribute__ ((unused))) +{ + GFC_LOGICAL_4 maskR = 0; + void *array_ptr; + void *buffer_ptr; + void *zero; + void *buffer; + void *res; + index_type ext0, ext1, ext2; + index_type str0, str1, str2; + index_type idx0, idx1, idx2; + index_type dimen, dimen_m1, ldx; + bool started; + bool masked = false; + bool dim_present = dim != NULL; + bool mask_present = mask != NULL; + bool identity_present = identity != NULL; + bool scalar_result; + int i; + int array_rank = (int)GFC_DESCRIPTOR_RANK (array); + size_t elem_len = GFC_DESCRIPTOR_SIZE (array); + +/* The standard mandates that OPERATION is a pure scalar function such that in + the reduction below: + + *buffer_ptr = OPERATION (*buffer_ptr, array(idx1, idx2, idx3)) + + To make this type agnostic, the front end builds a wrapper, that puts the + assignment within a subroutine and transforms it into a pointer operation: + + operation (buffer_ptr, &array(idx1, idx2, idx3), buffer_ptr) + + The wrapper also detects the presence or not of the second argument. If it + is not present, the wrapper effects *third_arg = *first_arg. + + The only information needed about the type of array is its element size. In + both modes, the wrapper takes care of allocatable components correctly, + which is why the second mode is used to fill the result elements. */ + + if (dim_present) + { + if ((*dim < 1) || (*dim > (GFC_INTEGER_4)array_rank)) + runtime_error ("DIM in REDUCE intrinsic is less than 0 or greater than " + "the rank of ARRAY"); + dimen = (index_type) *dim; + } + else + dimen = 1; + dimen_m1 = dimen -1; + + /* Set up the shape and strides for the reduction. This is made relatively + painless by the use of pointer arithmetic throughout (except for MASK, + whose type is known. */ + ext0 = ext1 = ext2 = 1; + str0 = str1 = str2 = 1; + + scalar_result = (!dim_present && array_rank > 1) || array_rank == 1; + + for (i = 0; i < array_rank; i++) + { + /* Obtain the shape of the reshaped ARRAY. */ + index_type ext = GFC_DESCRIPTOR_EXTENT (array,i); + index_type str = GFC_DESCRIPTOR_STRIDE (array,i); + + if (masked && (ext != GFC_DESCRIPTOR_EXTENT (mask, i))) + runtime_error ("shape mismatch between ARRAY and MASK in REDUCE " + "intrinsic"); + + if (scalar_result) + { + ext1 *= ext; + continue; + } + else if (i < dimen_m1) + ext0 *= ext; + else if (i == dimen_m1) + ext1 = ext; + else + ext2 *= ext; + + /* The dimensions of the return array. */ + if (i < (int)(dimen - 1)) + GFC_DIMENSION_SET (ret->dim[i], 0, ext - 1, str); + else if (i < array_rank - 1) + GFC_DIMENSION_SET (ret->dim[i], 0, ext - 1, str); + } + + if (!scalar_result) + { + str1 = GFC_DESCRIPTOR_STRIDE (array, dimen_m1); + if (dimen < array_rank) + str2 = GFC_DESCRIPTOR_STRIDE (array, dimen); + else + str2 = 1; + } + + /* Allocate the result data, the result buffer and zero. */ + if (ret->base_addr == NULL) + ret->base_addr = calloc ((size_t)(ext0 * ext2), elem_len); + buffer = calloc (1, elem_len); + zero = calloc (1, elem_len); + + /* Now loop over the first and third dimensions of the reshaped ARRAY. */ + for (idx0 = 0; idx0 < ext0; idx0++) + { + for (idx2 = 0; idx2 < ext2; idx2++) + { + ldx = idx0 * str0 + idx2 * str2; + if (mask_present) + maskR = mask->base_addr[ldx]; + + started = (mask_present && maskR) || !mask_present; + + buffer_ptr = array->base_addr + + (size_t)((idx0 * str0 + idx2 * str2) * elem_len); + + /* Start the iteration over the second dimension of ARRAY. */ + for (idx1 = 1; idx1 < ext1; idx1++) + { + /* If masked, cycle until after first element that is not masked + out. Then set 'started' and cycle so that this becomes the + first element in the reduction. */ + ldx = idx0 * str0 + idx1 * str1 + idx2 * str2; + if (mask_present) + maskR = mask->base_addr[ldx]; + + array_ptr = array->base_addr + + (size_t)((idx0 * str0 + idx1 * str1 + + idx2 * str2) * elem_len); + if (!started) + { + if (mask_present && maskR) + started = true; + buffer_ptr = array_ptr; + continue; + } + + /* Call the operation, if not masked out, with the previous + element or the buffer and current element as arguments. The + result is stored in the buffer and the buffer_ptr set to + point to buffer, instead of the previous array element. */ + if ((mask_present && maskR) || !mask_present) + { + operation (buffer_ptr, array_ptr, buffer); + buffer_ptr = buffer; + } + } + + /* Now the result of the iteration is transferred to the returned + result. If this result element is empty emit an error or, if + available, set to identity. Note that str1 is paired with idx2 + here because the result skips a dimension. */ + res = ret->base_addr + (size_t)((idx0 * str0 + idx2 * str1) * elem_len); + if (started) + { + operation (buffer_ptr, NULL, res); + operation (zero, NULL, buffer); + } + else + { + if (!identity_present) + runtime_error ("Empty column and no IDENTITY in REDUCE " + "intrinsic"); + else + operation (identity, NULL, res); + } + } + } + free (zero); + free (buffer); +} + + +extern void reduce_scalar (void *, parray *, + void (*operation) (void *, void *, void *), + GFC_INTEGER_4 *, gfc_array_l4 *, void *, void *); +export_proto (reduce_scalar); + +void +reduce_scalar (void *res, + parray *array, + void (*operation) (void *, void *, void *), + GFC_INTEGER_4 *dim, + gfc_array_l4 *mask, + void *identity, + void *ordered) +{ + parray ret; + ret.base_addr = NULL; + ret.dtype.rank = 0; + reduce (&ret, array, operation, dim, mask, identity, ordered); + memcpy (res, ret.base_addr, GFC_DESCRIPTOR_SIZE (array)); + if (ret.base_addr) free (ret.base_addr); +} + +extern void reduce_c (parray *, index_type, parray *, + void (*operation) (void *, void *, void *), + GFC_INTEGER_4 *, gfc_array_l4 *, void *, void *, + index_type, index_type); +export_proto (reduce_c); + +void +reduce_c (parray *ret, + index_type ret_strlen __attribute__ ((unused)), + parray *array, + void (*operation) (void *, void *, void *), + GFC_INTEGER_4 *dim, + gfc_array_l4 *mask, + void *identity, + void *ordered, + index_type array_strlen __attribute__ ((unused)), + index_type identity_strlen __attribute__ ((unused))) +{ + reduce (ret, array, operation, dim, mask, identity, ordered); +} + + +extern void reduce_scalar_c (void *, index_type, parray *, + void (*operation) (void *, void *, void *), + GFC_INTEGER_4 *, gfc_array_l4 *, void *, void *, + index_type, index_type); +export_proto (reduce_scalar_c); + + +void +reduce_scalar_c (void *res, + index_type res_strlen __attribute__ ((unused)), + parray *array, + void (*operation) (void *, void *, void *), + int *dim, + gfc_array_l4 *mask, + void *identity, + void *ordered, + index_type array_strlen __attribute__ ((unused)), + index_type identity_strlen __attribute__ ((unused))) +{ + parray ret; + ret.base_addr = NULL; + ret.dtype.rank = 0; + reduce (&ret, array, operation, dim, mask, identity, ordered); + memcpy (res, ret.base_addr, GFC_DESCRIPTOR_SIZE (array)); + if (ret.base_addr) free (ret.base_addr); +}