[v2] fortran: add INLINE and ALWAYS_INLINE attributes

Message ID f78f206ed7f08701d9167c92d5c9350e55806ade.camel@henrimenke.de
State New
Headers
Series [v2] fortran: add INLINE and ALWAYS_INLINE attributes |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-aarch64-bootstrap success Build passed
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap success Build passed

Commit Message

Henri Menke June 2, 2026, 11:15 a.m. UTC
  Hi Harald,

Thanks for the quick feedback.  Please find the patch for v2 attached.

Let me briefly address the requested changes point-by-point.

- Renamed FORCEINLINE to ALWAYS_INLINE and added a plain INLINE
attribute.  INLINE only sets DECL_DECLARED_INLINE_P, while
ALWAYS_INLINE additionally sets DECL_DISREGARD_INLINE_LIMITS.

- The new EXT_ATTR_INLINE and EXT_ATTR_ALWAYS_INLINE values are now
appended at the end of the enum, before EXT_ATTR_LAST, so the existing
bit positions in the module-file bitmask are preserved.  I also added a
source comment before the enum noting that new attributes must be
appended for this reason.

- The mutual-exclusion check now covers both INLINE and ALWAYS_INLINE
against NOINLINE.

While implementing these changes, I have also noticed a difference from
the C always_inline attribute.  Because ALWAYS_INLINE sets
DECL_DISREGARD_INLINE_LIMITS directly rather than attaching an
always_inline attribute, it does not force inlining at -O0.  For now I
have left it at that, because attaching an attribute to the declaration
would make this patch a bit more complicated.  If you would prefer the
exact C semantics I am happy to attach the real attribute instead.

One note on the testsuite.  The ALWAYS_INLINE test simply checks that
the procedure is inlined at -O.  The plain INLINE test needs more care,
because a small procedure is inlined at -O2 regardless of the
attribute.  That test therefore switches off automatic inlining with -
fno-inline-small-functions -fno-inline-functions -fno-inline-functions-
called-once and then verifies that the INLINE attribute alone re-
enables inlining for that one procedure.  I added a comment in the test
explaining this, but I worry about the robustness of this check,
especially on other platforms that I cannot test.

Bootstrapped and regtested on x86_64-pc-linux-gnu with --enable-
languages=c,c++,fortran, no regressions.

Kind regards,
Henri

