Here is a patch (stage1) for a feature which I think we have been
missing for a long time as we have __builtin_call_with_static_chain
to call into other languages but no way to use it for nested
functions in C (as a hack, one could extract the address from the
trampoline).
My personal motivation is mostly to get around the problem that we
currently cannot "devirtualize" nested functions (PR49666) which
prevents important optimizations for my main use case (higher-order
loop kernels in numerics). With this patch this works beautifully,
e.g. the function "foo" inĀ the first test is compiled to
foo:
leal (%rdi,%rdi), %eax
ret
which is perfect as all the overhead has completely disappeared.
But the built-ins also allows one to get get rid of the trampolines
more generally.
The implementation is relatively straightforward. The built-ins
are expanded in tree-nested.cc and either disable trampoline
generation to return the code address or return the static chain.
Bootstrapped and regression tested on x86_64.
Martin
c: Built-ins to access code pointer and static chain of nested function.
This patch adds two new built-ins, __builtin_nested_code and
__builtin_nested_chain, 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 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-ins.
* builtins.cc (expand_builtin): Trivially expand built-ins.
(is_simple_builtin): Add new built-ins.
* tree-nested.cc (convert_tramp_reference_stmt): Ingore
new built-ins.
(convert_gimple_call): Expand built-ins.
* tree-inline.cc (initialize_inlined_parameters): Replace
assertion with error.
gcc/doc/ChangeLog:
* extend.texi: Document new built-ins.
gcc/testsuite/ChangeLog:
* gcc.dg/builtin-nested-1.c: New test.
* gcc.dg/builtin-nested-2.c: New test.
* gcc.dg/builtin-nested-3.c: New test.
diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 692e20088c2..e12790ca0b0 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -8093,6 +8093,14 @@ 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_NESTED_CODEPTR:
+ /* If not expanded in tree-nested.cc. */
+ return expand_normal (CALL_EXPR_ARG (exp, 0));
+
+ case BUILT_IN_NESTED_CHAIN:
+ /* If not expanded in tree-nested.cc. */
+ return const0_rtx;
+
case BUILT_IN_SAVEREGS:
return expand_builtin_saveregs ();
@@ -12311,6 +12319,8 @@ is_simple_builtin (tree decl)
case BUILT_IN_STACK_SAVE:
case BUILT_IN_STACK_RESTORE:
case BUILT_IN_DWARF_CFA:
+ case BUILT_IN_NESTED_CODEPTR:
+ case BUILT_IN_NESTED_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 8ab0599b17f..817c39ecd94 100644
--- a/gcc/builtins.def
+++ b/gcc/builtins.def
@@ -1164,6 +1164,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)
+/* Using nested functions. */
+DEF_GCC_BUILTIN (BUILT_IN_NESTED_CHAIN, "nested_chain", BT_FN_PTR_PTR,
ATTR_NULL)
+DEF_GCC_BUILTIN (BUILT_IN_NESTED_CODEPTR, "nested_codeptr", 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 47b0bdf1340..8fbb088226f 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -16610,6 +16610,25 @@ This builtin can be used to call Go closures from C.
@enddefbuiltin
+@defbuiltin{@var{type} __builtin_nested_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_nested_codeptr (@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/testsuite/gcc.dg/builtin-nested-1.c
b/gcc/testsuite/gcc.dg/builtin-nested-1.c
new file mode 100644
index 00000000000..d1e887357b1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-nested-1.c
@@ -0,0 +1,34 @@
+/* { dg-do run } */
+/* { dg-options "-Wtrampolines" } */
+
+struct closure {
+ int (*fun)(int);
+ void *data;
+};
+
+int apply(struct closure c, int value)
+{
+ return __builtin_call_with_static_chain(c.fun(value), c.data);
+}
+
+int foo(int x)
+{
+ int add(int y)
+ {
+ return x + y;
+ }
+
+ struct closure c = {
+ __builtin_nested_codeptr(add),
+ __builtin_nested_chain(add),
+ };
+
+ return apply(c, x);
+}
+
+int main()
+{
+ if (4 != foo(2))
+ __builtin_abort();
+}
+
diff --git a/gcc/testsuite/gcc.dg/builtin-nested-2.c
b/gcc/testsuite/gcc.dg/builtin-nested-2.c
new file mode 100644
index 00000000000..af2cd207149
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-nested-2.c
@@ -0,0 +1,46 @@
+/* { dg-do run } */
+/* { dg-options "-Wtrampolines" } */
+
+/* Check that we get the expected pointers in
+ different context. */
+
+int f(int x)
+{
+ static void *chain, *code;
+
+ if (f != __builtin_nested_codeptr(f))
+ __builtin_abort();
+
+ if ((void*)0 != __builtin_nested_chain(f))
+ __builtin_abort();
+
+
+ int g(int y)
+ {
+ if (code != __builtin_nested_codeptr(g))
+ __builtin_abort();
+
+ if (chain != __builtin_nested_chain(g))
+ __builtin_abort();
+
+ return x + y;
+ }
+
+ chain = __builtin_nested_chain(g);
+ code = __builtin_nested_codeptr(g);
+
+ return g(x);
+}
+
+int main()
+{
+ if (f != __builtin_nested_codeptr(f))
+ __builtin_abort();
+
+ if ((void*)0 != __builtin_nested_chain(f))
+ __builtin_abort();
+
+ if (6 != f(3))
+ __builtin_abort();
+}
+
diff --git a/gcc/testsuite/gcc.dg/builtin-nested-3.c
b/gcc/testsuite/gcc.dg/builtin-nested-3.c
new file mode 100644
index 00000000000..2423cca737f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/builtin-nested-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_nested_codeptr(g); // ok
+}
+
+void * bar(int n)
+{
+ int g(int x)
+ {
+ return n + x;
+ }
+
+ return __builtin_nested_chain(g); /* { dg-warning "returns
address of local variable" } */
+}
+
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..0be2b5840ef 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_NESTED_CHAIN == DECL_FUNCTION_CODE (decl)
+ || BUILT_IN_NESTED_CODEPTR == 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,47 @@ 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_NESTED_CODEPTR
+ || DECL_FUNCTION_CODE (decl) == BUILT_IN_NESTED_CHAIN))
+ {
+ bool chain_p = DECL_FUNCTION_CODE (decl) == BUILT_IN_NESTED_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),
+ chain_p ? "argument to %<__builtin_nested_chain%>"
+ " must be a function"
+ : "argument to %<__builtin_nested_codeptr%>"
+ " must be a function");
+ }
+ else if (chain_p)
+ {
+ 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);
+ }
+ }
+ else
+ {
+ /* Return code pointer. */
+ ret = build_addr (TREE_OPERAND (d, 0));
+ TREE_NO_TRAMPOLINE (ret) = 1;
+ }
+ 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))
{