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