gcc/libgfortran/intrinsics/reduce.c
Paul Thomas ee65440cbd Fortran: Fix some problems with the reduce intrinsic [PR119460]
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.
2025-04-09 09:50:04 +01:00

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;
}