On Wed, 17 Jun 2026, Martin Uecker wrote:

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

No, I don't know.

Richard.

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

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