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)