Am Mittwoch, dem 17.06.2026 um 09:22 +0200 schrieb Richard Biener:
> On Tue, 16 Jun 2026, Martin Uecker wrote:
> > 

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

It seems important not to pessimize the code, so
I will document this as run-time UB.  I don't think it is
surprising for a user that calling a function that needs
a static chain without setting it up can not work.

Where we can set the static chain to NULL it should give 
a clean fault, but I can later investigate whether
catching this as suggested above can be done.


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

Yes, an attribute could be a start. The problem with attributes
is that the rules for type attributes are not very clear (but 
this is then on us to define this properly).

In the longer run, what I want is a wide pointer type that
is is a pair of code ptr and data ptr, similar to C++'s
std::function_ref (and this could have very good interoperability
with C++ and other languages).


In C, this would ideally (IMHO) be modeled  as pointer to a qualified
type, because it should be allowed to convert a regular function pointer
to such a wide pointer but not vice-versa and the qualifier rules
would fit well.  I have a preliminary proposal for WG14 that 
a preliminary design (N2862).


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

Yes, as long as "bar" accesses some non-local context
(so in this specific example it would work).

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

For the current __builtin_call_with_static_chain we can not fix 
this anymore, but with the type extensions above we can later make
this safe. In the meantime I would expect these builtins to be used
only in low-level libraries that expose a safe interface to the
user. At least this is what I plan for my projects.


For a future language feature, I would like to allow conversions from
regular pointers to wide pointers so that new APIs can be used with
regular functions.  

void api_with_callback(int (*cb)(int arg) _Wide),

int foo(int arg) { ... };

void f()
{
  int bar(int arg) { ... };

  api_with_callback(bar); // wide ptr instead of trampoline
  api_with_callback(foo); // also ok, unused static chain
}

For ABIs that do not have a static chain register reserved, 
I think this can be via a static thunk that  forwards the call, 
i.e. the code pointer becomes a pointer to the thunk and the data 
pointer is the pointer to the regular function. But maybe this is 
difficult for variadic functions. 

Do you know which ABIs are affected by this?


Martin

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

-- 
Univ.-Prof. Dr. rer. nat. Martin Uecker
Graz University of Technology
Institute of Biomedical Imaging

Reply via email to