On Tue, 16 Jun 2026, Martin Uecker wrote:

> 
> Thanks for the review!  See below for some answers and question.
> 
> Am Dienstag, dem 16.06.2026 um 10:53 +0200 schrieb Richard Biener:
> > On Sat, 13 Jun 2026, Martin Uecker wrote:
> > > 
> 
> > >  
> > > ++@defbuiltin{@var{type} __builtin_call_static_chain (@var{pointer_exp})}
> > > ++
> > > ++The @var{pointer_exp} expression must designate a function.
> > > ++The result is the static chain pointer that that is needed to call
> > > ++the function call in its current context, or a null pointer if none
> > 
> > the function @var{pointer_exp} in the current context
> > 
> > ?  It's the context of the __builtin_call_static_chain call, right?
> 
> Yes, it is the static chain (context) which would be used if the
> nested function would be called directly at this point.
> 
> 
> 
> ...
> 
> > >  
> > > +/* Fold __builtin_call_code_address builtin.  This handles only the
> > > +   trivial left-over cases not processed in tree-nested.cc.  */
> > > +
> > > +static bool
> > > +gimple_fold_builtin_call_code_address (gimple_stmt_iterator *gsi)
> > > +{
> > > +  gcall *stmt = as_a <gcall *>(gsi_stmt (*gsi));
> > > +  tree arg = gimple_call_arg (stmt, 0);
> > > +
> > > +  if (TREE_CODE (arg) != ADDR_EXPR || !DECL_P (TREE_OPERAND (arg, 0))
> > > +      || FUNCTION_DECL != TREE_CODE (TREE_OPERAND (arg, 0)))
> > 
> > !DECL_P || != FUNCTION_DECL makes the !DECL_P check redundant.
> > 
> > > +    {
> > > +      error_at (gimple_location (stmt),
> > > +         "argument to %<__builtin_call_code_address%> "
> > > +         "must be a function");
> > 
> > Hmm, late errors ...
> > 
> > > +      return false;
> > 
> > ... and this means you'd repeatedly emit it.  I'd suggest to
> > at least replace the call with something else, like, for
> > example a trap?
> 
> How about expanding in gimple_fold only in the valid case, and
> then emitting the error in builtins.cc?  This is how it is
> done for some other builtins.

Sure, that would work as well.

> We could also catch this in the FE, but it does not seem
> worth the trouble at this time.
> 
> > > +    }
> > > +
> > > +  /* The case with static chain is handled in tree-nested.cc.  */
> > > +  gcc_assert (!DECL_STATIC_CHAIN (TREE_OPERAND (arg, 0)));
> > 
> > That's a correctness assert?  I just wonder if enough obfuscation,
> > like
> > 
> > foo()
> > {
> >   bar(){}
> > 
> >   void *p = bar;
> >   p = p + 2;
> >   p = p - 2;
> >   x = __builtin_call_code_address (p)
> > 
> > might eventually end us up here, ICEing?
> 
> At this point, tree-nested.cc would have added a
> __builtin_adjust_trampoline already and we would then
> get the error because the argument does not have the
> expected form.  Exluding any such scenario was my
> motivation to make the argument check so restrictive.
> 
> 
> > > diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
> > > index 302ab8d6b7c..9461d531275 100644
> > > --- a/gcc/tree-inline.cc
> > > +++ b/gcc/tree-inline.cc
> > > @@ -3760,8 +3760,8 @@ initialize_inlined_parameters (copy_body_data *id, 
> > > gimple *stmt,
> > >    gcc_assert (fn != current_function_decl);
> > >    if (p)
> > >      {
> > > -      /* No static chain?  Seems like a bug in tree-nested.cc.  */
> > > -      gcc_assert (static_chain);
> > > +      if (!static_chain)
> > > + error ("called function requires a static chain");
> > 
> > but then you simply continue ... I'd rather make sure we do not
> > try inlining such a function, thus the assert does not hit
> > (and we can leave it in place)
> 
> If I check this before inlining, the assertion will never trigger,
> but also not detect bugs in tree-nested.  In this case, I would
> rather remove it.

Fine with me as well.

> The problem here is that the programmer has tried to call a function that
> requires a static chain to be set up using the bare code address.
> Inlining just happens to detect this, so it seems helpful to give
> the error.
> 
> For example, it would give an error in the following example:
> 
> int foo(int y)
> {
>   int b(int x)
>   {
>      return x + y;
>   }
>   
>   void *q = __builtin_call_code_address(b);
> 
>   typeof(b) p = q; // naively cast to the correct type
>   return p(0);           // should use __builtin_call_with_static_chain!
> }
> 
> 
> One could also argue that the programmer should know what he is doing,
> and just allow this.  In this case, one could remove the assertion, set the 
> static chain to NULL and simply continue to inline the function.

I wonder if we'd want to catch this in the isolate-path pass (but
that runs after inlining), which could then replace the direct
call with a trap() and diagnose the bogus call?  Note even RTL
optimization might expose the fndecl, so the issue is that
C doesn't have strong type requirements on function pointer type
(conversions) and/or the function pointer doesn't encode that
it needs a static chain in the first place?

> For example, the following would then be possible:
> 
> int foo(int y)
> {
>   int b(bool flag, int x)
>   {
>      if (flag) return x; // no static chain needed
>      return x + y;
>   }
>   
>   void *q = __builtin_call_code_address(b);
> 
>   typeof(b) p = q;    // just cast to the correct type
>   return p(true, 0);  // for true does not need the chain.
> }
> 
> Maybe the later is preferable for built-ins meant as a
> low-level tool?

I suppose that if calling a function that expects a static
chain without a static chain (again, what about indirect
calls?) is only UB at runtime then we shouldn't diagnose
anything but DWIM?  Btw,

>   int b(bool flag, int x)
>   {
>      if (flag) return x; // no static chain needed
>      return x + y;
>   }

so it's invalid for the compiler to hoist the access to y
before if (flag)?  Because I think we mark accesses via
the static chain as not trapping (aka we assume it's
correctly set up).

I realize nested functions are a GNU extension, so we should
amend our section about UBs in GNU extensions?


> On the language level, I hope that at some point we get some 
> type-safe feature that distinguished between functions pointer
> that require a static chain and those that do not, which would
> prevent this error (when not circumventing this by casts)

It should be possible to mark FUNCTION_TYPE with a flag that
indicates a static chain is required so that

 auto f = __builtin_call_code_address (b);

would get you an 'f' that's appropriately marked?  We could
simply add a new attribute for this which might play nicely
with _Generic and other C features?

> > 
> > >        setup_one_parameter (id, p, static_chain, fn, bb, &vars);
> > >      }
> > > diff --git a/gcc/tree-nested.cc b/gcc/tree-nested.cc
> > > index cdccc51d33e..d478461c1b4 100644
> > > --- a/gcc/tree-nested.cc
> > > +++ b/gcc/tree-nested.cc
> > > @@ -36,6 +36,7 @@
> > >  #include "gimplify.h"
> > >  #include "gimple-iterator.h"
> > >  #include "gimple-walk.h"
> > > +#include "gimple-fold.h"
> > >  #include "tree-cfg.h"
> > >  #include "explow.h"
> > >  #include "langhooks.h"
> > > @@ -2882,6 +2883,12 @@ convert_tramp_reference_stmt (gimple_stmt_iterator 
> > > *gsi, bool *handled_ops_p,
> > >      {
> > >      case GIMPLE_CALL:
> > >        {
> > > + tree decl = gimple_call_fndecl (stmt);
> > > + if (decl && fndecl_built_in_p (decl, BUILT_IN_NORMAL)
> > > +     && (BUILT_IN_CALL_CODE_ADDRESS == DECL_FUNCTION_CODE (decl)
> > > +         || BUILT_IN_CALL_STATIC_CHAIN == DECL_FUNCTION_CODE (decl)))
> > > +   break;
> > > +
> > >   /* Only walk call arguments, lest we generate trampolines for
> > >      direct calls.  */
> > >   unsigned long i, nargs = gimple_call_num_args (stmt);
> > > @@ -2994,11 +3001,59 @@ convert_gimple_call (gimple_stmt_iterator *gsi, 
> > > bool *handled_ops_p,
> > >    switch (gimple_code (stmt))
> > >      {
> > >      case GIMPLE_CALL:
> > > -      if (gimple_call_chain (stmt))
> > > - break;
> > >        decl = gimple_call_fndecl (stmt);
> > >        if (!decl)
> > >   break;
> > > +      if (fndecl_built_in_p (decl, BUILT_IN_NORMAL)
> > > +   && DECL_FUNCTION_CODE (decl) == BUILT_IN_CALL_CODE_ADDRESS)
> > > + {
> > > +   tree d = gimple_call_arg (stmt, 0);
> > > +   tree ret = null_pointer_node;
> > > +   if (TREE_CODE (d) != ADDR_EXPR || !DECL_P (TREE_OPERAND (d, 0))
> > > +       || FUNCTION_DECL != TREE_CODE (TREE_OPERAND (d, 0)))
> > > +     {
> > > +       error_at (gimple_location (stmt),
> > > +                 "argument to %<__builtin_call_code_address%> "
> > > +                 "must be a function");
> > > +     }
> > > +   else
> > > +     {
> > > +       /* Return code pointer.  */
> > > +       ret = build_addr (TREE_OPERAND (d, 0));
> > > +       TREE_NO_TRAMPOLINE (ret) = 1;
> > 
> > Seeing this, fold-const.cc:operand_compare::operand_equal_p does
> > not compare this flag so it would consider trampoline vs.
> > no-trampoline addresses the same.
> > 
> > What happens if you call a __builtin_call_code_address non-trampoline
> > directly indirectly?
> 
> I am not sure I understand the question correctöy, but if the static chain
> is not setup correctly the function will crash if one tries to use it.
> But if we encounter this during inlining, this would run into the error
> above. I am not sure if there other cases to worry about, e.g.
> some interprocedural constant propagation?  I would guess it would treat
> the static chain as unknown when not set, but I am not sure.

I guess it's the same issue discussed above.  Usually

foo()
{
  bar(){}

  auto p = bar;
  p();
}

works correctly, but if you do

  auto p = __builtin_call_code_address (bar);
  p()

it will not.

Depending on the ABI a __builtin_call_with_static_chain (x, y)
with a function x that doesn't expect a static chain might either
just ignore it or wreck its calling convention.

Given it's C I don't think we can do better than making those
cases invoke UB?

Richard.

> 
> Best,
> Martin
> 
> > 
> > Otherwise LGTM, I'd like to see approval from a C FE maintainer as well.
> > 
> > Thanks,
> > Richard.
> > 
> > > +     }
> > > +   replace_call_with_value (gsi, ret);
> > > +   break;
> > > + }
> > > +      else if (fndecl_built_in_p (decl, BUILT_IN_NORMAL)
> > > +        && DECL_FUNCTION_CODE (decl) == BUILT_IN_CALL_STATIC_CHAIN)
> > > + {
> > > +   tree d = gimple_call_arg (stmt, 0);
> > > +   tree ret = null_pointer_node;
> > > +   if (TREE_CODE (d) != ADDR_EXPR || !DECL_P (TREE_OPERAND (d, 0))
> > > +       || FUNCTION_DECL != TREE_CODE (TREE_OPERAND (d, 0)))
> > > +     {
> > > +       error_at (gimple_location (stmt),
> > > +                 "argument to %<__builtin_call_static-chain%> "
> > > +                 "must be a function");
> > > +     }
> > > +   else
> > > +     {
> > > +       decl = TREE_OPERAND (d, 0);
> > > +       target_context = decl_function_context (decl);
> > > +       if (target_context && DECL_STATIC_CHAIN (decl))
> > > +         {
> > > +           /* Return static chain.  */
> > > +           info->static_chain_added
> > > +             |= (1 << (info->context != target_context));
> > > +           ret = get_static_chain (info, target_context, &wi->gsi);
> > > +         }
> > > +     }
> > > +   replace_call_with_value (gsi, ret);
> > > +   break;
> > > + }
> > > +      if (gimple_call_chain (stmt))
> > > + break;
> > >        target_context = decl_function_context (decl);
> > >        if (target_context && DECL_STATIC_CHAIN (decl))
> > >   {
> > > 
> 

-- 
Richard Biener <[email protected]>
SUSE Software Solutions Germany GmbH,
Frankenstrasse 146, 90461 Nuernberg, Germany;
GF: Jochen Jaser, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)

Reply via email to