
2025-04-09 Paul Thomas <pault@gcc.gnu.org> and Harald Anlauf <anlauf@gcc.gnu.org> gcc/fortran PR fortran/119460 * iresolve.cc (generate_reduce_op_wrapper): Increase the size of 'tname'. Change intent of 'a' and 'b' to intent_in. * trans-decl.cc (add_argument_checking): Do not test artificial formal symbols. * trans-expr.cc (gfc_conv_procedure_call): Remove reduce_scalar and the blocks triggered by it. * trans-intrinsic.cc (gfc_conv_intrinsic_function): Set the result of non-character, scalar reduce to be allocatable. gcc/testsuite/ PR fortran/119460 * gfortran.dg/reduce_2.f90: Add test to check that deferred len characters cannot slip through. * gfortran.dg/reduce_3.f90: New test * gfortran.dg/reduce_4.f90: New test libgfortran/ PR libfortran/119460 * intrinsics/reduce.c (reduce): Correct error message about mismatch between dim and the rank of array. Output the values of both. Correct the evaluation of the result stride and extent. (reduce_scalar): The front end treats the result as an allocatable so eliminate memcpy and free. Return the base-addr of the local descriptor. (reduce_c): Correct the type of the string lengths. (reduce_scalar_c): Correct the type of the string lengths.Test to see if 'res' is allocated. If not then return the base_addr of the local descriptor.
299 lines
9 KiB
C
299 lines
9 KiB
C
/* Generic implementation of the reduce intrinsic
|
|
Copyright (C) 2002-2025 Free Software Foundation, Inc.
|
|
Contributed by Paul Thomas <pault@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, ext, str;
|
|
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, j;
|
|
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 ("Mismatch between DIM and the rank of ARRAY in the "
|
|
"REDUCE intrinsic (%d/%d)", (int)*dim, array_rank);
|
|
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;
|
|
|
|
j = 0;
|
|
for (i = 0; i < array_rank; i++)
|
|
{
|
|
/* Obtain the shape of the reshaped ARRAY. */
|
|
ext = GFC_DESCRIPTOR_EXTENT (array,i);
|
|
str = GFC_DESCRIPTOR_STRIDE (array,i);
|
|
|
|
if (masked && (ext != GFC_DESCRIPTOR_EXTENT (mask, i)))
|
|
{
|
|
int mext = (int)GFC_DESCRIPTOR_EXTENT (mask, i);
|
|
runtime_error ("shape mismatch between ARRAY and MASK in the REDUCE "
|
|
"intrinsic (%zd/%d)", ext, mext);
|
|
}
|
|
|
|
if (scalar_result)
|
|
{
|
|
ext1 *= ext;
|
|
continue;
|
|
}
|
|
else if (i < (int)dimen_m1)
|
|
ext0 *= ext;
|
|
else if (i == (int)dimen_m1)
|
|
ext1 = ext;
|
|
else
|
|
ext2 *= ext;
|
|
|
|
/* The dimensions of the return array. */
|
|
if (i != (int)dimen_m1)
|
|
{
|
|
str = GFC_DESCRIPTOR_STRIDE (array, j);
|
|
GFC_DIMENSION_SET (ret->dim[j], 0, ext - 1, str);
|
|
j++;
|
|
}
|
|
}
|
|
|
|
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 (parray *,
|
|
void (*operation) (void *, void *, void *),
|
|
GFC_INTEGER_4 *, gfc_array_l4 *, void *, void *);
|
|
export_proto (reduce_scalar);
|
|
|
|
void *
|
|
reduce_scalar (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);
|
|
return (void *)ret.base_addr;
|
|
}
|
|
|
|
extern void reduce_c (parray *, gfc_charlen_type, parray *,
|
|
void (*operation) (void *, void *, void *),
|
|
GFC_INTEGER_4 *, gfc_array_l4 *, void *, void *,
|
|
gfc_charlen_type, gfc_charlen_type);
|
|
export_proto (reduce_c);
|
|
|
|
void
|
|
reduce_c (parray *ret,
|
|
gfc_charlen_type ret_strlen __attribute__ ((unused)),
|
|
parray *array,
|
|
void (*operation) (void *, void *, void *),
|
|
GFC_INTEGER_4 *dim,
|
|
gfc_array_l4 *mask,
|
|
void *identity,
|
|
void *ordered,
|
|
gfc_charlen_type array_strlen __attribute__ ((unused)),
|
|
gfc_charlen_type identity_strlen __attribute__ ((unused)))
|
|
{
|
|
/* The frontend constraints make string length checking redundant. Also, the
|
|
scalar symbol is flagged to be allocatable in trans-intrinsic.cc so that
|
|
gfc_conv_procedure_call does the necessary allocation/deallocation. */
|
|
reduce (ret, array, operation, dim, mask, identity, ordered);
|
|
}
|
|
|
|
|
|
extern void reduce_scalar_c (void *, gfc_charlen_type, parray *,
|
|
void (*operation) (void *, void *, void *),
|
|
GFC_INTEGER_4 *, gfc_array_l4 *, void *, void *,
|
|
gfc_charlen_type, gfc_charlen_type);
|
|
export_proto (reduce_scalar_c);
|
|
|
|
|
|
void
|
|
reduce_scalar_c (void *res,
|
|
gfc_charlen_type res_strlen __attribute__ ((unused)),
|
|
parray *array,
|
|
void (*operation) (void *, void *, void *),
|
|
GFC_INTEGER_4 *dim,
|
|
gfc_array_l4 *mask,
|
|
void *identity,
|
|
void *ordered,
|
|
gfc_charlen_type array_strlen __attribute__ ((unused)),
|
|
gfc_charlen_type identity_strlen __attribute__ ((unused)))
|
|
{
|
|
parray ret;
|
|
ret.base_addr = NULL;
|
|
ret.dtype.rank = 0;
|
|
/* The frontend constraints make string length checking redundant. */
|
|
reduce (&ret, array, operation, dim, mask, identity, ordered);
|
|
if (res)
|
|
{
|
|
memcpy (res, ret.base_addr, GFC_DESCRIPTOR_SIZE (array));
|
|
if (ret.base_addr) free (ret.base_addr);
|
|
}
|
|
else
|
|
res = ret.base_addr;
|
|
}
|