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