(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
++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
+
@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)))
+ {
+ error_at (gimple_location (stmt),
+ "argument to %<__builtin_call_code_address%> "
+ "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)));
+
+ 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;
+ }
+
+ /* 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");
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;
+ }
+ 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))
{