On Sat, 13 Jun 2026, Martin Uecker wrote:
>
> (I am still in awe that GCC can fully optimize the first example
> with no other changes required.)
>
>
>
> Bootstrapped and regression tested on x86_64.
>
>
>
> Built-ins to access code pointer and static chain of nested function.
>
> This patch adds two new built-ins, __builtin_call_code_pointer and
> __builtin_call_static chain, to extract the code pointer and the static
> chain pointer from a (nested) function, respectively. Those can then be
> used to call the nested function using the existing built-in
> __builtin_call_with_static_chain. This feature can be used to avoid the
> creation of trampolines and often allows writing more efficient
> code, e.g. where trampolines prevent devirtualization (PR49666).
>
> gcc/ChangeLog:
> * builtins.cc (expand_builtin): Mark new cases as unreachable.
> (is_simple_builtin): Add new built-in functions.
> * gimple-fold.cc (gimple_fold_builtin_call_codeptr): New function.
> (gimple_fold_builtin_call_static_chain): New function.
> (gimple_fold_builtin): Expand new built-ins for non-nested
> functions.
> * tree-nested.cc (convert_tramp_reference_stmt): Ingore new
> built-ins.
> (convert_gimple_call): Expand built-ins for nested functions.
> * tree-inline.cc (initialize_inlined_parameters): Replace
> assertion with error.
>
> gcc/doc/ChangeLog:
> * extend.texi: Document new built-in functions.
>
> gcc/testsuite/ChangeLog:
> * gcc.dg/builtin-call-info-1.c: New test.
> * gcc.dg/builtin-call-info-2.c: New test.
> * gcc.dg/builtin-call-info-3.c: New test.
>
> diff --git a/gcc/builtins.cc b/gcc/builtins.cc
> index 52f3ee029d0..78610b5919b 100644
> --- a/gcc/builtins.cc
> +++ b/gcc/builtins.cc
> @@ -8092,6 +8092,12 @@ expand_builtin (tree exp, rtx target, rtx subtarget,
> machine_mode mode,
> expand_builtin_return (expand_normal (CALL_EXPR_ARG (exp, 0)));
> return const0_rtx;
>
> + case BUILT_IN_CALL_CODE_ADDRESS:
> + gcc_unreachable ();
> +
> + case BUILT_IN_CALL_STATIC_CHAIN:
> + gcc_unreachable ();
> +
> case BUILT_IN_SAVEREGS:
> return expand_builtin_saveregs ();
>
> @@ -12368,6 +12374,8 @@ is_simple_builtin (tree decl)
> case BUILT_IN_STACK_SAVE:
> case BUILT_IN_STACK_RESTORE:
> case BUILT_IN_DWARF_CFA:
> + case BUILT_IN_CALL_CODE_ADDRESS:
> + case BUILT_IN_CALL_STATIC_CHAIN:
> /* Exception state returns or moves registers around. */
> case BUILT_IN_EH_FILTER:
> case BUILT_IN_EH_POINTER:
> diff --git a/gcc/builtins.def b/gcc/builtins.def
> index e39c3427d77..4adeb440ed9 100644
> --- a/gcc/builtins.def
> +++ b/gcc/builtins.def
> @@ -1170,6 +1170,10 @@ DEF_BUILTIN_STUB (BUILT_IN_NONLOCAL_GOTO,
> "__builtin_nonlocal_goto")
> DEF_EXT_LIB_BUILTIN (BUILT_IN_GCC_NESTED_PTR_CREATED,
> "__gcc_nested_func_ptr_created", BT_FN_VOID_PTR_PTR_PTR, ATTR_NOTHROW_LIST)
> DEF_EXT_LIB_BUILTIN (BUILT_IN_GCC_NESTED_PTR_DELETED,
> "__gcc_nested_func_ptr_deleted", BT_FN_VOID, ATTR_NOTHROW_LIST)
>
> +/* Information needed to call (nested) functions. */
> +DEF_GCC_BUILTIN (BUILT_IN_CALL_CODE_ADDRESS, "call_code_address",
> BT_FN_PTR_PTR, ATTR_NULL)
> +DEF_GCC_BUILTIN (BUILT_IN_CALL_STATIC_CHAIN, "call_static_chain",
> BT_FN_PTR_PTR, ATTR_NULL)
> +
> /* Implementing __builtin_setjmp. */
> DEF_BUILTIN_STUB (BUILT_IN_SETJMP_SETUP, "__builtin_setjmp_setup")
> DEF_BUILTIN_STUB (BUILT_IN_SETJMP_RECEIVER, "__builtin_setjmp_receiver")
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index 9f821f04853..2fdd15c6ee8 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -16681,6 +16681,25 @@ This builtin can be used to call Go closures from C.
>
> @enddefbuiltin
>
> ++@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?
> ++is needed.
> ++
> ++@enddefbuiltin
> ++
> ++@defbuiltin{@var{type} __builtin_call_code_address (@var{pointer_exp})}
> ++
> ++The @var{pointer_exp} expression must designate a function.
> ++The result is the static address of the function. For a nested function,
> ++the address represents the address of the underlying machine code and
> ++not of a trampoline that would otherwise be generated to setup the
> ++static chain.
> ++
> ++@enddefbuiltin
> +
That looks like likes have a leading '+'?
We might want to cross-reference __builtin_call_with_static_chain?
Possibly even with an example?
> @node Return Address
> @section Getting the Return or Frame Address of a Function
>
> diff --git a/gcc/gimple-fold.cc b/gcc/gimple-fold.cc
> index fab4886b0f2..f96dea6e498 100644
> --- a/gcc/gimple-fold.cc
> +++ b/gcc/gimple-fold.cc
> @@ -5409,6 +5409,58 @@ gimple_fold_builtin_stdarg (gimple_stmt_iterator *gsi,
> gcall *call)
> }
> }
>
> +/* 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?
> + }
> +
> + /* 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?
> +
> + replace_call_with_value (gsi, arg);
> + return true;
> +}
> +
> +
> +/* Fold __builtin_call_static_chain builtin. This handles only the
> + trivial left-over cases not processed in tree-nested.cc. */
> +
> +static bool
> +gimple_fold_builtin_call_static_chain (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)))
> + {
> + error_at (gimple_location (stmt),
> + "argument to %<__builtin_call_static_chain%> "
> + "must be a function");
> + return false;
See above. Maybe have a single folding function and only dispatch
the replace_call_with_value on the built-in code?
> + }
> +
> + /* The case with static chain is handled in tree-nested.cc. */
> + gcc_assert (!DECL_STATIC_CHAIN (TREE_OPERAND (arg, 0)));
> +
> + replace_call_with_value (gsi, null_pointer_node);
> + return true;
> +}
> +
> +
> /* Fold the non-target builtin at *GSI and return whether any simplification
> was made. */
>
> @@ -5589,6 +5641,12 @@ gimple_fold_builtin (gimple_stmt_iterator *gsi)
> case BUILT_IN_CONSTANT_P:
> return gimple_fold_builtin_constant_p (gsi);
>
> + case BUILT_IN_CALL_CODE_ADDRESS:
> + return gimple_fold_builtin_call_code_address (gsi);
> +
> + case BUILT_IN_CALL_STATIC_CHAIN:
> + return gimple_fold_builtin_call_static_chain (gsi);
> +
> default:;
> }
>
> diff --git a/gcc/testsuite/gcc.dg/builtin-call-info-1.c
> b/gcc/testsuite/gcc.dg/builtin-call-info-1.c
> new file mode 100644
> index 00000000000..ecfe3a62c9a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-call-info-1.c
> @@ -0,0 +1,34 @@
> +/* { dg-do run } */
> +/* { dg-options "-Wtrampolines" } */
> +
> +typedef struct {
> + int (*code)(int);
> + void *chain;
> +} call_info_t;
> +
> +int apply(call_info_t c, int value)
> +{
> + return __builtin_call_with_static_chain(c.code(value), c.chain);
> +}
> +
> +int foo(int x)
> +{
> + int add(int y)
> + {
> + return x + y;
> + }
> +
> + call_info_t info = {
> + __builtin_call_code_address(add),
> + __builtin_call_static_chain(add)
> + };
> +
> + return apply(info, x);
> +}
> +
> +int main()
> +{
> + if (4 != foo(2))
> + __builtin_abort();
> +}
> +
> diff --git a/gcc/testsuite/gcc.dg/builtin-call-info-2.c
> b/gcc/testsuite/gcc.dg/builtin-call-info-2.c
> new file mode 100644
> index 00000000000..acb6ad98382
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-call-info-2.c
> @@ -0,0 +1,47 @@
> +/* { dg-do run } */
> +/* { dg-options "-Wtrampolines" } */
> +
> +/* Check that we get the expected pointers in
> + different context. */
> +
> +int f(int x)
> +{
> +
> + if (f != __builtin_call_code_address(f))
> + __builtin_abort();
> +
> + if ((void*)0 != __builtin_call_static_chain(f))
> + __builtin_abort();
> +
> + static int (*code)(int);
> + static void *chain;
> +
> + int g(int y)
> + {
> + if (code != __builtin_call_code_address(g))
> + __builtin_abort();
> +
> + if (chain != __builtin_call_static_chain(g))
> + __builtin_abort();
> +
> + return x + y;
> + }
> +
> + code = __builtin_call_code_address(g);
> + chain = __builtin_call_static_chain(g);
> +
> + return g(x);
> +}
> +
> +int main()
> +{
> + if (f != __builtin_call_code_address(f))
> + __builtin_abort();
> +
> + if ((void*)0 != __builtin_call_static_chain(f))
> + __builtin_abort();
> +
> + if (6 != f(3))
> + __builtin_abort();
> +}
> +
> diff --git a/gcc/testsuite/gcc.dg/builtin-call-info-3.c
> b/gcc/testsuite/gcc.dg/builtin-call-info-3.c
> new file mode 100644
> index 00000000000..70101c5fdee
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/builtin-call-info-3.c
> @@ -0,0 +1,24 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -Wtrampolines -Wreturn-local-addr" } */
> +
> +
> +void * foo(int n)
> +{
> + int g(int x)
> + {
> + return n + x;
> + }
> +
> + return __builtin_call_code_address(g); // ok
> +}
> +
> +void * bar(int n)
> +{
> + int g(int x)
> + {
> + return n + x;
> + }
> +
> + return __builtin_call_static_chain(g); /* { dg-warning "returns
> address of local variable" } */
> +}
> +
> 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)
> 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?
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)