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)

Reply via email to