On Fri, 12 Jun 2026, Martin Uecker wrote:
>
>
> I think a version with two separate builtins would be nicer
> from a user perspective and avoid misuse of the implied
> structure type.
>
> @Richard, Would you also be ok with this? Then
> I would create a new version but with the folding change.
Sure.
> But both versions seem fine to me.
>
>
> For me this has some urgency as I have an important project
> that uses the subset of Clang's blocks and GNU nested functions,
> but for GCC this currently requires trampolines and executable
> stack which is an issue.
>
> Also as it is numerical code, the optimization issue with trampolines
> is a real problem that currently blocks some improvements to the code
> base.
>
> Martin
>
>
> Am Sonntag, dem 31.05.2026 um 17:25 +0200 schrieb Martin Uecker:
> > Any comments on this patch?
> >
> >
> >
> > Best
> > Martin
> >
> >
> > Am Samstag, dem 18.04.2026 um 12:44 +0200 schrieb Martin Uecker:
> > > This is how this patch could look like with a single builtin that returns
> > > an anonymous structure. I named it __builtin_call_info which might be a
> > > bit
> > > nicer in combination with __builtin_call_with_static_chain. I also now
> > > fold
> > > this in gimple-fold.cc for the trivial case where there is no static
> > > chain.
> > >
> > > Bootstrapped and regression tested on x86_64. No AI was used.
> > >
> > > Best,
> > > Martin
> > >
> > >
> > >
> > >
> > >
> > > c: Built-in to access code pointer and static chain of nested
> > > function.
> > >
> > > This patch adds a new built-in, __builtin_call_info, to extract the
> > > code
> > > pointer and the static chain pointer from a (nested) function. 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. when trampolines prevent devirtualization (PR49666).
> > >
> > > gcc/ChangeLog:
> > > * builtins.def: Add new built-in.
> > > * builtin-types.def: Add new call info type.
> > > * tree.h: Add new call info type.
> > > * tree-core.h: Add new call info type.
> > > * tree.cc (build_common_tree_nodes): Build new call info type
> > > node.
> > > * builtins.cc (expand_builtin): Mark new case as unreachable.
> > > (is_simple_builtin): Add new built-in.
> > > * gimple-fold.cc (gimple_fold_builtin_call_info): New
> > > function.
> > > (gimple_fold_builtin) Expand new built-in for non-nested
> > > functions.
> > > * tree-nested.cc (convert_tramp_reference_stmt): Ingore new
> > > built-in.
> > > (convert_gimple_call): Expand built-in for nested functions.
> > > * tree-inline.cc (initialize_inlined_parameters): Replace
> > > assertion with error.
> > >
> > > gcc/doc/ChangeLog:
> > > * extend.texi: Document new built-in.
> > >
> > > 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/builtin-types.def b/gcc/builtin-types.def
> > > index ab0cbc65cfc..1ca220d1ce1 100644
> > > --- a/gcc/builtin-types.def
> > > +++ b/gcc/builtin-types.def
> > > @@ -209,6 +209,8 @@ DEF_PRIMITIVE_TYPE (BT_DFLOAT64X, (dfloat64x_type_node
> > > DEF_PRIMITIVE_TYPE (BT_VALIST_REF, va_list_ref_type_node)
> > > DEF_PRIMITIVE_TYPE (BT_VALIST_ARG, va_list_arg_type_node)
> > >
> > > +DEF_PRIMITIVE_TYPE (BT_CALL_INFO, call_info_type_node)
> > > +
> > > DEF_PRIMITIVE_TYPE (BT_I1, builtin_type_for_size (BITS_PER_UNIT*1, 1))
> > > DEF_PRIMITIVE_TYPE (BT_I2, builtin_type_for_size (BITS_PER_UNIT*2, 1))
> > > DEF_PRIMITIVE_TYPE (BT_I4, builtin_type_for_size (BITS_PER_UNIT*4, 1))
> > > @@ -425,6 +427,7 @@ DEF_FUNCTION_TYPE_1 (BT_FN_UINT16_UINT32, BT_UINT16,
> > > BT_UINT32)
> > > DEF_FUNCTION_TYPE_1 (BT_FN_UINT32_UINT16, BT_UINT32, BT_UINT16)
> > > DEF_FUNCTION_TYPE_1 (BT_FN_INT_FENV_T_PTR, BT_INT, BT_FENV_T_PTR)
> > > DEF_FUNCTION_TYPE_1 (BT_FN_INT_CONST_FENV_T_PTR, BT_INT,
> > > BT_CONST_FENV_T_PTR)
> > > +DEF_FUNCTION_TYPE_1 (BT_FN_CALL_INFO_PTR, BT_CALL_INFO, BT_PTR)
> > >
> > > DEF_POINTER_TYPE (BT_PTR_FN_VOID_PTR, BT_FN_VOID_PTR)
> > >
> > > diff --git a/gcc/builtins.cc b/gcc/builtins.cc
> > > index 692e20088c2..efef8afce69 100644
> > > --- a/gcc/builtins.cc
> > > +++ b/gcc/builtins.cc
> > > @@ -8093,6 +8093,9 @@ 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_INFO:
> > > + gcc_unreachable ();
> > > +
> > > case BUILT_IN_SAVEREGS:
> > > return expand_builtin_saveregs ();
> > >
> > > @@ -12311,6 +12314,7 @@ is_simple_builtin (tree decl)
> > > case BUILT_IN_STACK_SAVE:
> > > case BUILT_IN_STACK_RESTORE:
> > > case BUILT_IN_DWARF_CFA:
> > > + case BUILT_IN_CALL_INFO:
> > > /* 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 8ab0599b17f..1d5bff152e3 100644
> > > --- a/gcc/builtins.def
> > > +++ b/gcc/builtins.def
> > > @@ -1164,6 +1164,9 @@ 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_INFO, "call_info", BT_FN_CALL_INFO_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 47b0bdf1340..2ddcd1f30d6 100644
> > > --- a/gcc/doc/extend.texi
> > > +++ b/gcc/doc/extend.texi
> > > @@ -16610,6 +16610,19 @@ This builtin can be used to call Go closures
> > > from C.
> > >
> > > @enddefbuiltin
> > >
> > > +@defbuiltin{@var{type} __builtin_call_info (@var{pointer_exp})}
> > > +
> > > +The @var{pointer_exp} expression must designate a function.
> > > +The result is a structure with two members of pointer type named
> > > @code{code}
> > > +and @code{chain}. @code{code} holds 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. @code{chain} is the chain pointer that that is
> > > +needed to call the function call in its current context, or a null
> > > pointer
> > > +if none is needed.
> > > +
> > > +@enddefbuiltin
> > > +
> > > @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 e9019e2c7bd..d8eceb080be 100644
> > > --- a/gcc/gimple-fold.cc
> > > +++ b/gcc/gimple-fold.cc
> > > @@ -5354,6 +5354,41 @@ gimple_fold_builtin_stdarg (gimple_stmt_iterator
> > > *gsi, gcall *call)
> > > }
> > > }
> > >
> > > +/* Fold __builtin_call_info builtin. This handles only the trivial
> > > + left-over cases not processed in tree-nested.cc. */
> > > +
> > > +static bool
> > > +gimple_fold_builtin_call_info (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_info%> must be a function");
> > > + return false;
> > > + }
> > > +
> > > + /* The case with static chain is handled in tree-nested.cc. */
> > > + gcc_assert (!DECL_STATIC_CHAIN (TREE_OPERAND (arg, 0)));
> > > +
> > > + tree fields = TYPE_FIELDS (call_info_type_node);
> > > + tree ret = create_tmp_var (call_info_type_node);
> > > + tree cref2 = build3 (COMPONENT_REF, TREE_TYPE (fields),
> > > + ret, fields, NULL_TREE);
> > > + tree cref1 = build3 (COMPONENT_REF, TREE_TYPE (TREE_CHAIN (fields)),
> > > + ret, TREE_CHAIN (fields), NULL_TREE);
> > > + gimple *g = gimple_build_assign (cref1, null_pointer_node);
> > > + gsi_insert_before (gsi, g, GSI_SAME_STMT);
> > > + g = gimple_build_assign (cref2, arg);
> > > + gsi_insert_before (gsi, g, GSI_SAME_STMT);
> > > +
> > > + replace_call_with_value (gsi, ret);
> > > + return true;
> > > +}
> > > +
> > > /* Fold the non-target builtin at *GSI and return whether any
> > > simplification
> > > was made. */
> > >
> > > @@ -5530,6 +5565,9 @@ gimple_fold_builtin (gimple_stmt_iterator *gsi)
> > > case BUILT_IN_CONSTANT_P:
> > > return gimple_fold_builtin_constant_p (gsi);
> > >
> > > + case BUILT_IN_CALL_INFO:
> > > + return gimple_fold_builtin_call_info (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..24ac8b1ccf2
> > > --- /dev/null
> > > +++ b/gcc/testsuite/gcc.dg/builtin-call-info-1.c
> > > @@ -0,0 +1,26 @@
> > > +/* { dg-do run } */
> > > +/* { dg-options "-Wtrampolines" } */
> > > +
> > > +typedef typeof(__builtin_call_info(nullptr)) call_info_t;
> > > +
> > > +int apply(call_info_t c, int value)
> > > +{
> > > + return __builtin_call_with_static_chain(((int(*)(int))c.code)(value),
> > > c.chain);
> > > +}
> > > +
> > > +int foo(int x)
> > > +{
> > > + int add(int y)
> > > + {
> > > + return x + y;
> > > + }
> > > +
> > > + return apply(__builtin_call_info(add), 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..0ec8d865971
> > > --- /dev/null
> > > +++ b/gcc/testsuite/gcc.dg/builtin-call-info-2.c
> > > @@ -0,0 +1,49 @@
> > > +/* { dg-do run } */
> > > +/* { dg-options "-Wtrampolines" } */
> > > +
> > > +/* Check that we get the expected pointers in
> > > + different context. */
> > > +
> > > +int f(int x)
> > > +{
> > > + static typeof(__builtin_call_info(f)) c;
> > > + c = __builtin_call_info(f);
> > > +
> > > + if (f != c.code)
> > > + __builtin_abort();
> > > +
> > > + if ((void*)0 != c.chain)
> > > + __builtin_abort();
> > > +
> > > + int g(int y)
> > > + {
> > > + auto c2 = __builtin_call_info(g);
> > > +
> > > + if (c.code != c2.code)
> > > + __builtin_abort();
> > > +
> > > + if (c.chain != c2.chain)
> > > + __builtin_abort();
> > > +
> > > + return x + y;
> > > + }
> > > +
> > > + c = __builtin_call_info(g);
> > > +
> > > + return g(x);
> > > +}
> > > +
> > > +int main()
> > > +{
> > > + auto c = __builtin_call_info(f);
> > > +
> > > + if (f != c.code)
> > > + __builtin_abort();
> > > +
> > > + if ((void*)0 != c.chain)
> > > + __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..5d78892fefb
> > > --- /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_info(g).code; // ok
> > > +}
> > > +
> > > +void * bar(int n)
> > > +{
> > > + int g(int x)
> > > + {
> > > + return n + x;
> > > + }
> > > +
> > > + return __builtin_call_info(g).chain; /* { dg-warning "returns
> > > address of local variable" } */
> > > +}
> > > +
> > > diff --git a/gcc/tree-core.h b/gcc/tree-core.h
> > > index 07e9318f5e8..0a3740b060a 100644
> > > --- a/gcc/tree-core.h
> > > +++ b/gcc/tree-core.h
> > > @@ -822,6 +822,7 @@ enum tree_index : unsigned {
> > > TI_FEXCEPT_T_PTR_TYPE,
> > > TI_CONST_FEXCEPT_T_PTR_TYPE,
> > > TI_POINTER_SIZED_TYPE,
> > > + TI_CALL_INFO_TYPE,
> > >
> > > TI_DFLOAT32_TYPE,
> > > TI_DFLOAT64_TYPE,
> > > diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
> > > index 087fcc8a8b8..8f5701ab693 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");
> > >
> > > setup_one_parameter (id, p, static_chain, fn, bb, &vars);
> > > }
> > > diff --git a/gcc/tree-nested.cc b/gcc/tree-nested.cc
> > > index cdccc51d33e..0b0affe9a44 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,11 @@ 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_INFO == 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 +3000,52 @@ 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_INFO)
> > > + {
> > > + tree d = gimple_call_arg (stmt, 0);
> > > + tree ret1 = null_pointer_node;
> > > + tree ret2 = 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_info%> "
> > > + "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));
> > > + ret1 = get_static_chain (info, target_context, &wi->gsi);
> > > + }
> > > + /* Return code pointer. */
> > > + ret2 = build_addr (TREE_OPERAND (d, 0));
> > > + TREE_NO_TRAMPOLINE (ret2) = 1;
> > > + }
> > > + tree fields = TYPE_FIELDS (call_info_type_node);
> > > + tree ret = create_tmp_var_for (info, call_info_type_node, NULL);
> > > + tree cref2 = build3 (COMPONENT_REF, TREE_TYPE (fields),
> > > + ret, fields, NULL_TREE);
> > > + tree cref1 = build3 (COMPONENT_REF, TREE_TYPE (TREE_CHAIN (fields)),
> > > + ret, TREE_CHAIN (fields), NULL_TREE);
> > > + gimple *assign = gimple_build_assign (cref1, ret1);
> > > + gsi_insert_before (gsi, assign, GSI_SAME_STMT);
> > > + assign = gimple_build_assign (cref2, ret2);
> > > + gsi_insert_before (gsi, assign, GSI_SAME_STMT);
> > > + 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))
> > > {
> > > diff --git a/gcc/tree.cc b/gcc/tree.cc
> > > index d0e745e8d28..38d0590a8c7 100644
> > > --- a/gcc/tree.cc
> > > +++ b/gcc/tree.cc
> > > @@ -9984,6 +9984,18 @@ build_common_tree_nodes (bool signed_char)
> > > va_list_type_node = t;
> > > }
> > >
> > > + /* Create the call info type. */
> > > + call_info_type_node = make_node (RECORD_TYPE);
> > > + tree code_field = build_decl (BUILTINS_LOCATION, FIELD_DECL,
> > > + get_identifier ("code"), ptr_type_node);
> > > + DECL_FIELD_CONTEXT (code_field) = call_info_type_node;
> > > + tree data_field = build_decl (BUILTINS_LOCATION, FIELD_DECL,
> > > + get_identifier ("chain"), ptr_type_node);
> > > + DECL_FIELD_CONTEXT (data_field) = call_info_type_node;
> > > + TREE_CHAIN (code_field) = data_field;
> > > + TYPE_FIELDS (call_info_type_node) = code_field;
> > > + layout_type (call_info_type_node);
> > > +
> > > /* SCEV analyzer global shared trees. */
> > > chrec_dont_know = make_node (SCEV_NOT_KNOWN);
> > > TREE_TYPE (chrec_dont_know) = void_type_node;
> > > diff --git a/gcc/tree.h b/gcc/tree.h
> > > index 19bc67718d1..c63898f672c 100644
> > > --- a/gcc/tree.h
> > > +++ b/gcc/tree.h
> > > @@ -4665,6 +4665,7 @@ tree_strip_any_location_wrapper (tree exp)
> > > #define fexcept_t_ptr_type_node
> > > global_trees[TI_FEXCEPT_T_PTR_TYPE]
> > > #define const_fexcept_t_ptr_type_node
> > > global_trees[TI_CONST_FEXCEPT_T_PTR_TYPE]
> > > #define pointer_sized_int_node
> > > global_trees[TI_POINTER_SIZED_TYPE]
> > > +#define call_info_type_node global_trees[TI_CALL_INFO_TYPE]
> > >
> > > #define boolean_type_node global_trees[TI_BOOLEAN_TYPE]
> > > #define boolean_false_node global_trees[TI_BOOLEAN_FALSE]
>
--
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)