Le 26/08/2026 à 20:08, Jerry D a écrit :
Hi all,

The attached patch recovers most of the slowdown identified in the subject PR. Test results from the original version of tonto SPEC benchmark are also described in the PR126964 Comment #7:

"For the configuration in the initial report, I am getting roughly 2% slower than the baseline before the blamed commit, meaning that most of the performance has been recovered."

To develop this patch I used claude to analyze an archived open source copy of tonto and generate an input "deck" to produce a local bench mark. This was couple by multiple runs using callgrind to identify the "hot paths"

With the patch applied for a rank two dummy passed on from a loop calling it 120000 times, with a non-contiguous actual argument, the instructions executed fall from 17151636793 to 11676814, against 10478570 before r17-3342.

As I stated above, this recovers most of the performance regression. It also has peaked my curiosity so I plan some followup explorations.

Regression tested with full testsuite on x86_64.

OK for mainline?

Regards,

Jerry

---
     fortran: [PR126964] Reduce the cost of a span addressed dummy

     Assisted-by: Claude Opus 5

    r17-3342 made a TARGET assumed shape or assumed rank dummy be addressed     through the span of its descriptor, so that a pointer to it stays valid     when its elements are subobjects of larger ones.  That costs in two ways,
     and SPEC 465.tonto pays both.

    First, is_subref_array became true for such a dummy, so passing one on to
     another procedure takes the copy-in/copy-out path, with the copy made
    conditional on the actual argument being contiguous.  That is more than
     the receiving dummy needs: a dummy that has a descriptor of its own
    addresses its elements by the strides held in it, so it accepts an actual
     argument of any stride; the one thing it cannot do is address elements
    that are subobjects of larger ones, which is what a span differing from
     the element length means.
Not exactly. What is not supported is a span that is not divisible by the element length. Checking for equality between the span and element length gives a stronger condition than strictly necessary. But not a wrong condition of course. I expect subojects of larger elements to possibly work if the divisibility condition holds.

  Narrow the condition to the span alone when the
     dummy has a descriptor and is not CONTIGUOUS.  A dummy that needs the
     argument packed still gets the full test.
I think it works, but the second part of the fix (your next paragraph) should avoid the need to do a runtime check. If the type is numeric, the array is flagged as "normalized" instead of "pointer", and it's using array indexing with stride instead of span. What's missing is gfc_is_span_addressed_dummy should return false for it I suppose, and then is_subref_array would return false.


     Second, addressing every element as offset * span leaves the step of a
    data reference symbolic, so loop versioning cannot prove that the accesses     stay aligned and the loop is never vectorized.  Fold the spacing into the
     strides and the offset on entry instead, so that the elements are
     addressed by the constant element length as usual.  The element length
     divides the spacing whenever the element size equals the element
    alignment, which covers integer, real and logical elements of an assumed
     shape dummy; elsewhere the span is still used to address them.
That part looks good.
Some more comments below.

diff --git a/gcc/fortran/trans-array.cc b/gcc/fortran/trans-array.cc
index 2f11b61a4b8..9f45fea07b1 100644
--- a/gcc/fortran/trans-array.cc
+++ b/gcc/fortran/trans-array.cc
@@ -514,6 +514,41 @@ span_addressed_array (tree expr)
 }
