Re: [PATCH 2/2] Fortran: f_c_string intrinsic improvements
Sandra Loosemore <[email protected]>
| Newsgroups | gmane.comp.gcc.patches,gmane.comp.gcc.fortran |
|---|---|
| Message-ID | <[email protected]> |
On 5/28/26 08:38, Tobias Burnus wrote:
> Sandra Loosemore wrote:
>> The existing implementation of f_c_string is quite inefficient, doing
>> either 2 or 3 allocations and copies of the input string prefix. This
>> rewrite adds folding for constant string arguments and handles other
>> cases with a single allocation and copy.
>>
>> This patch also adds the missing documentation for this intrinsic to the
>> gfortran manual.
>
> Thanks for the patch. LGTM, I have one .texi comment, however:
>
>> @@ -3558,6 +3559,7 @@ Fortran 2023 and later.
>> @ref{C_LOC}, @*
>> @ref{C_F_POINTER}
>> @ref{C_F_PROCPOINTER}
>> +@ref{F_C_STRING}
>> @end table
>
> I noticed that the others use ', @*'. In 'info' it displays
> currently as:
>
> _See also_:
> *note C_ASSOCIATED::,
> *note C_LOC::,
> *note C_F_POINTER::,
> *note C_F_PROCPOINTER:: *note F_C_STRING::
>
>
> While in the PDF version:
>
> See also: Section 8.56 [C ASSOCIATED], page 155,
> Section 8.61 [C LOC], page 160,
> Section 8.57 [C F POINTER], page 156,
> Section 8.58 [C F PROCPOINTER], page 157, Section 8.112 [F C
> STRING], page 195,
>
> which is comma wise fine (seems as if a comma always gets added, like
> the superfluous tailing one). For consistency, I think it would be good
> to if all followed the same pattern.
Thanks for the speedy review! I have fixed the texinfo markup in both
patches, and pushed the versions attached.
-Sandra
0001-Fortran-Add-c_f_strpointer-intrinsic.patch
(text/x-patch, 35.2 KB)
From a6b2fe7f54bffc4006bee4b20dd788c0a81a9e2e Mon Sep 17 00:00:00 2001 From: Sandra Loosemore <[email protected]> Date: Thu, 28 May 2026 22:33:33 +0000 Subject: [PATCH 1/2] Fortran: Add c_f_strpointer intrinsic This is a missing Fortran 2023 feature. gcc/fortran/ChangeLog * check.cc (gfc_check_c_f_strpointer): New. * f95-lang.cc (gfc_init_builtin_functions): Add BUILT_IN_STRNLEN. * gfortran.h (enum gfc_isym_id): Add GFC_ISYM_C_F_STRPOINTER. * gfortran.texi (Interoperable Subroutines and Functions): Mention f_c_string and c_f_strpointer. * intrinsic.cc (add_subroutines): Add c_f_strpointer. Fix nearby whitespace errors. (sort_actual): Handle first argument to c_f_strpointer specially. * intrinsic.h (gfc_check_c_f_strpointer): Declare. * intrinsic.texi (C_F_STRPOINTER): New section. Add entry to menu and cross-references from similar functions. * iso-c-binding.def: Add c_f_strpointer. * trans-intrinsic.cc (conv_isocbinding_subroutine_strpointer): New. (gfc_conv_intrinsic_subroutine): Call it. gcc/testsuite/ChangeLog * gfortran.dg/c_f_strpointer-1.f90: New. * gfortran.dg/c_f_strpointer-2.f90: New. * gfortran.dg/c_f_strpointer-3.f90: New. * gfortran.dg/c_f_strpointer-4.f90: New. * gfortran.dg/c_f_strpointer-5.f90: New. * gfortran.dg/c_f_strpointer-6.f90: New. * gfortran.dg/c_f_strpointer-7.f90: New. * gfortran.dg/c_f_strpointer-8.f90: New. * gfortran.dg/c_f_strpointer-9.f90: New. * gfortran.dg/c_f_strpointer-10.f90: New. * gfortran.dg/pr108961.f90: Rename locally-defined c_f_strpointer. Co-authored-by: Tobias Burnus <[email protected]> --- gcc/fortran/check.cc | 142 ++++++++++++++++++ gcc/fortran/f95-lang.cc | 5 + gcc/fortran/gfortran.h | 1 + gcc/fortran/gfortran.texi | 7 + gcc/fortran/intrinsic.cc | 48 +++++- gcc/fortran/intrinsic.h | 1 + gcc/fortran/intrinsic.texi | 86 ++++++++++- gcc/fortran/iso-c-binding.def | 2 + gcc/fortran/trans-intrinsic.cc | 120 +++++++++++++++ .../gfortran.dg/c_f_strpointer-1.f90 | 30 ++++ .../gfortran.dg/c_f_strpointer-10.f90 | 39 +++++ .../gfortran.dg/c_f_strpointer-2.f90 | 33 ++++ .../gfortran.dg/c_f_strpointer-3.f90 | 37 +++++ .../gfortran.dg/c_f_strpointer-4.f90 | 18 +++ .../gfortran.dg/c_f_strpointer-5.f90 | 19 +++ .../gfortran.dg/c_f_strpointer-6.f90 | 20 +++ .../gfortran.dg/c_f_strpointer-7.f90 | 50 ++++++ .../gfortran.dg/c_f_strpointer-8.f90 | 11 ++ .../gfortran.dg/c_f_strpointer-9.f90 | 34 +++++ gcc/testsuite/gfortran.dg/pr108961.f90 | 4 +- 20 files changed, 699 insertions(+), 8 deletions(-) create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-1.f90 create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-10.f90 create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-2.f90 create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-3.f90 create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-4.f90 create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-5.f90 create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-6.f90 create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-7.f90 create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-8.f90 create mode 100644 gcc/testsuite/gfortran.dg/c_f_strpointer-9.f90 diff --git a/gcc/fortran/check.cc b/gcc/fortran/check.cc index c4d9901a82d..ad6f66015d7 100644 --- a/gcc/fortran/check.cc +++ b/gcc/fortran/check.cc @@ -6306,6 +6306,148 @@ gfc_check_c_f_procpointer (gfc_expr *cptr, gfc_expr *fptr) } +/* Handle both forms of this intrinsic, differentiated by whether + the first argument is a scalar or array. */ + +bool +gfc_check_c_f_strpointer (gfc_expr *arg0, gfc_expr *fstrptr, + gfc_expr *nchars) +{ + bool arg0_is_scalar = false; + const char *arg0name = "cstrarray"; + + if (arg0->rank == 0) + { + arg0_is_scalar = true; + arg0name = "cstrptr"; + + /* cstrptr is a scalar of type c_ptr. It is an intent in argument + holding the C address of a contiguous array s of nchars characters. + Its value must not be the C address of a Fortran variable without + the target attribute. */ + if (arg0->ts.type != BT_DERIVED + || arg0->ts.u.derived->from_intmod != INTMOD_ISO_C_BINDING + || arg0->ts.u.derived->intmod_sym_id != ISOCBINDING_PTR) + { + gfc_error ("%qs argument of %qs intrinsic at %L shall be " + "a scalar of type C_PTR", + arg0name, gfc_current_intrinsic, &arg0->where); + return false; + } + + if (!nchars) + { + gfc_error ("%qs argument of %qs intrinsic shall be present " + "when the %qs argument at %L is a C_PTR", + gfc_current_intrinsic_arg[2]->name, + gfc_current_intrinsic, arg0name, &arg0->where); + return false; + } + } + else + { + /* arg0 is a rank-one character array of kind c_char and character + length one. It is an intent in argument. Its actual argument + must be simply contiguous and have the target attribute. */ + if (arg0->rank != 1 + || arg0->ts.type != BT_CHARACTER + || arg0->ts.kind != gfc_default_character_kind + || get_ul_from_cst_cl (arg0->ts.u.cl) != 1) + { + gfc_error ("%qs argument of %qs intrinsic at %L shall be " + "a rank-one character array of kind C_CHAR and " + "character length one", + arg0name, gfc_current_intrinsic, &arg0->where); + return false; + } + if (!gfc_is_simply_contiguous (arg0, true, false)) + { + gfc_error ("%qs argument of %qs intrinsic at %L shall be " + "simply contiguous", + arg0name, gfc_current_intrinsic, &arg0->where); + return false; + } + if (!gfc_expr_attr (arg0).target) + { + gfc_error ("%qs argument of %qs intrinsic at %L shall have " + "the TARGET attribute", + arg0name, gfc_current_intrinsic, &arg0->where); + return false; + } + + /* If cstrarray is assumed-size, nchars must be present. */ + if (!nchars) + { + gfc_array_ref *ar = gfc_find_array_ref (arg0); + if (ar->as && ar->as->type == AS_ASSUMED_SIZE + && (ar->type == AR_FULL || ar->end[0] == nullptr)) + { + gfc_error ("%qs argument of %qs intrinsic shall be present " + "when the %qs argument at %L is assumed-size", + gfc_current_intrinsic_arg[2]->name, + gfc_current_intrinsic, arg0name, &arg0->where); + return false; + } + } + } + + /* fstrptr is a scalar deferred-length character pointer of kind c_char. + It is an intent out argument [...] */ + if (fstrptr->rank != 0 + || fstrptr->ts.type != BT_CHARACTER + || fstrptr->ts.kind != gfc_default_character_kind + || !fstrptr->ts.deferred + || !gfc_expr_attr (fstrptr).pointer) + { + gfc_error ("%qs argument of %qs intrinsic at %L shall be " + "a scalar deferred-length character pointer of kind C_CHAR", + gfc_current_intrinsic_arg[1]->name, gfc_current_intrinsic, + &fstrptr->where); + return false; + } + if (gfc_expr_attr (fstrptr).intent == INTENT_IN) + { + gfc_error ("%qs argument of %qs intrinsic at %L cannot be INTENT(IN)", + gfc_current_intrinsic_arg[1]->name, gfc_current_intrinsic, + &fstrptr->where); + return false; + } + + /* For the array form: nchars is an optional integer scalar with intent in. + If nchars is present, its value must be nonnegative and not greater + than the size of cstrarray. + For the scalar form: nchars is an integer scalar with intent in. Its + value must be nonnegative. */ + if (!nchars) + return true; + if (nchars->rank != 0 || nchars->ts.type != BT_INTEGER) + { + gfc_error ("%qs argument of %qs intrinsic at %L shall be " + "a scalar integer", + gfc_current_intrinsic_arg[2]->name, gfc_current_intrinsic, + &nchars->where); + return false; + } + if (nchars->expr_type != EXPR_CONSTANT) + return true; + if (!nonnegative_check (gfc_current_intrinsic_arg[2]->name, nchars)) + return false; + if (!arg0_is_scalar) + { + mpz_t asize; + if (gfc_array_size (arg0, &asize) + && mpz_cmp (nchars->value.integer, asize) > 0) + { + gfc_error ("%qs at %L must not be greater than the size of %qs", + gfc_current_intrinsic_arg[2]->name, &nchars->where, + arg0name); + return false; + } + } + + return true; +} + bool gfc_check_c_funloc (gfc_expr *x) { diff --git a/gcc/fortran/f95-lang.cc b/gcc/fortran/f95-lang.cc index 45aab34865f..1cdc83500a9 100644 --- a/gcc/fortran/f95-lang.cc +++ b/gcc/fortran/f95-lang.cc @@ -1036,6 +1036,11 @@ gfc_init_builtin_functions (void) gfc_define_builtin ("__builtin_realloc", ftype, BUILT_IN_REALLOC, "realloc", ATTR_NOTHROW_LEAF_LIST); + ftype = build_function_type_list (size_type_node, pchar_type_node, + size_type_node, NULL_TREE); + gfc_define_builtin ("__builtin_strnlen", ftype, BUILT_IN_STRNLEN, + "strnlen", ATTR_PURE_NOTHROW_LEAF_LIST); + /* Type-generic floating-point classification built-ins. */ ftype = build_function_type (integer_type_node, NULL_TREE); diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h index 6c45e9b1682..67b351347c4 100644 --- a/gcc/fortran/gfortran.h +++ b/gcc/fortran/gfortran.h @@ -501,6 +501,7 @@ enum gfc_isym_id GFC_ISYM_C_ASSOCIATED, GFC_ISYM_C_F_POINTER, GFC_ISYM_C_F_PROCPOINTER, + GFC_ISYM_C_F_STRPOINTER, GFC_ISYM_C_FUNLOC, GFC_ISYM_C_LOC, GFC_ISYM_C_SIZEOF, diff --git a/gcc/fortran/gfortran.texi b/gcc/fortran/gfortran.texi index 16553508a58..716e58cf1b3 100644 --- a/gcc/fortran/gfortran.texi +++ b/gcc/fortran/gfortran.texi @@ -3117,6 +3117,13 @@ example, we ignore the return value: end @end smallexample +Fortran 2023 added two new intrinsic functions for converting between +C and Fortran string representations: @code{f_c_string} transforms a +Fortran string into a C string by appending a null character, and +@code{c_f_strpointer} allows access to a null-terminated C string or +simply contiguous array of @code{c_char} as a Fortran deferred-length +character pointer. + The intrinsic procedures are described in @ref{Intrinsic Procedures}. @node Working with C Pointers diff --git a/gcc/fortran/intrinsic.cc b/gcc/fortran/intrinsic.cc index 6ffd7237468..1c97af087d5 100644 --- a/gcc/fortran/intrinsic.cc +++ b/gcc/fortran/intrinsic.cc @@ -3957,14 +3957,27 @@ add_subroutines (void) "fptr", BT_UNKNOWN, 0, REQUIRED, INTENT_OUT, "shape", BT_INTEGER, di, OPTIONAL, INTENT_IN, "lower", BT_INTEGER, di, OPTIONAL, INTENT_IN); - make_from_module(); + make_from_module (); add_sym_2s ("c_f_procpointer", GFC_ISYM_C_F_PROCPOINTER, CLASS_IMPURE, BT_UNKNOWN, 0, GFC_STD_F2003, gfc_check_c_f_procpointer, NULL, NULL, "cptr", BT_VOID, 0, REQUIRED, INTENT_IN, "fptr", BT_UNKNOWN, 0, REQUIRED, INTENT_OUT); - make_from_module(); + make_from_module (); + + /* This represents both forms of the intrinsic; the one with the + signature given here, and the one that accepts a scalar for the + first argument with name "cstrptr" instead of "cstrarray". + This is handled by special-casing in sort_actual as well as + in the check function. */ + add_sym_3s ("c_f_strpointer", GFC_ISYM_C_F_STRPOINTER, CLASS_IMPURE, + BT_UNKNOWN, 0, GFC_STD_F2023, gfc_check_c_f_strpointer, + NULL, NULL, + "cstrarray", BT_VOID, dc, REQUIRED, INTENT_IN, + "fstrptr", BT_UNKNOWN, dc, REQUIRED, INTENT_OUT, + "nchars", BT_INTEGER, di, OPTIONAL, INTENT_IN); + make_from_module (); /* Internal subroutine for emitting a runtime error. */ @@ -4516,6 +4529,7 @@ sort_actual (const char *name, gfc_actual_arglist **ap, { gfc_actual_arglist *actual, *a; gfc_intrinsic_arg *f; + bool is_c_f_strpointer = false; remove_nullargs (ap); actual = *ap; @@ -4536,7 +4550,9 @@ sort_actual (const char *name, gfc_actual_arglist **ap, return true; /* ALLOCATED has two mutually exclusive keywords, but only one - can be present at time and neither is optional. */ + can be present at time and neither is optional. Likewise + C_F_STRPOINTER, but since that subroutine has multiple arguments + it has to be handled in the keywords loop below. */ if (strcmp (name, "allocated") == 0) { if (!a) @@ -4605,9 +4621,32 @@ whoops: keywords: /* Associate the remaining actual arguments, all of which have to be keyword arguments. */ + is_c_f_strpointer = strcmp (name, "c_f_strpointer") == 0; for (; a; a = a->next) { int idx; + + /* Special case C_F_STRPOINTER. The first argument can either + be an array named "cstrarray" or a scalar named "cstrptr". */ + if (is_c_f_strpointer) + { + idx = 0; + if (strcmp (a->name, "cstrarray") == 0) + { + if (a->expr->rank != 0) + goto got_keyword; + gfc_error ("Array entity required at %L", &a->expr->where); + return false; + } + else if (strcmp (a->name, "cstrptr") == 0) + { + if (a->expr->rank == 0) + goto got_keyword; + gfc_error ("Scalar entity required at %L", &a->expr->where); + return false; + } + } + FOR_EACH_VEC_ELT (dummy_args, idx, f) if (strcmp (a->name, f->name) == 0) break; @@ -4623,10 +4662,11 @@ keywords: return false; } + got_keyword: if (ordered_actual_args[idx] != NULL) { gfc_error ("Argument %qs appears twice in call to %qs at %L", - f->name, name, where); + a->name, name, where); return false; } ordered_actual_args[idx] = a; diff --git a/gcc/fortran/intrinsic.h b/gcc/fortran/intrinsic.h index 0b520f03332..ad0c54f2959 100644 --- a/gcc/fortran/intrinsic.h +++ b/gcc/fortran/intrinsic.h @@ -168,6 +168,7 @@ bool gfc_check_sizeof (gfc_expr *); bool gfc_check_c_associated (gfc_expr *, gfc_expr *); bool gfc_check_c_f_pointer (gfc_expr *, gfc_expr *, gfc_expr *, gfc_expr *); bool gfc_check_c_f_procpointer (gfc_expr *, gfc_expr *); +bool gfc_check_c_f_strpointer (gfc_expr *, gfc_expr *, gfc_expr *); bool gfc_check_c_funloc (gfc_expr *); bool gfc_check_c_loc (gfc_expr *); bool gfc_check_c_sizeof (gfc_expr *); diff --git a/gcc/fortran/intrinsic.texi b/gcc/fortran/intrinsic.texi index f5a29606eb4..53014f47882 100644 --- a/gcc/fortran/intrinsic.texi +++ b/gcc/fortran/intrinsic.texi @@ -106,6 +106,7 @@ Some basic guidelines for editing this document: * @code{C_ASSOCIATED}: C_ASSOCIATED, Status of a C pointer * @code{C_F_POINTER}: C_F_POINTER, Convert C into Fortran pointer * @code{C_F_PROCPOINTER}: C_F_PROCPOINTER, Convert C into Fortran procedure pointer +* @code{C_F_STRPOINTER}: C_F_STRPOINTER, Convert C string to Fortran string pointer * @code{C_FUNLOC}: C_FUNLOC, Obtain the C address of a procedure * @code{C_LOC}: C_LOC, Obtain the C address of an object * @code{C_SIZEOF}: C_SIZEOF, Size in bytes of an expression @@ -3417,7 +3418,8 @@ Fortran 2003 and later, with @var{LOWER} argument Fortran 2023 and later @item @emph{See also}: @ref{C_LOC}, @* -@ref{C_F_PROCPOINTER} +@ref{C_F_PROCPOINTER}, @* +@ref{C_F_STRPOINTER} @end table @@ -3475,7 +3477,87 @@ Fortran 2003 and later @item @emph{See also}: @ref{C_LOC}, @* -@ref{C_F_POINTER} +@ref{C_F_POINTER}, @* +@ref{C_F_STRPOINTER} +@end table + + +@node C_F_STRPOINTER +@section @code{C_F_STRPOINTER} --- Convert C string into Fortran string pointer +@fnindex C_F_STRPOINTER +@cindex string, convert C to Fortran + +@table @asis +@item @emph{Synopsis}: +@multitable @columnfractions .80 +@item @code{CALL C_F_STRPOINTER(CSTRARRAY, FSTRPTR[, NCHARS])} +@item @code{CALL C_F_STRPOINTER(CSTRPTR, FSTRPTR, NCHARS)} +@end multitable + +@item @emph{Description}: +@code{C_F_STRPOINTER(CSTRARRAY, FSTRPTR[, NCHARS])} +pointer-associates the deferred-length character pointer +@code{FSTRPTR} with the initial substring of the simply contiguous +Fortran character array @code{STRARRAY}, up to the first null character, +the length @code{NCHARS} if specified, or the actual size of @code{CSTRARRAY}. + +@code{CALL C_F_STRPOINTER(CSTRPTR, FSTRPTR, NCHARS)} +pointer-associates the deferred-length array pointer @code{FSTRPTR} with the +initial substring of the continguous array of characters pointed to by +the C pointer @code{CSTRPTR}, up to the first null character or +length @code{NCHARS}. + +@item @emph{Class}: +Subroutine + +@item @emph{Arguments}: +@multitable @columnfractions .15 .70 +@item @var{CSTRARRAY} @tab Rank-one character array of kind @code{C_CHAR} +and character length one, which must be simply contiguous and have the +@code{TARGET} attribute. It is @code{INTENT(IN)}. +@item @var{CSTRPTR} @tab Scalar of the type @code{C_PTR}. It is +@code{INTENT(IN)}. +@item @var{FSTRPTR} @tab Scalar deferred-length character pointer of kind +@code{C_CHAR}. It is @code{INTENT(OUT)}. +@item @var{NCHARS} @tab (Optional) Integer scalar. It is @code{INTENT(IN)}. +This argument can only be omitted for the @code{CSTRARRAY} form of the +intrinsic, and only if @code{STRARRAY} is not assumed-size. +@end multitable + +@item @emph{Example}: +@smallexample +program main + + use iso_c_binding + implicit none + + character (kind=c_char, len=1), dimension(15), target :: a + type(c_ptr) :: p + character (len=:, kind=c_char), pointer :: fp1, fp2 + + a = [ character :: 'h', 'e', 'l', 'l', 'o', ' ', & + 'w', 'o', 'r', 'l', 'd', '!', & + ' ', ' ', ' '] + ! give array a terminating null so its C string length is 12. + a(13) = C_NULL_CHAR + + ! p is a C pointer to the the first character in the array + p = C_LOC (a(1)) + + ! Make both fp1 and fp2 point to a with Fortran string length 12. + call c_f_strpointer (p, fp1, 15) + call c_f_strpointer (a, fp2) +end program main + +@end smallexample + +@item @emph{Standard}: +Fortran 2023 and later. + +@item @emph{See also}: +@ref{C_LOC}, @* +@ref{C_F_POINTER}, @* +@ref{C_F_PROCPOINTER} @end table diff --git a/gcc/fortran/iso-c-binding.def b/gcc/fortran/iso-c-binding.def index c7a67229273..041fcb2ff52 100644 --- a/gcc/fortran/iso-c-binding.def +++ b/gcc/fortran/iso-c-binding.def @@ -190,6 +190,8 @@ NAMED_SUBROUTINE (ISOCBINDING_F_POINTER, "c_f_pointer", GFC_ISYM_C_F_POINTER, GFC_STD_F2003) NAMED_SUBROUTINE (ISOCBINDING_F_PROCPOINTER, "c_f_procpointer", GFC_ISYM_C_F_PROCPOINTER, GFC_STD_F2003) +NAMED_SUBROUTINE (ISOCBINDING_F_STRPOINTER, "c_f_strpointer", + GFC_ISYM_C_F_STRPOINTER, GFC_STD_F2023) NAMED_FUNCTION (ISOCBINDING_ASSOCIATED, "c_associated", GFC_ISYM_C_ASSOCIATED, GFC_STD_F2003) diff --git a/gcc/fortran/trans-intrinsic.cc b/gcc/fortran/trans-intrinsic.cc index 391e8061db7..a18a6436062 100644 --- a/gcc/fortran/trans-intrinsic.cc +++ b/gcc/fortran/trans-intrinsic.cc @@ -10267,6 +10267,122 @@ conv_isocbinding_subroutine (gfc_code *code) } +/* The following routine generates code for both forms of the intrinsic + subroutine C_F_STRPOINTER from the ISO_C_BINDING module. */ +static tree +conv_isocbinding_subroutine_strpointer (gfc_code *code) +{ + gfc_actual_arglist *arg = code->ext.actual; + gfc_expr *arg0 = arg->expr; + gfc_expr *fstrptr = arg->next->expr; + gfc_expr *nchars = arg->next->next->expr; + tree ptr; + tree size = NULL_TREE; + tree nc = NULL_TREE; + tree fstrptr_ptr, fstrptr_len; + stmtblock_t block; + gfc_init_block (&block); + gfc_se se0, se1, se2; + gfc_init_se (&se0, NULL); + gfc_init_se (&se1, NULL); + gfc_init_se (&se2, NULL); + + /* arg0 can either be a simply contiguous rank-one character array, + or a scalar of type c_ptr that points to a contiguous array. + In the first case nchars may be omitted and defaults to the size + of the array. */ + if (arg0->rank == 1) + { + gfc_array_ref *ar = gfc_find_array_ref (arg0); + if (ar->as && ar->as->type == AS_ASSUMED_SIZE + && (ar->type == AR_FULL || ar->end[0] == nullptr)) + /* No size available. */ + gfc_conv_array_parameter (&se0, arg0, true, NULL, NULL, NULL); + else + { + gfc_conv_array_parameter (&se0, arg0, true, NULL, NULL, &size); + gcc_assert (size); + } + ptr = se0.expr; + } + else if (arg0->rank == 0) + { + /* Scalar case. arg0 is a C pointer to the string, and the + nchars argument is required. */ + gfc_conv_expr (&se0, arg0); + ptr = se0.expr; + /* We already issued a diagnostic for this in parsing. */ + gcc_assert (nchars); + } + else + gcc_unreachable (); + + /* Translate the fortran array pointer argument. AFAICT the + representation here is that this returns the pointer location in + se1.expr and there is a separate decl for the length. + Of course none of this is properly documented.... :-( */ + gfc_conv_expr (&se1, fstrptr); + fstrptr_ptr = se1.expr; + gcc_assert (fstrptr->ts.u.cl && fstrptr->ts.u.cl->backend_decl); + fstrptr_len = fstrptr->ts.u.cl->backend_decl; + + /* Translate nchars, if provided. If we have both the array size + and nchars, take the minimum value. NC is the tree expr to hold + the value. */ + if (nchars) + { + gfc_conv_expr (&se2, nchars); + nc = se2.expr; + if (size) + nc = fold_build2_loc (input_location, MIN_EXPR, + TREE_TYPE (nc), nc, size); + /* Check for the case where an optional dummy parameter is + passed as the optional nchars argument. It's not supposed to + be omitted if we don't also have an array size; rather than + produce a run-time error, assume size 0. */ + if (nchars->expr_type == EXPR_VARIABLE + && nchars->symtree->n.sym->attr.dummy + && nchars->symtree->n.sym->attr.optional) + { + tree present = gfc_conv_expr_present (nchars->symtree->n.sym); + nc = build3_loc (input_location, COND_EXPR, + TREE_TYPE (nc), present, nc, + size ? size : build_int_cst (TREE_TYPE (nc), 0)); + } + } + else + { + gcc_assert (size); + nc = size; + } + + /* Collect argument side-effect statements. */ + gfc_add_block_to_block (&block, &se0.pre); + gfc_add_block_to_block (&block, &se1.pre); + gfc_add_block_to_block (&block, &se2.pre); + + /* Generate a call to builtin_strnlen to get the C string length + for the output fstrptr. */ + ptr = gfc_evaluate_now (ptr, &block); + size = build_call_expr_loc (input_location, + builtin_decl_explicit (BUILT_IN_STRNLEN), 2, + fold_convert (const_ptr_type_node, ptr), + fold_convert (size_type_node, nc)); + + /* Stuff the raw C char pointer PTR and actual length SIZE into fstrptr. */ + gfc_add_modify (&block, fstrptr_ptr, + fold_convert (TREE_TYPE (fstrptr_ptr), ptr)); + gfc_add_modify (&block, fstrptr_len, + fold_convert (gfc_charlen_type_node, size)); + + /* Collect argument cleanups. */ + gfc_add_block_to_block (&block, &se2.post); + gfc_add_block_to_block (&block, &se1.post); + gfc_add_block_to_block (&block, &se0.post); + + return gfc_finish_block (&block); +} + /* Save and restore floating-point state. */ tree @@ -13534,6 +13650,10 @@ gfc_conv_intrinsic_subroutine (gfc_code *code) res = conv_isocbinding_subroutine (code); break; + case GFC_ISYM_C_F_STRPOINTER: + res = conv_isocbinding_subroutine_strpointer (code); + break; + case GFC_ISYM_CAF_SEND: res = conv_caf_send_to_remote (code); break; diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-1.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-1.f90 new file mode 100644 index 00000000000..cbdfd84f6a5 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-1.f90 @@ -0,0 +1,30 @@ +! { dg-do run } +program test + + use iso_c_binding + implicit none + + character (kind=c_char, len=1), dimension(15), target :: a + type(c_ptr) :: p + character (len=:, kind=c_char), pointer :: fp1, fp2 + + a = [ character :: 'h', 'e', 'l', 'l', 'o', ' ', & + 'w', 'o', 'r', 'l', 'd', '!', & + ' ', ' ', ' '] + a(13) = C_NULL_CHAR + p = C_LOC (a(1)) + + ! check length is correct + call c_f_strpointer (p, fp1, 15) + if (len (fp1) .ne. 12) stop 100 + call c_f_strpointer (a, fp2) + if (len (fp2) .ne. 12) stop 101 + + ! check that fp1 and fp2 both point to the contents of array a. + if (fp1(1:1) .ne. 'h') stop 200 + if (fp2(1:1) .ne. 'h') stop 201 + a(1) = 'H' + if (fp1(1:1) .ne. 'H') stop 202 + if (fp2(1:1) .ne. 'H') stop 203 + +end program diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-10.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-10.f90 new file mode 100644 index 00000000000..ac18336d240 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-10.f90 @@ -0,0 +1,39 @@ +! { dg-do run } +! Check handling of optional dummy nchars argument to c_f_strpointer +! when its corresponding actual argument is an optional dummy that is +! not present, and the C string argument has no size information (C +! pointer or assumed-size array). +! The Fortran spec says this is not allowed, but it's a runtime error +! and the gfortran implementation assumes size 0 in this case rather than +! diagnosing it. + +program test + + use iso_c_binding + implicit none + + character (kind=c_char, len=1), dimension(15), target :: a + + a = [ character :: 'h', 'e', 'l', 'l', 'o', ' ', & + 'w', 'o', 'r', 'l', 'd', '!', & + ' ', ' ', ' '] + a(13) = C_NULL_CHAR + call doit (a, 12, 15) + call doit (a, 0) +contains + +subroutine doit (aa, n, m) + character (kind=c_char, len=1), dimension(*), target, intent(inout) :: aa + integer, intent(in) :: n + integer, intent(in), optional :: m + character (len=:, kind=c_char), pointer :: fp + type(c_ptr) :: p + + p = C_LOC (aa(1)) + call c_f_strpointer (p, fp, m) + if (len(fp) .ne. n) stop 100 + call c_f_strpointer (aa, fp, m) + if (len(fp) .ne. n) stop 200 +end subroutine + +end program diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-2.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-2.f90 new file mode 100644 index 00000000000..b4a44db68c5 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-2.f90 @@ -0,0 +1,33 @@ +! { dg-do run } +program test + + use iso_c_binding + implicit none + + character (kind=c_char, len=1), dimension(15), target :: a + + a = [ character :: 'h', 'e', 'l', 'l', 'o', ' ', & + 'w', 'o', 'r', 'l', 'd', '!', & + ' ', ' ', ' '] + a(13) = C_NULL_CHAR + call doit (a, 12, 15) + call doit (a(7:), 6, 9) + +contains + +subroutine doit (aa, n, m) + character (kind=c_char, len=1), dimension(*), target, intent(inout) :: aa + integer, intent(in) :: n, m + character (len=:, kind=c_char), pointer :: fp + + ! check length is correct + call c_f_strpointer (aa, fp, m) + if (len (fp) .ne. n) stop 100 + + ! check that fp points to the contents of array aa. + if (fp(1:1) .ne. aa(1)) stop 101 + aa(1) = '?' + if (fp(1:1) .ne. '?') stop 102 +end subroutine + +end program diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-3.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-3.f90 new file mode 100644 index 00000000000..958145a3585 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-3.f90 @@ -0,0 +1,37 @@ +! { dg-do compile } +! Test that missing size argument is rejected. + +program test + + use iso_c_binding + implicit none + + character (kind=c_char, len=1), dimension(15), target :: a + + a = [ character :: 'h', 'e', 'l', 'l', 'o', ' ', & + 'w', 'o', 'r', 'l', 'd', '!', & + ' ', ' ', ' '] + a(13) = C_NULL_CHAR + call doit (a, 12) + +contains + +subroutine doit (aa, n) + character (kind=c_char, len=1), dimension(*), target, intent(inout) :: aa + integer, intent(in) :: n + character (len=:, kind=c_char), pointer :: fp + type(c_ptr) :: p + + p = C_LOC (aa(1)) + call c_f_strpointer (p, fp) ! { dg-error ".nchars. argument of .c_f_strpointer. intrinsic shall be present when the .cstrptr. argument at .1. is a C_PTR" } + call c_f_strpointer (aa, fp) ! { dg-error ".nchars. argument of .c_f_strpointer. intrinsic shall be present when the .cstrarray. argument at .1. is assumed-size" } + + ! These are all OK, they are known-size array sections of the assumed-size + ! array aa. + call c_f_strpointer (aa(:10), fp) + call c_f_strpointer (aa(:huge(1)), fp) + call c_f_strpointer (aa(5:10), fp) + +end subroutine + +end program diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-4.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-4.f90 new file mode 100644 index 00000000000..e6268172f98 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-4.f90 @@ -0,0 +1,18 @@ +! { dg-do compile } +! Test that non-contiguous array section argument is rejected. +program test + + use iso_c_binding + implicit none + + character (kind=c_char, len=1), dimension(15), target :: a + character (len=:, kind=c_char), pointer :: fp + + a = [ character :: 'h', 'e', 'l', 'l', 'o', ' ', & + 'w', 'o', 'r', 'l', 'd', '!', & + ' ', ' ', ' '] + a(13) = C_NULL_CHAR + + call c_f_strpointer (a(1:13), fp) + call c_f_strpointer (a(1:13:2), fp) ! { dg-error ".cstrarray. argument of .c_f_strpointer. intrinsic at .1. shall be simply contiguous" } +end program diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-5.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-5.f90 new file mode 100644 index 00000000000..d9de7bf830b --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-5.f90 @@ -0,0 +1,19 @@ +! { dg-do compile } +! Test that multi-dimensional array arguments are rejected. +program test + + use iso_c_binding + implicit none + + character (kind=c_char, len=1), target :: a(15), b(3,5) + character (len=:, kind=c_char), pointer :: fp + + a = [ character :: 'h', 'e', 'l', 'l', 'o', ' ', & + 'w', 'o', 'r', 'l', 'd', '!', & + ' ', ' ', ' '] + a(13) = C_NULL_CHAR + b = reshape (a, shape (b)) + + call c_f_strpointer (a, fp) + call c_f_strpointer (b, fp) ! { dg-error ".cstrarray. argument of .c_f_strpointer. intrinsic at .1. shall be a rank-one character array of kind C_CHAR and character length one" } +end program diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-6.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-6.f90 new file mode 100644 index 00000000000..a90526af284 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-6.f90 @@ -0,0 +1,20 @@ +! { dg-do compile } +! Test that lack of target attribute and wrong-length character array are +! rejected. +program test + + use iso_c_binding + implicit none + + character (kind=c_char, len=1), dimension(15) :: a + character (kind=c_char, len=4), dimension(15), target :: b + character (len=:, kind=c_char), pointer :: fp + + a = [ character :: 'h', 'e', 'l', 'l', 'o', ' ', & + 'w', 'o', 'r', 'l', 'd', '!', & + ' ', ' ', ' '] + a(13) = C_NULL_CHAR + b = a + call c_f_strpointer (a, fp) ! { dg-error ".cstrarray. argument of .c_f_strpointer. intrinsic at .1. shall have the TARGET attribute" } + call c_f_strpointer (b, fp) ! { dg-error ".cstrarray. argument of .c_f_strpointer. intrinsic at .1. shall be a rank-one character array of kind C_CHAR and character length one" } +end program diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-7.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-7.f90 new file mode 100644 index 00000000000..b9531fc4c09 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-7.f90 @@ -0,0 +1,50 @@ +! { dg-do run } +! Test that c_f_strpointer works with strings/arrays of known length but +! no null terminator character. + +module mod +use iso_c_binding +implicit none(type, external) +type t + type(c_ptr) :: cptr + character(1, c_char) :: carr(10) +end type t +contains +subroutine sub(x, y) + type(t), target :: x + character, pointer, contiguous, intent(in) :: y(:) + character(:), pointer :: fstr + + call c_f_strpointer (x%cptr, fstr, 10) + print *, len(fstr), fstr + if (len(fstr) /= 10 .or. fstr /= "1234567890") stop 1 + + call c_f_strpointer (x%carr, fstr) + print *, len(fstr), fstr + if (len(fstr) /= 10 .or. fstr /= "abcdefghij") stop 2 + + call c_f_strpointer (y, fstr) + if (len(fstr) /= 10 .or. fstr /= "abcdefghij") stop 3 + + call c_f_strpointer (y(5:), fstr) + if (len(fstr) /= 6 .or. fstr /= "efghij") stop 4 + + call c_f_strpointer (x%carr(2:4), fstr) + if (len(fstr) /= 3 .or. fstr /= "bcd") stop 5 +end +end module + +use mod +implicit none +character(10,c_char), target :: str10 +character(1,c_char), target :: arr10(10) + +type(t) :: arg + +str10 = '1234567890' +arr10 = ['a','b','c','d','e','f','g','h','i', 'j'] + +arg%cptr = c_loc(str10) +arg%carr = arr10 +call sub(arg, arr10) +end diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-8.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-8.f90 new file mode 100644 index 00000000000..b8e8abe501f --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-8.f90 @@ -0,0 +1,11 @@ +! { dg-do compile } +! Test that c_f_strpointer rejects assumed-rank array argument. + +subroutine sub(y) + use iso_c_binding + implicit none (type, external) + character, pointer, contiguous, intent(in) :: y(..) + character(:), pointer :: fstr + + call c_f_strpointer (y, fstr, 10) ! { dg-error "Assumed-rank argument at .1. is only permitted as actual argument to intrinsic inquiry functions or to RESHAPE." } +end diff --git a/gcc/testsuite/gfortran.dg/c_f_strpointer-9.f90 b/gcc/testsuite/gfortran.dg/c_f_strpointer-9.f90 new file mode 100644 index 00000000000..0e7043253b0 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/c_f_strpointer-9.f90 @@ -0,0 +1,34 @@ +! { dg-do compile } +! Test that problems with the fstrptr argument are diagnosed. + +program test + + use iso_c_binding + implicit none + + character (kind=c_char, len=1), dimension(15), target :: a + character (len=:, kind=c_char), pointer :: fp + + a = [ character :: 'h', 'e', 'l', 'l', 'o', ' ', & + 'w', 'o', 'r', 'l', 'd', '!', & + ' ', ' ', ' '] + a(13) = C_NULL_CHAR + call doit (a, 12, fp) + +contains + +subroutine doit (aa, n, fp1) + character (kind=c_char, len=1), dimension(*), target, intent(inout) :: aa + integer, intent(in) :: n + character (len=:, kind=c_char), pointer, intent(in) :: fp1 + character (len=42, kind=c_char), pointer :: fp2 + character (len=:, kind=c_char), allocatable :: fp3 + type(c_ptr) :: p + + p = C_LOC (aa(1)) + call c_f_strpointer (p, fp1, n) ! { dg-error ".fstrptr. argument of .c_f_strpointer. intrinsic at .1. cannot be INTENT.IN." } + call c_f_strpointer (p, fp2, n) ! { dg-error ".fstrptr. argument of .c_f_strpointer. intrinsic at .1. shall be a scalar deferred-length character pointer of kind C_CHAR" } + call c_f_strpointer (p, fp3, n) ! { dg-error ".fstrptr. argument of .c_f_strpointer. intrinsic at .1. shall be a scalar deferred-length character pointer of kind C_CHAR" } +end subroutine + +end program diff --git a/gcc/testsuite/gfortran.dg/pr108961.f90 b/gcc/testsuite/gfortran.dg/pr108961.f90 index 3e6c9df48bb..30eb502cbc4 100644 --- a/gcc/testsuite/gfortran.dg/pr108961.f90 +++ b/gcc/testsuite/gfortran.dg/pr108961.f90 @@ -5,7 +5,7 @@ module associate_ptr use iso_c_binding contains - subroutine c_f_strpointer(cptr, ptr2) + subroutine my_c_f_strpointer(cptr, ptr2) type(c_ptr), target, intent(in) :: cptr character(kind=c_char,len=4), pointer :: ptr1 character(kind=c_char,len=:), pointer, intent(out) :: ptr2 @@ -21,6 +21,6 @@ program test_associate_ptr character(kind=c_char,len=:), pointer :: ptr2 char_array = ['a', 'b', 'c', 'd', c_null_char, 'e', 'f'] ! The first argument was providing a constant hidden string length => segfault - call c_f_strpointer(c_loc(char_array), ptr2) + call my_c_f_strpointer(c_loc(char_array), ptr2) if (ptr2 .ne. 'abcd') stop 2 end program -- 2.39.5
0002-Fortran-f_c_string-intrinsic-improvements.patch
(text/x-patch, 19.4 KB)
From 656d97bbc4b0f4614f1f074ab3ca7c425fe569d2 Mon Sep 17 00:00:00 2001 From: Sandra Loosemore <[email protected]> Date: Thu, 28 May 2026 22:33:33 +0000 Subject: [PATCH 2/2] Fortran: f_c_string intrinsic improvements The existing implementation of f_c_string is quite inefficient, doing either 2 or 3 allocations and copies of the input string prefix. This rewrite adds folding for constant string arguments and handles other cases with a single allocation and copy. This patch also adds the missing documentation for this intrinsic to the gfortran manual. gcc/fortran/ChangeLog * intrinsic.texi (F_C_STRING): New section. * trans-intrinsic.cc (conv_trim): Delete. (conv_isocbinding_function): Rewrite the F_C_STRING case. gcc/testsuite/ChangeLog * gfortran.dg/f_c_string3.f90: New. * gfortran.dg/f_c_string4.f90: New. * gfortran.dg/f_c_string5.f90: New. --- gcc/fortran/intrinsic.texi | 59 +++- gcc/fortran/trans-intrinsic.cc | 328 ++++++++++++---------- gcc/testsuite/gfortran.dg/f_c_string3.f90 | 53 ++++ gcc/testsuite/gfortran.dg/f_c_string4.f90 | 26 ++ gcc/testsuite/gfortran.dg/f_c_string5.f90 | 20 ++ 5 files changed, 329 insertions(+), 157 deletions(-) create mode 100644 gcc/testsuite/gfortran.dg/f_c_string3.f90 create mode 100644 gcc/testsuite/gfortran.dg/f_c_string4.f90 create mode 100644 gcc/testsuite/gfortran.dg/f_c_string5.f90 diff --git a/gcc/fortran/intrinsic.texi b/gcc/fortran/intrinsic.texi index 53014f47882..64309fd852c 100644 --- a/gcc/fortran/intrinsic.texi +++ b/gcc/fortran/intrinsic.texi @@ -159,6 +159,7 @@ Some basic guidelines for editing this document: * @code{EXP}: EXP, Exponential function * @code{EXPONENT}: EXPONENT, Exponent function * @code{EXTENDS_TYPE_OF}: EXTENDS_TYPE_OF, Query dynamic type for extension +* @code{F_C_STRING}: F_C_STRING, Convert character scalar to C string * @code{FDATE}: FDATE, Subroutine (or function) to get the current time as a string * @code{FGET}: FGET, Read a single character in stream mode from stdin * @code{FGETC}: FGETC, Read a single character in stream mode @@ -3557,7 +3558,8 @@ Fortran 2023 and later. @item @emph{See also}: @ref{C_LOC}, @* @ref{C_F_POINTER}, @* -@ref{C_F_PROCPOINTER} +@ref{C_F_PROCPOINTER}, @* +@ref{F_C_STRING} @end table @@ -6394,6 +6396,61 @@ Fortran 2003 and later +@node F_C_STRING +@section @code{F_C_STRING} --- Convert Fortran character scalar to C string +@fnindex F_C_STRING +@cindex string, convert Fortran to C + +@table @asis +@item @emph{Synopsis}: +@code{RESULT = F_C_STRING(STRING[, ASIS])} + +@item @emph{Description}: +The @code{F_C_STRING} intrinsic is equivalent to @code{STRING//C_NULL_CHAR} +if the @code{ASIS} argument is present and true, and to +@code{TRIM(STRING)//C_NULL_CHAR} otherwise. + +@item @emph{Class}: +Transformational function + +@item @emph{Arguments}: +@multitable @columnfractions .15 .70 +@item @var{STRING} @tab A character scalar of kind @code{C_CHAR}. +@item @var{ASIS} @tab An optional logical scalar. +@end multitable + +@item @emph{Return value}: +The result is a null-terminated character scalar of the same type and kind +as @code{STRING}, suitable for passing to a C function that accepts a +@code{char *} argument. + +@item @emph{Example}: +@smallexample +program main + use iso_c_binding, only: f_c_string, c_char + implicit none (external, type) + character(:, c_char), allocatable :: s1, s2, s3 + + ! s1 is null-terminated "hello, world! " + s1 = f_c_string ("hello, world! ", .true.) + + ! s2 is null-terminated "hello, world!" + s2 = f_c_string ("hello, world! ", .false.) + + ! s3 is null-terminated "hello, world!" (same as s2 example) + s3 = f_c_string ("hello, world! ") +end program main + +@end smallexample + +@item @emph{Standard}: +Fortran 2023 and later. + +@item @emph{See also}: +@ref{C_F_STRPOINTER} +@end table + + @node FDATE @section @code{FDATE} --- Get the current time as a string @fnindex FDATE diff --git a/gcc/fortran/trans-intrinsic.cc b/gcc/fortran/trans-intrinsic.cc index a18a6436062..fdb9ddb52ea 100644 --- a/gcc/fortran/trans-intrinsic.cc +++ b/gcc/fortran/trans-intrinsic.cc @@ -9840,37 +9840,6 @@ gfc_conv_intrinsic_loc (gfc_se * se, gfc_expr * expr) se->expr = temp_var; } - -/* Specialized trim for f_c_string. */ - -static void -conv_trim (gfc_se *tse, gfc_se *str) -{ - tree cond, plen, pvar, tlen, ttmp, tvar; - - tlen = gfc_create_var (gfc_charlen_type_node, "tlen"); - plen = gfc_build_addr_expr (NULL_TREE, tlen); - - tvar = gfc_create_var (pchar_type_node, "tstr"); - pvar = gfc_build_addr_expr (ppvoid_type_node, tvar); - - ttmp = build_call_expr_loc (input_location, gfor_fndecl_string_trim, 4, - plen, pvar, str->string_length, str->expr); - - gfc_add_expr_to_block (&tse->pre, ttmp); - - /* Free the temporary afterwards, if necessary. */ - cond = fold_build2_loc (input_location, GT_EXPR, logical_type_node, - tlen, build_int_cst (TREE_TYPE (tlen), 0)); - ttmp = gfc_call_free (tvar); - ttmp = build3_v (COND_EXPR, cond, ttmp, build_empty_stmt (input_location)); - gfc_add_expr_to_block (&tse->post, ttmp); - - tse->expr = tvar; - tse->string_length = tlen; -} - - /* The following routine generates code for the intrinsic functions from the ISO_C_BINDING module: C_LOC, C_FUNLOC, C_ASSOCIATED, and F_C_STRING. */ @@ -9965,141 +9934,188 @@ conv_isocbinding_function (gfc_se *se, gfc_expr *expr) f_c_string(string, .false.) -> trim(string) // c_null_char f_c_string(string, .true.) -> string // c_null_char */ - gfc_se lse, rse, tse; - tree len, tmp, var; gfc_expr *string = arg->expr; gfc_expr *asis = arg->next->expr; - gfc_expr *cnc; + bool need_asis = false, need_trim = false; + gfc_se asis_se; - /* Convert string. */ - gfc_init_se (&lse, se); - gfc_conv_expr (&lse, string); - gfc_conv_string_parameter (&lse); - - /* Create a string for C_NULL_CHAR and convert it. */ - cnc = gfc_get_character_expr (gfc_default_character_kind, - &string->where, "\0", 1); - gfc_init_se (&rse, se); - gfc_conv_expr (&rse, cnc); - gfc_conv_string_parameter (&rse); - gfc_free_expr (cnc); - -#ifdef cnode -#undef cnode -#endif -#define cnode gfc_charlen_type_node - if (asis) + if (!asis) { - stmtblock_t block; - gfc_se asis_se, vse; - tree elen, evar, tlen, tvar; - tree else_branch, then_branch; - - elen = evar = tlen = tvar = NULL_TREE; - - /* f_c_string(string, .true.) -> string // c_null_char */ - - gfc_init_block (&block); - - gfc_add_block_to_block (&block, &lse.pre); - gfc_add_block_to_block (&block, &rse.pre); - - tlen = fold_build2_loc (input_location, PLUS_EXPR, cnode, - fold_convert (cnode, lse.string_length), - fold_convert (cnode, rse.string_length)); - - gfc_init_se (&vse, se); - tvar = gfc_conv_string_tmp (&vse, pchar_type_node, tlen); - gfc_add_block_to_block (&block, &vse.pre); - - tmp = build_call_expr_loc (input_location, gfor_fndecl_concat_string, - 6, tlen, tvar, - lse.string_length, lse.expr, - rse.string_length, rse.expr); - gfc_add_expr_to_block (&block, tmp); - - then_branch = gfc_finish_block (&block); - - /* f_c_string(string, .false.) = trim(string) // c_null_char */ - - gfc_init_block (&block); - - gfc_init_se (&tse, se); - conv_trim (&tse, &lse); - gfc_add_block_to_block (&block, &tse.pre); - gfc_add_block_to_block (&block, &rse.pre); - - elen = fold_build2_loc (input_location, PLUS_EXPR, cnode, - fold_convert (cnode, tse.string_length), - fold_convert (cnode, rse.string_length)); - - gfc_init_se (&vse, se); - evar = gfc_conv_string_tmp (&vse, pchar_type_node, elen); - gfc_add_block_to_block (&block, &vse.pre); - - tmp = build_call_expr_loc (input_location, gfor_fndecl_concat_string, - 6, elen, evar, - tse.string_length, tse.expr, - rse.string_length, rse.expr); - gfc_add_expr_to_block (&block, tmp); - - else_branch = gfc_finish_block (&block); - - gfc_init_se (&asis_se, se); - gfc_conv_expr (&asis_se, asis); - if (asis->expr_type == EXPR_VARIABLE - && asis->symtree->n.sym->attr.dummy - && asis->symtree->n.sym->attr.optional) - { - tree present = gfc_conv_expr_present (asis->symtree->n.sym); - asis_se.expr = build3_loc (input_location, COND_EXPR, - logical_type_node, present, - asis_se.expr, - build_int_cst (logical_type_node, 0)); - } - gfc_add_block_to_block (&se->pre, &asis_se.pre); - tmp = fold_build3_loc (input_location, COND_EXPR, void_type_node, - asis_se.expr, then_branch, else_branch); - - gfc_add_expr_to_block (&se->pre, tmp); - - var = fold_build3_loc (input_location, COND_EXPR, pchar_type_node, - asis_se.expr, tvar, evar); - gfc_add_expr_to_block (&se->pre, var); - - len = fold_build3_loc (input_location, COND_EXPR, cnode, - asis_se.expr, tlen, elen); - gfc_add_expr_to_block (&se->pre, len); + need_trim = true; + need_asis = false; + } + else if (asis->expr_type == EXPR_CONSTANT) + { + need_asis = asis->value.logical; + need_trim = !need_asis; } else { - /* f_c_string(string) = trim(string) // c_null_char */ - - gfc_add_block_to_block (&se->pre, &lse.pre); - gfc_add_block_to_block (&se->pre, &rse.pre); - - gfc_init_se (&tse, se); - conv_trim (&tse, &lse); - gfc_add_block_to_block (&se->pre, &tse.pre); - gfc_add_block_to_block (&se->post, &tse.post); - - len = fold_build2_loc (input_location, PLUS_EXPR, cnode, - fold_convert (cnode, tse.string_length), - fold_convert (cnode, rse.string_length)); - - var = gfc_conv_string_tmp (se, pchar_type_node, len); - - tmp = build_call_expr_loc (input_location, gfor_fndecl_concat_string, - 6, len, var, - tse.string_length, tse.expr, - rse.string_length, rse.expr); - gfc_add_expr_to_block (&se->pre, tmp); + /* A conditional expression is needed. */ + need_asis = true; + need_trim = true; + gfc_init_se (&asis_se, se); + gfc_conv_expr (&asis_se, asis); + if (asis->expr_type == EXPR_VARIABLE + && asis->symtree->n.sym->attr.dummy + && asis->symtree->n.sym->attr.optional) + { + tree present = gfc_conv_expr_present (asis->symtree->n.sym); + asis_se.expr + = build3_loc (input_location, COND_EXPR, + logical_type_node, present, + asis_se.expr, logical_false_node); + } + gfc_make_safe_expr (&asis_se); } - se->expr = var; - se->string_length = len; + /* Handle the case of a constant string argument first. */ + if (string->expr_type == EXPR_CONSTANT) + { + /* Output for the asis "then" case goes tlen/tstr, and the + trimmed case in elen/estr. */ + tree elen, estr, tlen, tstr; + elen = estr = tlen = tstr = NULL_TREE; -#undef cnode + gfc_char_t *orig_string = string->value.character.string; + gfc_charlen_t orig_len = string->value.character.length; + gfc_charlen_t n; + gfc_char_t *buf + = (gfc_char_t *) alloca ((orig_len + 1) * sizeof (gfc_char_t)); + memcpy (buf, orig_string, orig_len * sizeof (gfc_char_t)); + buf[orig_len] = '\0'; + int kind = gfc_default_character_kind; + gcc_assert (string->ts.kind == kind); + + /* Build the new string constant(s). */ + if (need_asis) + { + tstr = gfc_build_wide_string_const (kind, orig_len + 1, buf); + tlen = TYPE_MAX_VALUE (TYPE_DOMAIN (TREE_TYPE (tstr))); + if (!need_trim) + { + se->expr = tstr; + se->string_length = tlen; + return; + } + } + if (need_trim) + { + for (n = orig_len; n; n--) + if (buf[n - 1] != ' ') + break; + buf[n] = '\0'; + if (need_asis && n == orig_len) + { + /* Special case; trimming is a no-op. Add side-effects + from the condition and then just return the string + without a conditional. */ + gfc_add_block_to_block (&se->pre, &asis_se.pre); + se->expr = tstr; + se->string_length = tlen; + return; + } + else + { + estr = gfc_build_wide_string_const (kind, n + 1, buf); + elen = TYPE_MAX_VALUE (TYPE_DOMAIN (TREE_TYPE (estr))); + } + if (!need_asis) + { + se->expr = estr; + se->string_length = elen; + return; + } + } + gcc_assert (need_asis && need_trim); + gfc_add_block_to_block (&se->pre, &asis_se.pre); + se->expr + = fold_build3_loc (input_location, COND_EXPR, + pchar_type_node, asis_se.expr, + tstr, estr); + se->string_length + = fold_build3_loc (input_location, COND_EXPR, + gfc_charlen_type_node, asis_se.expr, + tlen, elen); + return; + } + else + /* We have to generate code to do the string transformation(s) at + runtime. */ + { + tree tmp; + + /* Convert input string. */ + gfc_se sse; + gfc_init_se (&sse, se); + gfc_conv_expr (&sse, string); + gfc_conv_string_parameter (&sse); + gfc_make_safe_expr (&sse); + gfc_add_block_to_block (&se->pre, &sse.pre); + + /* Use a temporary for the (possibly trimmed) string length. */ + tree lenvar = gfc_create_var (gfc_charlen_type_node, NULL); + gfc_add_modify (&se->pre, lenvar, sse.string_length); + + /* Build the expression for a call to LEN_TRIM if we may need + to trim the string. If it's conditional, handle that too. */ + if (need_trim) + { + tree trimlen + = build_call_expr_loc (input_location, + gfor_fndecl_string_len_trim, 2, + lenvar, sse.expr); + if (need_asis) + { + gfc_add_block_to_block (&se->pre, &asis_se.pre); + tmp = fold_build3_loc (input_location, COND_EXPR, + gfc_charlen_type_node, asis_se.expr, + lenvar, trimlen); + gfc_add_modify (&se->pre, lenvar, tmp); + } + else + gfc_add_modify (&se->pre, lenvar, trimlen); + } + + /* Allocate a new string newvar that is lenvar+1 bytes long. + memcpy the first lenvar bytes from the input string, and + add a null character. Note that lenvar, the length of + the (trimmed) original string, has type gfc_charlen_type_node, + but newlen is size_type_node. */ + tree string_type_node = build_pointer_type (char_type_node); + tree newvar = gfc_create_var (string_type_node, NULL); + tree newlen = fold_build2_loc (input_location, PLUS_EXPR, + size_type_node, + fold_convert (size_type_node, + lenvar), + size_one_node); + gfc_add_modify (&se->pre, newvar, + gfc_call_malloc (&se->pre, string_type_node, + newlen)); + tmp = build_call_expr_loc (input_location, + builtin_decl_explicit (BUILT_IN_MEMCPY), + 3, + fold_convert (pvoid_type_node, newvar), + fold_convert (pvoid_type_node, sse.expr), + fold_convert (size_type_node, lenvar)); + gfc_add_expr_to_block (&se->pre, tmp); + tmp = fold_build2_loc (input_location, POINTER_PLUS_EXPR, + string_type_node, newvar, + fold_convert (size_type_node, lenvar)); + tmp = fold_build1_loc (input_location, INDIRECT_REF, + char_type_node, tmp); + gfc_add_modify (&se->pre, tmp, + fold_convert (char_type_node, integer_zero_node)); + + /* Remember to free the string later. */ + tmp = gfc_call_free (newvar); + gfc_add_expr_to_block (&se->post, tmp); + + /* Return the result. */ + se->expr = newvar; + se->string_length = fold_convert (gfc_charlen_type_node, newlen); + return; + } } else gcc_unreachable (); diff --git a/gcc/testsuite/gfortran.dg/f_c_string3.f90 b/gcc/testsuite/gfortran.dg/f_c_string3.f90 new file mode 100644 index 00000000000..3e9d4a79d3b --- /dev/null +++ b/gcc/testsuite/gfortran.dg/f_c_string3.f90 @@ -0,0 +1,53 @@ +! Test f_c_string cases that can be fully constant-folded +! { dg-do compile } +! { dg-additional-options "-fdump-tree-original" } + +use iso_c_binding, only: f_c_string, c_char +implicit none (external, type) +character(*, c_char), parameter :: str1 = "blah1" +character(*, c_char), parameter :: str2 = "blah2" +character(*, c_char), parameter :: str3 = "blah3" +character(*, c_char), parameter :: str4 = "blah4 " +character(*, c_char), parameter :: str5 = "blah5 " +character(*, c_char), parameter :: str6 = "blah6 " +external foo + +call foo(f_c_string("hello world1", asis=.true.)) +! { dg-final { scan-tree-dump-times "hello world1.\[^\\n\\r\]*, 13" 1 "original" } } + +call foo(f_c_string("hello world2", asis=.false.)) +! { dg-final { scan-tree-dump-times "hello world2.\[^\\n\\r\]*, 13" 1 "original" } } + +call foo(f_c_string("hello world3")) +! { dg-final { scan-tree-dump-times "hello world3.\[^\\n\\r\]*, 13" 1 "original" } } + +call foo(f_c_string("hello1 ", asis=.true.)) +! { dg-final { scan-tree-dump-times "hello1 .\[^\\n\\r\]*, 8" 1 "original" } } + +call foo(f_c_string("hello2 ", asis=.false.)) +! { dg-final { scan-tree-dump-times "hello2.\[^\\n\\r\]*, 7" 1 "original" } } + +call foo(f_c_string("hello3 ")) +! { dg-final { scan-tree-dump-times "hello3.\[^\\n\\r\]*, 7" 1 "original" } } + +call foo(f_c_string(str1, asis=.true.)) +! { dg-final { scan-tree-dump-times "blah1.\[^\\n\\r\]*, 6" 1 "original" } } + +call foo(f_c_string(str2, asis=.false.)) +! { dg-final { scan-tree-dump-times "blah2.\[^\\n\\r\]*, 6" 1 "original" } } + +call foo(f_c_string(str3)) +! { dg-final { scan-tree-dump-times "blah3.\[^\\n\\r\]*, 6" 1 "original" } } + +call foo(f_c_string(str4, asis=.true.)) +! { dg-final { scan-tree-dump-times "blah4 .\[^\\n\\r\]*, 9" 1 "original" } } + +call foo(f_c_string(str5, asis=.false.)) +! { dg-final { scan-tree-dump-times "blah5.\[^\\n\\r\]*, 6" 1 "original" } } + +call foo(f_c_string(str6)) +! { dg-final { scan-tree-dump-times "blah6.\[^\\n\\r\]*, 6" 1 "original" } } + +end + + diff --git a/gcc/testsuite/gfortran.dg/f_c_string4.f90 b/gcc/testsuite/gfortran.dg/f_c_string4.f90 new file mode 100644 index 00000000000..d38e16f0268 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/f_c_string4.f90 @@ -0,0 +1,26 @@ +! Test f_c_string cases with constant strings but that need a conditional. +! { dg-do compile } +! { dg-additional-options "-fdump-tree-original" } + +subroutine doit (x) + +use iso_c_binding, only: f_c_string, c_char +implicit none (external, type) +logical :: x +character(*, c_char), parameter :: str1 = "blah1" +character(*, c_char), parameter :: str2 = "blah2 " +external foo + +call foo(f_c_string("hello world1", asis=x)) +! { dg-final { scan-tree-dump-times "hello world1.\[^\\n\\r\]*, 13" 1 "original" } } + +call foo(f_c_string("hello1 ", asis=x)) +! { dg-final { scan-tree-dump-times "hello1 .\[^\\n\\r\]* 8 : 7" 1 "original" } } + +call foo(f_c_string(str1, asis=x)) +! { dg-final { scan-tree-dump-times "blah1.\[^\\n\\r\]*, 6" 1 "original" } } + +call foo(f_c_string(str2, asis=x)) +! { dg-final { scan-tree-dump-times "blah2 .\[^\\n\\r\]* 9 : 6" 1 "original" } } + +end subroutine diff --git a/gcc/testsuite/gfortran.dg/f_c_string5.f90 b/gcc/testsuite/gfortran.dg/f_c_string5.f90 new file mode 100644 index 00000000000..25c5115f214 --- /dev/null +++ b/gcc/testsuite/gfortran.dg/f_c_string5.f90 @@ -0,0 +1,20 @@ +! { dg-do run } +! This is the example from the manual. + +program main + use iso_c_binding, only: f_c_string, c_char + implicit none (external, type) + character(:, c_char), allocatable :: s1, s2, s3 + + ! s1 is null-terminated "hello, world! " + s1 = f_c_string ("hello, world! ", .true.) + if (len(s1) .ne. 17) stop 100 + + ! s2 is null-terminated "hello, world!" + s2 = f_c_string ("hello, world! ", .false.) + if (len(s2) .ne. 14) stop 200 + + ! s3 is null-terminated "hello, world!" (same as s2 example) + s3 = f_c_string ("hello, world! ") + if (len(s3) .ne. 14) stop 200 +end program main -- 2.39.5