On Mon, 2026-06-01 at 21:15 +0200, Harald Anlauf wrote:
> Hi Henry!
> 
> Am 01.06.26 um 9:49 AM schrieb Henri Menke:
> > Hi Harald!
> > 
> > First of all thank you very much for your fast and careful review!
> > 
> > On Sun, 2026-05-31 at 21:55 +0200, Harald Anlauf wrote:
> > > Hi Henri!
> > > 
> > > Am 31.05.26 um 8:01 PM schrieb Henri Menke:
> > > > gfortran already understands the !GCC$ ATTRIBUTES NOINLINE
> > > > directive to
> > > > suppress inlining of a procedure, but there was no way to
> > > > request
> > > > the
> > > > opposite, equivalent to the C always_inline attribute.
> > > > 
> > > > This adds a FORCEINLINE attribute usable as
> > > > 
> > > >       !GCC$ ATTRIBUTES forceinline :: my_procedure
> > > > 
> > > > Since Fortran has no 'inline' keyword, the translation sets
> > > > both
> > > > DECL_DECLARED_INLINE_P and DECL_DISREGARD_INLINE_LIMITS on the
> > > > function
> > > > declaration; setting only the latter would make the middle-end
> > > > warn
> > > > that
> > > > the always-inline function "might not be inlinable".
> > > > 
> > > > FORCEINLINE and NOINLINE are mutually exclusive.  In the
> > > > middle-end
> > > > the
> > > > DECL_UNINLINABLE flag set by NOINLINE always takes precedence,
> > > > so a
> > > > combination of the two would silently ignore FORCEINLINE.  To
> > > > avoid
> > > > that
> > > > surprise the directive parser warns and drops FORCEINLINE when
> > > > both
> > > > are
> > > > applied to the same procedure, whether in one directive or in
> > > > two.
> > > 
> > > While I think that options controlling inlining are desirable,
> > > I wonder if "FORCEINLINE" is a good choice.  Also, why is there
> > > no regular "INLINE"?
> > > 
> > > A commercial Fortran compiler I use at work supports "INLINE" and
> > > "ALWAYS_INLINE" with semantics comparable to gcc.  My preference
> > > would be to follow this convention also for gfortran.
> > > 
> > > (The Intel compiler has INLINE and FORCEINLINE, but according to
> > > the
> > > documentation these are used differently (and at the call site!),
> > > so there would be potential confusion of users.)
> > 
> > You are of course right.  I have to admit that I took the name
> > FORCEINLINE from the Intel compiler without realizing that Intel's
> > INLINE/FORCEINLINE are call-site directives with semantics
> > different
> > from what I implemented here.  That mismatch would indeed be a
> > source
> > of confusion and I fully agree that we should follow the INLINE /
> > ALWAYS_INLINE convention instead.  For the next revision I would
> > propose to:
> > 
> > - rename FORCEINLINE to ALWAYS_INLINE, keeping the current
> > semantics
> > (DECL_DECLARED_INLINE_P + DECL_DISREGARD_INLINE_LIMITS, i.e. the
> > equivalent of __attribute__((always_inline)))
> > 
> > - add INLINE as a plain inlining hint that only sets
> > DECL_DECLARED_INLINE_P and leaves the inliner's size limits in
> > effect
> > (in line with the C "inline" keyword)
> > 
> > - keep the mutual-exclusion handling, now between
> > {INLINE,ALWAYS_INLINE} and NOINLINE.
> > 
> > Does that sound okay?
> 
> Yes, this sounds fine with me.
> 
> > > A real problem of your patch is that it shifts the enums
> > > EXT_ATTR_*,
> > > after which the module files compiler from previous versions of
> > > gfortran are incompatible with those after the patch.
> > > This must be fixed.
> > 
> > Thanks, I did not realize that the EXT_ATTR_* values end up in the
> > module files (and there also seems to be no source comment warning
> > about this).  As far as I can tell, appending the new values at the
> > end
> > of the enum (before EXT_ATTR_LAST), and likewise appending the
> > corresponding rows at the end of ext_attr_list[], leaves the
> > existing
> > bit positions untouched.  Old modules would then continue to read
> > correctly and only modules that actually use the new attributes
> > would
> > be unreadable by an older gfortran, which seems unavoidable.
> 
> The externally visible extension attributes (attr->ext_attr) are
> saved
> to/restored from module files, and since this is a bitmask, appending
> at
> the end is the way to go.  We want to avoid surprises if a harmless
> bit
> in another gfortran version is suddenly interpreted e.g. as
> NORETURN...
> 
> > Would that approach be acceptable (append at the end), or would you
> > rather see the module version bumped?  Since I just started
> > contributing to GCC, I'm not sure what is the appropriate practice.
> 
> Appending at the end would be currently harmless in the sense that
> the
> new bits would likely be ignored by an older gfortran version (IIRC).
> You could extend the comment before the enum definition to indicate
> that new attributes must be added at the end (before EXT_ATTR_LAST).
> 
> One wish from my side: can you attach patches to your mails?
> This reduces the risk of whitespace struggles on my end.
> 
> Thanks,
> Harald
> 
> > Kind regards,
> > Henri
> > 
> > > Harald
> > > 
> > > > Bootstrapped and regtested on x86_64-pc-linux-gnu
> > > > (--enable-languages=c,c++,fortran): all three stages built and
> > > > the
> > > > stage 2/3 comparison succeeded, and the gfortran testsuite
> > > > shows no
> > > > regressions (75375 expected passes, 335 expected failures, 0
> > > > unexpected
> > > > failures), with the two new tests passing.
> > > > 
> > > > gcc/fortran/ChangeLog:
> > > > 
> > > > 	* gfortran.h (enum ext_attr_id_t): Add
> > > > EXT_ATTR_FORCEINLINE.
> > > > 	* decl.cc (ext_attr_list): Add "forceinline".
> > > > 	(gfc_match_gcc_attributes): Warn about and drop
> > > > FORCEINLINE when
> > > > 	it is combined with NOINLINE on the same symbol.
> > > > 	* trans-decl.cc (build_function_decl): Handle
> > > > EXT_ATTR_FORCEINLINE
> > > > 	by setting DECL_DECLARED_INLINE_P and
> > > > DECL_DISREGARD_INLINE_LIMITS.
> > > > 	* gfortran.texi (ATTRIBUTES directive): Document
> > > > FORCEINLINE.
> > > > 
> > > > gcc/testsuite/ChangeLog:
> > > > 
> > > > 	* gfortran.dg/forceinline_1.f90: New test.
> > > > 	* gfortran.dg/forceinline_2.f90: New test.
> > > > 
> > > > Signed-off-by: Henri Menke <henri@henrimenke.de>
> > > > ---
> > > >    gcc/fortran/decl.cc                         | 13 +++++++++++
> > > >    gcc/fortran/gfortran.h                      |  1 +
> > > >    gcc/fortran/gfortran.texi                   |  5 ++++
> > > >    gcc/fortran/trans-decl.cc                   |  9 +++++++
> > > >    gcc/testsuite/gfortran.dg/forceinline_1.f90 | 26
> > > > +++++++++++++++++++++
> > > >    gcc/testsuite/gfortran.dg/forceinline_2.f90 | 14 +++++++++++
> > > >    6 files changed, 68 insertions(+)
> > > >    create mode 100644
> > > > gcc/testsuite/gfortran.dg/forceinline_1.f90
> > > >    create mode 100644
> > > > gcc/testsuite/gfortran.dg/forceinline_2.f90
> > > > 
> > > > diff --git a/gcc/fortran/decl.cc b/gcc/fortran/decl.cc
> > > > index 166b10d4cd4..aa5b291ac41 100644
> > > > --- a/gcc/fortran/decl.cc
> > > > +++ b/gcc/fortran/decl.cc
> > > > @@ -12847,6 +12847,7 @@ const ext_attr_t ext_attr_list[] = {
> > > >      { "no_arg_check", EXT_ATTR_NO_ARG_CHECK, NULL        },
> > > >      { "deprecated",   EXT_ATTR_DEPRECATED,   NULL	      
> > > > },
> > > >      { "noinline",     EXT_ATTR_NOINLINE,     NULL	      
> > > > },
> > > > +  { "forceinline",  EXT_ATTR_FORCEINLINE,  NULL	      
> > > > },
> > > >      { "noreturn",     EXT_ATTR_NORETURN,     NULL	      
> > > > },
> > > >      { "weak",	    EXT_ATTR_WEAK,	   NULL	      
> > > > },
> > > >      { NULL,           EXT_ATTR_LAST,         NULL        }
> > > > @@ -12925,6 +12926,18 @@ gfc_match_gcc_attributes (void)
> > > >    
> > > >          sym->attr.ext_attr |= attr.ext_attr;
> > > >    
> > > > +      /* FORCEINLINE and NOINLINE are mutually exclusive.  In
> > > > the
> > > > middle-end
> > > > +	 DECL_UNINLINABLE (set by NOINLINE) always wins, so
> > > > FORCEINLINE would
> > > > +	 be silently ignored.  Warn and drop it instead.  */
> > > > +      if ((sym->attr.ext_attr & (1 << EXT_ATTR_FORCEINLINE))
> > > > +	  && (sym->attr.ext_attr & (1 << EXT_ATTR_NOINLINE)))
> > > > +	{
> > > > +	  gfc_warning (0, "Attribute %<FORCEINLINE%> at %C is
> > > > incompatible "
> > > > +		       "with %<NOINLINE%> for %qs and will be
> > > > ignored",
> > > > +		       sym->name);
> > > > +	  sym->attr.ext_attr &= ~(1 << EXT_ATTR_FORCEINLINE);
> > > > +	}
> > > > +
> > > >          if (gfc_match_eos () == MATCH_YES)
> > > >    	break;
> > > >    
> > > > diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h
> > > > index 37a8582e36d..440ad050bcb 100644
> > > > --- a/gcc/fortran/gfortran.h
> > > > +++ b/gcc/fortran/gfortran.h
> > > > @@ -886,6 +886,7 @@ typedef enum
> > > >      EXT_ATTR_NO_ARG_CHECK,
> > > >      EXT_ATTR_DEPRECATED,
> > > >      EXT_ATTR_NOINLINE,
> > > > +  EXT_ATTR_FORCEINLINE,
> > > >      EXT_ATTR_NORETURN,
> > > >      EXT_ATTR_WEAK,
> > > >      EXT_ATTR_LAST, EXT_ATTR_NUM = EXT_ATTR_LAST
> > > > diff --git a/gcc/fortran/gfortran.texi
> > > > b/gcc/fortran/gfortran.texi
> > > > index a930cc1dc9c..255a9235f60 100644
> > > > --- a/gcc/fortran/gfortran.texi
> > > > +++ b/gcc/fortran/gfortran.texi
> > > > @@ -3397,6 +3397,11 @@ requires an explicit interface.
> > > >    deprecated procedure, variable or parameter; the warning can
> > > > be
> > > > suppressed
> > > >    with @option{-Wno-deprecated-declarations}.
> > > >    @item @code{NOINLINE} -- prevent inlining given function.
> > > > +@item @code{FORCEINLINE} -- force inlining of a given
> > > > function,
> > > > ignoring the
> > > > +inlining size limits.  This is the counterpart of
> > > > @code{NOINLINE}
> > > > and
> > > > +corresponds to the C @code{always_inline} attribute.
> > > > @code{FORCEINLINE} and
> > > > +@code{NOINLINE} are mutually exclusive; specifying both for
> > > > the
> > > > same procedure
> > > > +makes @code{FORCEINLINE} ignored with a warning.
> > > >    @item @code{NORETURN} -- add a hint that a given function
> > > > cannot
> > > > return.
> > > >    @item @code{WEAK} -- emit the declaration of an external
> > > > symbol
> > > > as a weak
> > > >    symbol rather than a global.  This is primarily useful in
> > > > defining library
> > > > diff --git a/gcc/fortran/trans-decl.cc b/gcc/fortran/trans-
> > > > decl.cc
> > > > index 1bcbfdfd2c9..da291c976ba 100644
> > > > --- a/gcc/fortran/trans-decl.cc
> > > > +++ b/gcc/fortran/trans-decl.cc
> > > > @@ -2652,6 +2652,15 @@ build_function_decl (gfc_symbol * sym,
> > > > bool
> > > > global)
> > > >      if (attr.ext_attr & (1 << EXT_ATTR_NOINLINE))
> > > >        DECL_UNINLINABLE (fndecl) = 1;
> > > >    
> > > > +  /* Mark forceinline functions.  Fortran has no 'inline'
> > > > keyword,
> > > > so set
> > > > +     DECL_DECLARED_INLINE_P as well; otherwise the middle-end
> > > > warns that the
> > > > +     always-inline function "might not be inlinable".  */
> > > > +  if (attr.ext_attr & (1 << EXT_ATTR_FORCEINLINE))
> > > > +    {
> > > > +      DECL_DECLARED_INLINE_P (fndecl) = 1;
> > > > +      DECL_DISREGARD_INLINE_LIMITS (fndecl) = 1;
> > > > +    }
> > > > +
> > > >      /* Mark noreturn functions.  */
> > > >      if (attr.ext_attr & (1 << EXT_ATTR_NORETURN))
> > > >        TREE_THIS_VOLATILE (fndecl) = 1;
> > > > diff --git a/gcc/testsuite/gfortran.dg/forceinline_1.f90
> > > > b/gcc/testsuite/gfortran.dg/forceinline_1.f90
> > > > new file mode 100644
> > > > index 00000000000..2058c088662
> > > > --- /dev/null
> > > > +++ b/gcc/testsuite/gfortran.dg/forceinline_1.f90
> > > > @@ -0,0 +1,26 @@
> > > > +! { dg-do compile }
> > > > +! { dg-options "-O -fdump-tree-optimized" }
> > > > +!
> > > > +! Verify that !GCC$ ATTRIBUTES forceinline forces inlining:
> > > > the
> > > > helper
> > > > +! procedure is inlined into its caller and no standalone call
> > > > remains.
> > > > +
> > > > +subroutine caller(a, n)
> > > > +  implicit none
> > > > +  integer, intent(in) :: n
> > > > +  real, intent(inout) :: a(n)
> > > > +  call helper(a, n)
> > > > +  call helper(a, n)
> > > > +contains
> > > > +  subroutine helper(x, m)
> > > > +    implicit none
> > > > +    integer, intent(in) :: m
> > > > +    real, intent(inout) :: x(m)
> > > > +!GCC$ ATTRIBUTES forceinline :: helper
> > > > +    integer :: i
> > > > +    do i = 1, m
> > > > +      x(i) = x(i) + 1.0
> > > > +    end do
> > > > +  end subroutine helper
> > > > +end subroutine caller
> > > > +
> > > > +! { dg-final { scan-tree-dump-not "helper" "optimized" } }
> > > > diff --git a/gcc/testsuite/gfortran.dg/forceinline_2.f90
> > > > b/gcc/testsuite/gfortran.dg/forceinline_2.f90
> > > > new file mode 100644
> > > > index 00000000000..44e4c107034
> > > > --- /dev/null
> > > > +++ b/gcc/testsuite/gfortran.dg/forceinline_2.f90
> > > > @@ -0,0 +1,14 @@
> > > > +! { dg-do compile }
> > > > +!
> > > > +! FORCEINLINE and NOINLINE are mutually exclusive.  Specifying
> > > > both for the
> > > > +! same procedure must warn and drop FORCEINLINE, whether they
> > > > appear in one
> > > > +! directive or in separate directives.
> > > > +
> > > > +subroutine foo()
> > > > +!GCC$ ATTRIBUTES forceinline, noinline :: foo  ! { dg-warning
> > > > "FORCEINLINE. at .1. is incompatible with .NOINLINE." }
> > > > +end subroutine foo
> > > > +
> > > > +subroutine bar()
> > > > +!GCC$ ATTRIBUTES forceinline :: bar
> > > > +!GCC$ ATTRIBUTES noinline :: bar  ! { dg-warning "FORCEINLINE.
> > > > at
> > > > .1. is incompatible with .NOINLINE." }
> > > > +end subroutine bar
> >
  

Comments

Harald Anlauf June 2, 2026, 8:07 p.m. UTC | #1
Hi Henri!

Am 02.06.26 um 1:15 PM schrieb Henri Menke:
> Hi Harald,
> 
> Thanks for the quick feedback.  Please find the patch for v2 attached.
> 
> Let me briefly address the requested changes point-by-point.
> 
> - Renamed FORCEINLINE to ALWAYS_INLINE and added a plain INLINE
> attribute.  INLINE only sets DECL_DECLARED_INLINE_P, while
> ALWAYS_INLINE additionally sets DECL_DISREGARD_INLINE_LIMITS.
> 
> - The new EXT_ATTR_INLINE and EXT_ATTR_ALWAYS_INLINE values are now
> appended at the end of the enum, before EXT_ATTR_LAST, so the existing
> bit positions in the module-file bitmask are preserved.  I also added a
> source comment before the enum noting that new attributes must be
> appended for this reason.
> 
> - The mutual-exclusion check now covers both INLINE and ALWAYS_INLINE
> against NOINLINE.
> 
> While implementing these changes, I have also noticed a difference from
> the C always_inline attribute.  Because ALWAYS_INLINE sets
> DECL_DISREGARD_INLINE_LIMITS directly rather than attaching an
> always_inline attribute, it does not force inlining at -O0.  For now I
> have left it at that, because attaching an attribute to the declaration
> would make this patch a bit more complicated.  If you would prefer the
> exact C semantics I am happy to attach the real attribute instead.

No, your current solution is really fine with me.

> One note on the testsuite.  The ALWAYS_INLINE test simply checks that
> the procedure is inlined at -O.  The plain INLINE test needs more care,
> because a small procedure is inlined at -O2 regardless of the
> attribute.  That test therefore switches off automatic inlining with -
> fno-inline-small-functions -fno-inline-functions -fno-inline-functions-
> called-once and then verifies that the INLINE attribute alone re-
> enables inlining for that one procedure.  I added a comment in the test
> explaining this, but I worry about the robustness of this check,
> especially on other platforms that I cannot test.

This is also fine.

There is only one thing I noted while playing with your new testcase
inline_2.f90: depending on the ordering of the lines

!GCC$ ATTRIBUTES inline :: bar
!GCC$ ATTRIBUTES noinline :: bar

one gets a warning that either looks correct or confusing.
The way you've chosen it for the testcase I get:

    13 | !GCC$ ATTRIBUTES noinline :: bar  ! { dg-warning "INLINE. at 
.1. is incompatible with .NOINLINE." }
       |                                 1
Warning: Attribute »INLINE« at (1) is incompatible with »NOINLINE« for 
»bar« and will be ignored

This is because the order in which the attributes are processed
is not taken into account by your new checks.

Anyway, I think this is rather a cosmetic issue and not a real problem.

Besides, there is a minor whitespace issue your can check yourself:

% ./contrib/check_GNU_style.sh 
0001-fortran-add-INLINE-and-ALWAYS_INLINE-attributes.patch

Blocks of 8 spaces should be replaced with tabs.
83:+  { "inline",       EXT_ATTR_INLINE,       NULL        },
84:+  { "always_inline",EXT_ATTR_ALWAYS_INLINE,NULL        },

It is hard to see without this hint, but after "NULL" there
shall be a tab.  I can fix this myself.

> Bootstrapped and regtested on x86_64-pc-linux-gnu with --enable-
> languages=c,c++,fortran, no regressions.

If nobody else speaks up, particularly w.r.t. the warnings mentioned
above, I'll commit for you tomorrow.

Thanks for the patch!

Harald

> Kind regards,
> Henri
> 
> On Mon, 2026-06-01 at 21:15 +0200, Harald Anlauf wrote:
>> Hi Henry!
>>
>> Am 01.06.26 um 9:49 AM schrieb Henri Menke:
>>> Hi Harald!
>>>
>>> First of all thank you very much for your fast and careful review!
>>>
>>> On Sun, 2026-05-31 at 21:55 +0200, Harald Anlauf wrote:
>>>> Hi Henri!
>>>>
>>>> Am 31.05.26 um 8:01 PM schrieb Henri Menke:
>>>>> gfortran already understands the !GCC$ ATTRIBUTES NOINLINE
>>>>> directive to
>>>>> suppress inlining of a procedure, but there was no way to
>>>>> request
>>>>> the
>>>>> opposite, equivalent to the C always_inline attribute.
>>>>>
>>>>> This adds a FORCEINLINE attribute usable as
>>>>>
>>>>>        !GCC$ ATTRIBUTES forceinline :: my_procedure
>>>>>
>>>>> Since Fortran has no 'inline' keyword, the translation sets
>>>>> both
>>>>> DECL_DECLARED_INLINE_P and DECL_DISREGARD_INLINE_LIMITS on the
>>>>> function
>>>>> declaration; setting only the latter would make the middle-end
>>>>> warn
>>>>> that
>>>>> the always-inline function "might not be inlinable".
>>>>>
>>>>> FORCEINLINE and NOINLINE are mutually exclusive.  In the
>>>>> middle-end
>>>>> the
>>>>> DECL_UNINLINABLE flag set by NOINLINE always takes precedence,
>>>>> so a
>>>>> combination of the two would silently ignore FORCEINLINE.  To
>>>>> avoid
>>>>> that
>>>>> surprise the directive parser warns and drops FORCEINLINE when
>>>>> both
>>>>> are
>>>>> applied to the same procedure, whether in one directive or in
>>>>> two.
>>>>
>>>> While I think that options controlling inlining are desirable,
>>>> I wonder if "FORCEINLINE" is a good choice.  Also, why is there
>>>> no regular "INLINE"?
>>>>
>>>> A commercial Fortran compiler I use at work supports "INLINE" and
>>>> "ALWAYS_INLINE" with semantics comparable to gcc.  My preference
>>>> would be to follow this convention also for gfortran.
>>>>
>>>> (The Intel compiler has INLINE and FORCEINLINE, but according to
>>>> the
>>>> documentation these are used differently (and at the call site!),
>>>> so there would be potential confusion of users.)
>>>
>>> You are of course right.  I have to admit that I took the name
>>> FORCEINLINE from the Intel compiler without realizing that Intel's
>>> INLINE/FORCEINLINE are call-site directives with semantics
>>> different
>>> from what I implemented here.  That mismatch would indeed be a
>>> source
>>> of confusion and I fully agree that we should follow the INLINE /
>>> ALWAYS_INLINE convention instead.  For the next revision I would
>>> propose to:
>>>
>>> - rename FORCEINLINE to ALWAYS_INLINE, keeping the current
>>> semantics
>>> (DECL_DECLARED_INLINE_P + DECL_DISREGARD_INLINE_LIMITS, i.e. the
>>> equivalent of __attribute__((always_inline)))
>>>
>>> - add INLINE as a plain inlining hint that only sets
>>> DECL_DECLARED_INLINE_P and leaves the inliner's size limits in
>>> effect
>>> (in line with the C "inline" keyword)
>>>
>>> - keep the mutual-exclusion handling, now between
>>> {INLINE,ALWAYS_INLINE} and NOINLINE.
>>>
>>> Does that sound okay?
>>
>> Yes, this sounds fine with me.
>>
>>>> A real problem of your patch is that it shifts the enums
>>>> EXT_ATTR_*,
>>>> after which the module files compiler from previous versions of
>>>> gfortran are incompatible with those after the patch.
>>>> This must be fixed.
>>>
>>> Thanks, I did not realize that the EXT_ATTR_* values end up in the
>>> module files (and there also seems to be no source comment warning
>>> about this).  As far as I can tell, appending the new values at the
>>> end
>>> of the enum (before EXT_ATTR_LAST), and likewise appending the
>>> corresponding rows at the end of ext_attr_list[], leaves the
>>> existing
>>> bit positions untouched.  Old modules would then continue to read
>>> correctly and only modules that actually use the new attributes
>>> would
>>> be unreadable by an older gfortran, which seems unavoidable.
>>
>> The externally visible extension attributes (attr->ext_attr) are
>> saved
>> to/restored from module files, and since this is a bitmask, appending
>> at
>> the end is the way to go.  We want to avoid surprises if a harmless
>> bit
>> in another gfortran version is suddenly interpreted e.g. as
>> NORETURN...
>>
>>> Would that approach be acceptable (append at the end), or would you
>>> rather see the module version bumped?  Since I just started
>>> contributing to GCC, I'm not sure what is the appropriate practice.
>>
>> Appending at the end would be currently harmless in the sense that
>> the
>> new bits would likely be ignored by an older gfortran version (IIRC).
>> You could extend the comment before the enum definition to indicate
>> that new attributes must be added at the end (before EXT_ATTR_LAST).
>>
>> One wish from my side: can you attach patches to your mails?
>> This reduces the risk of whitespace struggles on my end.
>>
>> Thanks,
>> Harald
>>
>>> Kind regards,
>>> Henri
>>>
>>>> Harald
>>>>
>>>>> Bootstrapped and regtested on x86_64-pc-linux-gnu
>>>>> (--enable-languages=c,c++,fortran): all three stages built and
>>>>> the
>>>>> stage 2/3 comparison succeeded, and the gfortran testsuite
>>>>> shows no
>>>>> regressions (75375 expected passes, 335 expected failures, 0
>>>>> unexpected
>>>>> failures), with the two new tests passing.
>>>>>
>>>>> gcc/fortran/ChangeLog:
>>>>>
>>>>> 	* gfortran.h (enum ext_attr_id_t): Add
>>>>> EXT_ATTR_FORCEINLINE.
>>>>> 	* decl.cc (ext_attr_list): Add "forceinline".
>>>>> 	(gfc_match_gcc_attributes): Warn about and drop
>>>>> FORCEINLINE when
>>>>> 	it is combined with NOINLINE on the same symbol.
>>>>> 	* trans-decl.cc (build_function_decl): Handle
>>>>> EXT_ATTR_FORCEINLINE
>>>>> 	by setting DECL_DECLARED_INLINE_P and
>>>>> DECL_DISREGARD_INLINE_LIMITS.
>>>>> 	* gfortran.texi (ATTRIBUTES directive): Document
>>>>> FORCEINLINE.
>>>>>
>>>>> gcc/testsuite/ChangeLog:
>>>>>
>>>>> 	* gfortran.dg/forceinline_1.f90: New test.
>>>>> 	* gfortran.dg/forceinline_2.f90: New test.
>>>>>
>>>>> Signed-off-by: Henri Menke <henri@henrimenke.de>
>>>>> ---
>>>>>     gcc/fortran/decl.cc                         | 13 +++++++++++
>>>>>     gcc/fortran/gfortran.h                      |  1 +
>>>>>     gcc/fortran/gfortran.texi                   |  5 ++++
>>>>>     gcc/fortran/trans-decl.cc                   |  9 +++++++
>>>>>     gcc/testsuite/gfortran.dg/forceinline_1.f90 | 26
>>>>> +++++++++++++++++++++
>>>>>     gcc/testsuite/gfortran.dg/forceinline_2.f90 | 14 +++++++++++
>>>>>     6 files changed, 68 insertions(+)
>>>>>     create mode 100644
>>>>> gcc/testsuite/gfortran.dg/forceinline_1.f90
>>>>>     create mode 100644
>>>>> gcc/testsuite/gfortran.dg/forceinline_2.f90
>>>>>
>>>>> diff --git a/gcc/fortran/decl.cc b/gcc/fortran/decl.cc
>>>>> index 166b10d4cd4..aa5b291ac41 100644
>>>>> --- a/gcc/fortran/decl.cc
>>>>> +++ b/gcc/fortran/decl.cc
>>>>> @@ -12847,6 +12847,7 @@ const ext_attr_t ext_attr_list[] = {
>>>>>       { "no_arg_check", EXT_ATTR_NO_ARG_CHECK, NULL        },
>>>>>       { "deprecated",   EXT_ATTR_DEPRECATED,   NULL	
>>>>> },
>>>>>       { "noinline",     EXT_ATTR_NOINLINE,     NULL	
>>>>> },
>>>>> +  { "forceinline",  EXT_ATTR_FORCEINLINE,  NULL	
>>>>> },
>>>>>       { "noreturn",     EXT_ATTR_NORETURN,     NULL	
>>>>> },
>>>>>       { "weak",	    EXT_ATTR_WEAK,	   NULL	
>>>>> },
>>>>>       { NULL,           EXT_ATTR_LAST,         NULL        }
>>>>> @@ -12925,6 +12926,18 @@ gfc_match_gcc_attributes (void)
>>>>>     
>>>>>           sym->attr.ext_attr |= attr.ext_attr;
>>>>>     
>>>>> +      /* FORCEINLINE and NOINLINE are mutually exclusive.  In
>>>>> the
>>>>> middle-end
>>>>> +	 DECL_UNINLINABLE (set by NOINLINE) always wins, so
>>>>> FORCEINLINE would
>>>>> +	 be silently ignored.  Warn and drop it instead.  */
>>>>> +      if ((sym->attr.ext_attr & (1 << EXT_ATTR_FORCEINLINE))
>>>>> +	  && (sym->attr.ext_attr & (1 << EXT_ATTR_NOINLINE)))
>>>>> +	{
>>>>> +	  gfc_warning (0, "Attribute %<FORCEINLINE%> at %C is
>>>>> incompatible "
>>>>> +		       "with %<NOINLINE%> for %qs and will be
>>>>> ignored",
>>>>> +		       sym->name);
>>>>> +	  sym->attr.ext_attr &= ~(1 << EXT_ATTR_FORCEINLINE);
>>>>> +	}
>>>>> +
>>>>>           if (gfc_match_eos () == MATCH_YES)
>>>>>     	break;
>>>>>     
>>>>> diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h
>>>>> index 37a8582e36d..440ad050bcb 100644
>>>>> --- a/gcc/fortran/gfortran.h
>>>>> +++ b/gcc/fortran/gfortran.h
>>>>> @@ -886,6 +886,7 @@ typedef enum
>>>>>       EXT_ATTR_NO_ARG_CHECK,
>>>>>       EXT_ATTR_DEPRECATED,
>>>>>       EXT_ATTR_NOINLINE,
>>>>> +  EXT_ATTR_FORCEINLINE,
>>>>>       EXT_ATTR_NORETURN,
>>>>>       EXT_ATTR_WEAK,
>>>>>       EXT_ATTR_LAST, EXT_ATTR_NUM = EXT_ATTR_LAST
>>>>> diff --git a/gcc/fortran/gfortran.texi
>>>>> b/gcc/fortran/gfortran.texi
>>>>> index a930cc1dc9c..255a9235f60 100644
>>>>> --- a/gcc/fortran/gfortran.texi
>>>>> +++ b/gcc/fortran/gfortran.texi
>>>>> @@ -3397,6 +3397,11 @@ requires an explicit interface.
>>>>>     deprecated procedure, variable or parameter; the warning can
>>>>> be
>>>>> suppressed
>>>>>     with @option{-Wno-deprecated-declarations}.
>>>>>     @item @code{NOINLINE} -- prevent inlining given function.
>>>>> +@item @code{FORCEINLINE} -- force inlining of a given
>>>>> function,
>>>>> ignoring the
>>>>> +inlining size limits.  This is the counterpart of
>>>>> @code{NOINLINE}
>>>>> and
>>>>> +corresponds to the C @code{always_inline} attribute.
>>>>> @code{FORCEINLINE} and
>>>>> +@code{NOINLINE} are mutually exclusive; specifying both for
>>>>> the
>>>>> same procedure
>>>>> +makes @code{FORCEINLINE} ignored with a warning.
>>>>>     @item @code{NORETURN} -- add a hint that a given function
>>>>> cannot
>>>>> return.
>>>>>     @item @code{WEAK} -- emit the declaration of an external
>>>>> symbol
>>>>> as a weak
>>>>>     symbol rather than a global.  This is primarily useful in
>>>>> defining library
>>>>> diff --git a/gcc/fortran/trans-decl.cc b/gcc/fortran/trans-
>>>>> decl.cc
>>>>> index 1bcbfdfd2c9..da291c976ba 100644
>>>>> --- a/gcc/fortran/trans-decl.cc
>>>>> +++ b/gcc/fortran/trans-decl.cc
>>>>> @@ -2652,6 +2652,15 @@ build_function_decl (gfc_symbol * sym,
>>>>> bool
>>>>> global)
>>>>>       if (attr.ext_attr & (1 << EXT_ATTR_NOINLINE))
>>>>>         DECL_UNINLINABLE (fndecl) = 1;
>>>>>     
>>>>> +  /* Mark forceinline functions.  Fortran has no 'inline'
>>>>> keyword,
>>>>> so set
>>>>> +     DECL_DECLARED_INLINE_P as well; otherwise the middle-end
>>>>> warns that the
>>>>> +     always-inline function "might not be inlinable".  */
>>>>> +  if (attr.ext_attr & (1 << EXT_ATTR_FORCEINLINE))
>>>>> +    {
>>>>> +      DECL_DECLARED_INLINE_P (fndecl) = 1;
>>>>> +      DECL_DISREGARD_INLINE_LIMITS (fndecl) = 1;
>>>>> +    }
>>>>> +
>>>>>       /* Mark noreturn functions.  */
>>>>>       if (attr.ext_attr & (1 << EXT_ATTR_NORETURN))
>>>>>         TREE_THIS_VOLATILE (fndecl) = 1;
>>>>> diff --git a/gcc/testsuite/gfortran.dg/forceinline_1.f90
>>>>> b/gcc/testsuite/gfortran.dg/forceinline_1.f90
>>>>> new file mode 100644
>>>>> index 00000000000..2058c088662
>>>>> --- /dev/null
>>>>> +++ b/gcc/testsuite/gfortran.dg/forceinline_1.f90
>>>>> @@ -0,0 +1,26 @@
>>>>> +! { dg-do compile }
>>>>> +! { dg-options "-O -fdump-tree-optimized" }
>>>>> +!
>>>>> +! Verify that !GCC$ ATTRIBUTES forceinline forces inlining:
>>>>> the
>>>>> helper
>>>>> +! procedure is inlined into its caller and no standalone call
>>>>> remains.
>>>>> +
>>>>> +subroutine caller(a, n)
>>>>> +  implicit none
>>>>> +  integer, intent(in) :: n
>>>>> +  real, intent(inout) :: a(n)
>>>>> +  call helper(a, n)
>>>>> +  call helper(a, n)
>>>>> +contains
>>>>> +  subroutine helper(x, m)
>>>>> +    implicit none
>>>>> +    integer, intent(in) :: m
>>>>> +    real, intent(inout) :: x(m)
>>>>> +!GCC$ ATTRIBUTES forceinline :: helper
>>>>> +    integer :: i
>>>>> +    do i = 1, m
>>>>> +      x(i) = x(i) + 1.0
>>>>> +    end do
>>>>> +  end subroutine helper
>>>>> +end subroutine caller
>>>>> +
>>>>> +! { dg-final { scan-tree-dump-not "helper" "optimized" } }
>>>>> diff --git a/gcc/testsuite/gfortran.dg/forceinline_2.f90
>>>>> b/gcc/testsuite/gfortran.dg/forceinline_2.f90
>>>>> new file mode 100644
>>>>> index 00000000000..44e4c107034
>>>>> --- /dev/null
>>>>> +++ b/gcc/testsuite/gfortran.dg/forceinline_2.f90
>>>>> @@ -0,0 +1,14 @@
>>>>> +! { dg-do compile }
>>>>> +!
>>>>> +! FORCEINLINE and NOINLINE are mutually exclusive.  Specifying
>>>>> both for the
>>>>> +! same procedure must warn and drop FORCEINLINE, whether they
>>>>> appear in one
>>>>> +! directive or in separate directives.
>>>>> +
>>>>> +subroutine foo()
>>>>> +!GCC$ ATTRIBUTES forceinline, noinline :: foo  ! { dg-warning
>>>>> "FORCEINLINE. at .1. is incompatible with .NOINLINE." }
>>>>> +end subroutine foo
>>>>> +
>>>>> +subroutine bar()
>>>>> +!GCC$ ATTRIBUTES forceinline :: bar
>>>>> +!GCC$ ATTRIBUTES noinline :: bar  ! { dg-warning "FORCEINLINE.
>>>>> at
>>>>> .1. is incompatible with .NOINLINE." }
>>>>> +end subroutine bar
>>>
  
Harald Anlauf June 3, 2026, 8:29 p.m. UTC | #2
Am 02.06.26 um 10:07 PM schrieb Harald Anlauf:
> Hi Henri!
> 
> Am 02.06.26 um 1:15 PM schrieb Henri Menke:
>> Hi Harald,
>>
>> Thanks for the quick feedback.  Please find the patch for v2 attached.
>>
>> Let me briefly address the requested changes point-by-point.
>>
>> - Renamed FORCEINLINE to ALWAYS_INLINE and added a plain INLINE
>> attribute.  INLINE only sets DECL_DECLARED_INLINE_P, while
>> ALWAYS_INLINE additionally sets DECL_DISREGARD_INLINE_LIMITS.
>>
>> - The new EXT_ATTR_INLINE and EXT_ATTR_ALWAYS_INLINE values are now
>> appended at the end of the enum, before EXT_ATTR_LAST, so the existing
>> bit positions in the module-file bitmask are preserved.  I also added a
>> source comment before the enum noting that new attributes must be
>> appended for this reason.
>>
>> - The mutual-exclusion check now covers both INLINE and ALWAYS_INLINE
>> against NOINLINE.
>>
>> While implementing these changes, I have also noticed a difference from
>> the C always_inline attribute.  Because ALWAYS_INLINE sets
>> DECL_DISREGARD_INLINE_LIMITS directly rather than attaching an
>> always_inline attribute, it does not force inlining at -O0.  For now I
>> have left it at that, because attaching an attribute to the declaration
>> would make this patch a bit more complicated.  If you would prefer the
>> exact C semantics I am happy to attach the real attribute instead.
> 
> No, your current solution is really fine with me.
> 
>> One note on the testsuite.  The ALWAYS_INLINE test simply checks that
>> the procedure is inlined at -O.  The plain INLINE test needs more care,
>> because a small procedure is inlined at -O2 regardless of the
>> attribute.  That test therefore switches off automatic inlining with -
>> fno-inline-small-functions -fno-inline-functions -fno-inline-functions-
>> called-once and then verifies that the INLINE attribute alone re-
>> enables inlining for that one procedure.  I added a comment in the test
>> explaining this, but I worry about the robustness of this check,
>> especially on other platforms that I cannot test.
> 
> This is also fine.
> 
> There is only one thing I noted while playing with your new testcase
> inline_2.f90: depending on the ordering of the lines
> 
> !GCC$ ATTRIBUTES inline :: bar
> !GCC$ ATTRIBUTES noinline :: bar
> 
> one gets a warning that either looks correct or confusing.
> The way you've chosen it for the testcase I get:
> 
>     13 | !GCC$ ATTRIBUTES noinline :: bar  ! { dg-warning "INLINE. 
> at .1. is incompatible with .NOINLINE." }
>        |                                 1
> Warning: Attribute »INLINE« at (1) is incompatible with »NOINLINE« for 
> »bar« and will be ignored
> 
> This is because the order in which the attributes are processed
> is not taken into account by your new checks.
> 
> Anyway, I think this is rather a cosmetic issue and not a real problem.
> 
> Besides, there is a minor whitespace issue your can check yourself:
> 
> % ./contrib/check_GNU_style.sh 0001-fortran-add-INLINE-and- 
> ALWAYS_INLINE-attributes.patch
> 
> Blocks of 8 spaces should be replaced with tabs.
> 83:+  { "inline",       EXT_ATTR_INLINE,       NULL        },
> 84:+  { "always_inline",EXT_ATTR_ALWAYS_INLINE,NULL        },
> 
> It is hard to see without this hint, but after "NULL" there
> shall be a tab.  I can fix this myself.
> 
>> Bootstrapped and regtested on x86_64-pc-linux-gnu with --enable-
>> languages=c,c++,fortran, no regressions.
> 
> If nobody else speaks up, particularly w.r.t. the warnings mentioned
> above, I'll commit for you tomorrow.
> 
> Thanks for the patch!
> 
> Harald

No further comments, so pushed as r17-1274-ga3de5985947287 .

Thanks,
Harald
  

Patch

From 0d00e57d09d98d6ef11dc431be1ac2c927a6d3c5 Mon Sep 17 00:00:00 2001
From: Henri Menke <henri@henrimenke.de>
Date: Fri, 29 May 2026 18:09:05 +0000
Subject: [PATCH v2] fortran: add INLINE and ALWAYS_INLINE attributes

gfortran already understands the !GCC$ ATTRIBUTES NOINLINE directive to
suppress inlining of a procedure, but there was no way to request the
opposite.  This adds two attributes:

    !GCC$ ATTRIBUTES inline :: my_procedure
    !GCC$ ATTRIBUTES always_inline :: my_procedure

INLINE is a plain hint equivalent to the C inline keyword and only sets
DECL_DECLARED_INLINE_P, so the inliner's size limits still apply.
ALWAYS_INLINE corresponds to the C always_inline attribute and
additionally sets DECL_DISREGARD_INLINE_LIMITS.  Since Fortran has no
inline keyword, DECL_DECLARED_INLINE_P is set explicitly.  Setting only
DECL_DISREGARD_INLINE_LIMITS would make the middle-end warn that the
always-inline function might not be inlinable.

INLINE and ALWAYS_INLINE are incompatible with NOINLINE.  In the
middle-end the DECL_UNINLINABLE flag set by NOINLINE always wins, so the
inline request would be silently ignored.  To avoid that surprise the
directive parser warns and drops the inline attribute when both are
applied to the same procedure, whether in one directive or in two.

The new EXT_ATTR_* values are appended at the end of the enum because the
symbol_attribute.ext_attr bitmask is written to module files.  Appending
preserves the existing bit positions and keeps modules written by older
gfortran readable.

Bootstrapped and regtested on x86_64-pc-linux-gnu with
--enable-languages=c,c++,fortran.  All three stages built and the
stage 2 and 3 comparison succeeded.  The gfortran testsuite shows no
regressions (75660 expected passes, 339 expected failures, 0 unexpected
failures) and the four new tests pass.

gcc/fortran/ChangeLog:

	* gfortran.h (enum ext_attr_id_t): Add EXT_ATTR_INLINE and
	EXT_ATTR_ALWAYS_INLINE at the end.  Document that new attributes
	must be appended to preserve module compatibility.
	* decl.cc (ext_attr_list): Add "inline" and "always_inline".
	(gfc_match_gcc_attributes): Warn about and drop INLINE or
	ALWAYS_INLINE when combined with NOINLINE on the same symbol.
	* trans-decl.cc (build_function_decl): Handle EXT_ATTR_INLINE and
	EXT_ATTR_ALWAYS_INLINE by setting DECL_DECLARED_INLINE_P, and
	DECL_DISREGARD_INLINE_LIMITS for ALWAYS_INLINE.
	* gfortran.texi (ATTRIBUTES directive): Document INLINE and
	ALWAYS_INLINE.

gcc/testsuite/ChangeLog:

	* gfortran.dg/always_inline_1.f90: New test.
	* gfortran.dg/always_inline_2.f90: New test.
	* gfortran.dg/inline_1.f90: New test.
	* gfortran.dg/inline_2.f90: New test.

Signed-off-by: Henri Menke <henri@henrimenke.de>
---
 gcc/fortran/decl.cc                           | 23 ++++++++++++++++
 gcc/fortran/gfortran.h                        | 10 ++++++-
 gcc/fortran/gfortran.texi                     |  8 ++++++
 gcc/fortran/trans-decl.cc                     | 10 +++++++
 gcc/testsuite/gfortran.dg/always_inline_1.f90 | 26 ++++++++++++++++++
 gcc/testsuite/gfortran.dg/always_inline_2.f90 | 14 ++++++++++
 gcc/testsuite/gfortran.dg/inline_1.f90        | 27 +++++++++++++++++++
 gcc/testsuite/gfortran.dg/inline_2.f90        | 14 ++++++++++
 8 files changed, 131 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/gfortran.dg/always_inline_1.f90
 create mode 100644 gcc/testsuite/gfortran.dg/always_inline_2.f90
 create mode 100644 gcc/testsuite/gfortran.dg/inline_1.f90
 create mode 100644 gcc/testsuite/gfortran.dg/inline_2.f90

diff --git a/gcc/fortran/decl.cc b/gcc/fortran/decl.cc
index b81d81b2dd1..8b1bb8240f5 100644
--- a/gcc/fortran/decl.cc
+++ b/gcc/fortran/decl.cc
@@ -12849,6 +12849,8 @@  const ext_attr_t ext_attr_list[] = {
   { "noinline",     EXT_ATTR_NOINLINE,     NULL	       },
   { "noreturn",     EXT_ATTR_NORETURN,     NULL	       },
   { "weak",	    EXT_ATTR_WEAK,	   NULL	       },
+  { "inline",       EXT_ATTR_INLINE,       NULL        },
+  { "always_inline",EXT_ATTR_ALWAYS_INLINE,NULL        },
   { NULL,           EXT_ATTR_LAST,         NULL        }
 };
 
@@ -12925,6 +12927,27 @@  gfc_match_gcc_attributes (void)
 
       sym->attr.ext_attr |= attr.ext_attr;
 
+      /* INLINE and ALWAYS_INLINE are incompatible with NOINLINE.  In the
+	 middle-end the DECL_UNINLINABLE flag set by NOINLINE always wins, so
+	 the inline request would be silently ignored.  Warn and drop it.  */
+      if (sym->attr.ext_attr & (1 << EXT_ATTR_NOINLINE))
+	{
+	  if (sym->attr.ext_attr & (1 << EXT_ATTR_ALWAYS_INLINE))
+	    {
+	      gfc_warning (0, "Attribute %<ALWAYS_INLINE%> at %C is "
+			   "incompatible with %<NOINLINE%> for %qs and will "
+			   "be ignored", sym->name);
+	      sym->attr.ext_attr &= ~(1 << EXT_ATTR_ALWAYS_INLINE);
+	    }
+	  if (sym->attr.ext_attr & (1 << EXT_ATTR_INLINE))
+	    {
+	      gfc_warning (0, "Attribute %<INLINE%> at %C is incompatible "
+			   "with %<NOINLINE%> for %qs and will be ignored",
+			   sym->name);
+	      sym->attr.ext_attr &= ~(1 << EXT_ATTR_INLINE);
+	    }
+	}
+
       if (gfc_match_eos () == MATCH_YES)
 	break;
 
diff --git a/gcc/fortran/gfortran.h b/gcc/fortran/gfortran.h
index 8328ff8b34b..d8d8f3faae2 100644
--- a/gcc/fortran/gfortran.h
+++ b/gcc/fortran/gfortran.h
@@ -877,7 +877,13 @@  enum gfc_omp_at_type
   OMP_AT_EXECUTION
 };
 
-/* Structure and list of supported extension attributes.  */
+/* Structure and list of supported extension attributes.
+
+   The bitmask formed from these values (symbol_attribute.ext_attr) is
+   written to and read from module files, see mio_symbol_attribute.  New
+   attributes must therefore be appended at the end (before EXT_ATTR_LAST)
+   so that the existing bit positions, and thus module compatibility, are
+   preserved.  */
 typedef enum
 {
   EXT_ATTR_DLLIMPORT = 0,
@@ -890,6 +896,8 @@  typedef enum
   EXT_ATTR_NOINLINE,
   EXT_ATTR_NORETURN,
   EXT_ATTR_WEAK,
+  EXT_ATTR_INLINE,
+  EXT_ATTR_ALWAYS_INLINE,
   EXT_ATTR_LAST, EXT_ATTR_NUM = EXT_ATTR_LAST
 }
 ext_attr_id_t;
diff --git a/gcc/fortran/gfortran.texi b/gcc/fortran/gfortran.texi
index 12b95f4b19b..c9f45df1882 100644
--- a/gcc/fortran/gfortran.texi
+++ b/gcc/fortran/gfortran.texi
@@ -3404,6 +3404,14 @@  requires an explicit interface.
 deprecated procedure, variable or parameter; the warning can be suppressed
 with @option{-Wno-deprecated-declarations}.
 @item @code{NOINLINE} -- prevent inlining given function.
+@item @code{INLINE} -- hint that the given function should be inlined, while
+still respecting the inlining size limits, corresponding to the C @code{inline}
+keyword.
+@item @code{ALWAYS_INLINE} -- force inlining of a given function, ignoring the
+inlining size limits.  This is the counterpart of @code{NOINLINE} and
+corresponds to the C @code{always_inline} attribute.  @code{INLINE} and
+@code{ALWAYS_INLINE} are incompatible with @code{NOINLINE}.  Specifying both
+for the same procedure makes the inline attribute ignored with a warning.
 @item @code{NORETURN} -- add a hint that a given function cannot return.
 @item @code{WEAK} -- emit the declaration of an external symbol as a weak
 symbol rather than a global.  This is primarily useful in defining library
diff --git a/gcc/fortran/trans-decl.cc b/gcc/fortran/trans-decl.cc
index ea12be5dd25..42a5c06d5f0 100644
--- a/gcc/fortran/trans-decl.cc
+++ b/gcc/fortran/trans-decl.cc
@@ -2673,6 +2673,16 @@  build_function_decl (gfc_symbol * sym, bool global)
   if (attr.ext_attr & (1 << EXT_ATTR_NOINLINE))
     DECL_UNINLINABLE (fndecl) = 1;
 
+  /* Mark inline functions.  Fortran has no 'inline' keyword, so both INLINE
+     and ALWAYS_INLINE set DECL_DECLARED_INLINE_P explicitly.  ALWAYS_INLINE
+     additionally disregards the inliner's size limits.  Setting only that
+     would make the middle-end warn that the always-inline function "might
+     not be inlinable".  */
+  if (attr.ext_attr & ((1 << EXT_ATTR_INLINE) | (1 << EXT_ATTR_ALWAYS_INLINE)))
+    DECL_DECLARED_INLINE_P (fndecl) = 1;
+  if (attr.ext_attr & (1 << EXT_ATTR_ALWAYS_INLINE))
+    DECL_DISREGARD_INLINE_LIMITS (fndecl) = 1;
+
   /* Mark noreturn functions.  */
   if (attr.ext_attr & (1 << EXT_ATTR_NORETURN))
     TREE_THIS_VOLATILE (fndecl) = 1;
diff --git a/gcc/testsuite/gfortran.dg/always_inline_1.f90 b/gcc/testsuite/gfortran.dg/always_inline_1.f90
new file mode 100644
index 00000000000..fce9ce0781e
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/always_inline_1.f90
@@ -0,0 +1,26 @@ 
+! { dg-do compile }
+! { dg-options "-O -fdump-tree-optimized" }
+!
+! Verify that !GCC$ ATTRIBUTES always_inline forces inlining: the helper
+! procedure is inlined into its caller and no standalone call remains.
+
+subroutine caller(a, n)
+  implicit none
+  integer, intent(in) :: n
+  real, intent(inout) :: a(n)
+  call helper(a, n)
+  call helper(a, n)
+contains
+  subroutine helper(x, m)
+    implicit none
+    integer, intent(in) :: m
+    real, intent(inout) :: x(m)
+!GCC$ ATTRIBUTES always_inline :: helper
+    integer :: i
+    do i = 1, m
+      x(i) = x(i) + 1.0
+    end do
+  end subroutine helper
+end subroutine caller
+
+! { dg-final { scan-tree-dump-not "helper" "optimized" } }
diff --git a/gcc/testsuite/gfortran.dg/always_inline_2.f90 b/gcc/testsuite/gfortran.dg/always_inline_2.f90
new file mode 100644
index 00000000000..2f6040865e6
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/always_inline_2.f90
@@ -0,0 +1,14 @@ 
+! { dg-do compile }
+!
+! ALWAYS_INLINE is incompatible with NOINLINE.  Specifying both for the same
+! procedure must warn and drop ALWAYS_INLINE, whether they appear in one
+! directive or in separate directives.
+
+subroutine foo()
+!GCC$ ATTRIBUTES always_inline, noinline :: foo  ! { dg-warning "ALWAYS_INLINE. at .1. is incompatible with .NOINLINE." }
+end subroutine foo
+
+subroutine bar()
+!GCC$ ATTRIBUTES always_inline :: bar
+!GCC$ ATTRIBUTES noinline :: bar  ! { dg-warning "ALWAYS_INLINE. at .1. is incompatible with .NOINLINE." }
+end subroutine bar
diff --git a/gcc/testsuite/gfortran.dg/inline_1.f90 b/gcc/testsuite/gfortran.dg/inline_1.f90
new file mode 100644
index 00000000000..6dc59bf6e2a
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/inline_1.f90
@@ -0,0 +1,27 @@ 
+! { dg-do compile }
+! { dg-options "-O2 -fno-inline-small-functions -fno-inline-functions -fno-inline-functions-called-once -fdump-tree-optimized" }
+!
+! With all automatic inlining disabled the helper procedure would not be
+! inlined, but the INLINE attribute marks it as declared-inline, so it is
+! inlined anyway and no standalone call remains.
+
+subroutine caller(a, n)
+  implicit none
+  integer, intent(in) :: n
+  real, intent(inout) :: a(n)
+  call helper(a, n)
+  call helper(a, n)
+contains
+  subroutine helper(x, m)
+    implicit none
+    integer, intent(in) :: m
+    real, intent(inout) :: x(m)
+!GCC$ ATTRIBUTES inline :: helper
+    integer :: i
+    do i = 1, m
+      x(i) = x(i) + 1.0
+    end do
+  end subroutine helper
+end subroutine caller
+
+! { dg-final { scan-tree-dump-not "helper" "optimized" } }
diff --git a/gcc/testsuite/gfortran.dg/inline_2.f90 b/gcc/testsuite/gfortran.dg/inline_2.f90
new file mode 100644
index 00000000000..60da68c947b
--- /dev/null
+++ b/gcc/testsuite/gfortran.dg/inline_2.f90
@@ -0,0 +1,14 @@ 
+! { dg-do compile }
+!
+! INLINE is incompatible with NOINLINE.  Specifying both for the same
+! procedure must warn and drop INLINE, whether they appear in one directive
+! or in separate directives.
+
+subroutine foo()
+!GCC$ ATTRIBUTES inline, noinline :: foo  ! { dg-warning "INLINE. at .1. is incompatible with .NOINLINE." }
+end subroutine foo
+
+subroutine bar()
+!GCC$ ATTRIBUTES inline :: bar
+!GCC$ ATTRIBUTES noinline :: bar  ! { dg-warning "INLINE. at .1. is incompatible with .NOINLINE." }
+end subroutine bar
-- 
2.54.0