+/* An actual argument whose elements are subobjects can be described to a
+   span addressed dummy by the strides of its descriptor instead of by its
+   span, provided the element length divides the spacing.  That holds when
+   the element size equals the element alignment: the type of the object the
+   elements are part of is then at least as aligned, so its size, and hence
+   the spacing, is a multiple of the element length.  Folding the spacing
+   into the strides lets the elements be addressed by a constant element
+   length, which keeps the address evolutions analyzable.  */
+
+bool
+gfc_span_folds_into_stride (gfc_symbol *sym)
+{
+  if (!gfc_is_span_addressed_dummy (sym))
+    return false;
+
+  /* A character element length is not necessarily constant and a complex or
+     derived type can be larger than its alignment.  */
+  if (sym->ts.type != BT_INTEGER
+      && sym->ts.type != BT_REAL
+      && sym->ts.type != BT_LOGICAL)
+    return false;
This could be extended to single-field derived types and length one character. Good enough as is for now.

+
+  /* An assumed rank dummy has no strides to fold the spacing into.  */
+  if (!sym->as || sym->as->type != AS_ASSUMED_SHAPE || sym->as->rank < 1)
+    return false;
+
+  tree etype = gfc_typenode_for_spec (&sym->ts);
+  tree size = etype ? TYPE_SIZE_UNIT (etype) : NULL_TREE;
+
+  return (size
+         && tree_fits_uhwi_p (size)
+         && tree_to_uhwi (size) == TYPE_ALIGN_UNIT (etype));
I'm not sure about the guarantee we have that the same tree node will be used for equal values. I think wi::to_wide (...) == wi::to_wide (...) could be used instead. Again, good enough as is for now.
+}
+
+
 /* If the symbol or expression reference a CFI descriptor, return the
    pointer to the converted gfc descriptor. If an array reference is
    present as the last argument, check that it is the one applied to
@@ -7520,6 +7555,45 @@ gfc_trans_dummy_array_bias (gfc_symbol * sym, tree 
tmpdesc,
   if (VAR_P (GFC_TYPE_ARRAY_OFFSET (type)))
     gfc_add_modify (&init, GFC_TYPE_ARRAY_OFFSET (type), offset);
+ /* Fold the element spacing of the actual argument into the strides and the
+     offset, so that the elements are addressed by the constant element length
+     rather than by a span loaded from the descriptor.  The unit case is kept
+     as a separate arm of the conditional rather than folded into the
+     multiplication, so that the strides remain recognizable as being one for
+     a contiguous innermost dimension.  */
+  if (DECL_LANG_SPECIFIC (tmpdesc) && GFC_DECL_SPAN_NORMALIZED (tmpdesc))
+    {
+      tree element = fold_convert (gfc_array_index_type,
+                                  TYPE_SIZE_UNIT (gfc_get_element_type 
(type)));
+      tree span = gfc_evaluate_now (gfc_conv_descriptor_span_get (dumdesc),
+                                   &init);
+      tree unit = fold_build2_loc (input_location, EQ_EXPR, logical_type_node,
+                                  span, element);
+      tree factor = fold_build2_loc (input_location, TRUNC_DIV_EXPR,
+                                    gfc_array_index_type, span, element);
+      factor = gfc_evaluate_now (factor, &init);
+
+      auto scale = [&] (tree var)
+       {
+         tree scaled = fold_build2_loc (input_location, MULT_EXPR,
+                                        gfc_array_index_type, var, factor);
+         scaled = fold_build3_loc (input_location, COND_EXPR,
+                                   gfc_array_index_type, unit, var, scaled);
+         gfc_add_modify (&init, var, scaled);
+       };
+
+      for (n = 0; n < as->rank; n++)
+       {
+         /* A span addressed dummy is never repacked, so every stride is a
+            variable loaded from the descriptor.  */
+         gcc_assert (VAR_P (GFC_TYPE_ARRAY_STRIDE (type, n)));
+         scale (GFC_TYPE_ARRAY_STRIDE (type, n));
+       }
+
+      if (VAR_P (GFC_TYPE_ARRAY_OFFSET (type)))
Since you used asserts for GFC_TYPE_ARRAY_STRIDE, you can use an assert for GFC_TYPE_ARRAY_OFFSET.

+       scale (GFC_TYPE_ARRAY_OFFSET (type));
+    }
+
   gfc_trans_vla_type_sizes (sym, &init);
stmtInit = gfc_finish_block (&init);

diff --git a/gcc/fortran/trans-intrinsic.cc b/gcc/fortran/trans-intrinsic.cc
index 06c96d5a0a9..647f5a498b2 100644
--- a/gcc/fortran/trans-intrinsic.cc
+++ b/gcc/fortran/trans-intrinsic.cc
@@ -2311,10 +2311,14 @@ gfc_conv_intrinsic_is_contiguous (gfc_se * se, gfc_expr 
* expr)
 }
/* This function does the work for gfc_conv_intrinsic_is_contiguous,
-   plus it can be called directly.  */
+   plus it can be called directly.  With SPAN_ONLY, the strides are not
+   tested and the result is just that the span of the descriptor is the
+   element length, ie. that the elements are not subobjects of larger ones.
+   That is all that has to hold for a dummy that has a descriptor of its own,
+   since it addresses its elements by the strides held in it.  */
void
-gfc_conv_is_contiguous_expr (gfc_se *se, gfc_expr *arg)
+gfc_conv_is_contiguous_expr (gfc_se *se, gfc_expr *arg, bool span_only)

We may want to check at some point that the element length divides the span rather than just equals it. It is a different concept from contiguity, so please don't touch gfc_conv_is_contiguous_expr and use a separate function to do the check.

 {
   gfc_ss *ss;
   gfc_se argse;

Reply via email to