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                |  57 ++++
 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, 328 insertions(+), 156 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 8a33fff68f9..699b38e157c 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
@@ -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
 
 
@@ -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

Reply via email to