📰 newsreader

hackernews score 0.91 好み 0.12 en

ワイドポインタとトランポリンなしでGCCの入れ子関数を使用する II

原題: Using GCC's Nested Functions with Wide Pointers and No Trampolines II

gccnested functionstrampolineswide pointersstatic chainsecuritybuilt-in functionsabi
原文 ↗

日本語訳

# タイトル

GCCのネストされた関数をワイドポインタとトランポリンなしで使用する II

# 本文

Martin Uecker, 2026-07-14

ネストされた関数のアドレスが取得されると、GCCは実行時にトランポリンを作成します。このトランポリンはスタック上に配置されるため、スタックを実行可能にする必要があります。これは、実行可能でないスタックが重要なセキュリティ機能であるため、問題となります。

しばらくの間、GCCにはトランポリンをヒープに配置するオプションがあり、これは以前よりは改善されていますが、メモリ割り当てのコストが高く、`longjmp`が使用された際にトランポリンがリークする可能性があるため、依然として理想的ではありません。

以前、私は新しいワイドポインタ型によってこれを回避する方法を指摘し、そのために必要となるコア機能を実装するGCCへの暫定的なパッチについても述べました。ここでは、2つのアップデートをお伝えします。

まず、GCC 16がリリースされました。GCC 16では、親関数の変数にアクセスしないネストされた関数は、トランポリンを必要としないことが保証されます。実用的には、最適化時にはすでにこれが保証されていましたが、現在は最適化を行わない場合でも保証され、ドキュメント化されています。これは、そのような関数を親関数から安全に返せることを意味し、また、ローカルなコンテキストにアクセスしようとする関数を返そうとした場合には(少なくともコンパイラにとって明白な単純なケースでは)、警告が表示されるようになります(以下のGodboltの例を参照)。

```c

typedef int cb_f(int);

cb_f *foo(int x)

{

int worker(int y)

{

return x + y; // キャプチャ

}

return worker;

}

cb_f *bar(int _x)

{

static int x;

x = _x;

int worker(int y)

{

return x + y; // キャプチャなし

}

return worker;

}

```

親関数の変数にアクセスしないネストされた関数でも、静的変数、名前付き定数、または型(可変修飾されていない場合)にはアクセスできます。これはコールバック関数を書く際に便利です(Godboltの例を参照)。

```c

typedef int cb_f(void *, int y);

int process(cb_f *cb, void *data)

{

return cb(data, 5);

}

int foo(int x)

{

struct { int x; } data = { .x = x };

int worker(void *_data, int y)

{

typeof(data) *data = _data;

return data->x + y;

}

return process(worker, &data);

}

```

それでも、ネストされた関数の真の力は、親関数の変数に直接アクセスできることにあります。また、上記のコードは、手動でボイラープレートを追加することで、そのような関数をシミュレートしているように見えます。コンパイラは間違いなくこれを支援できるはずです。これが、私の2つ目のアップデートの内容です。

最近、私のパッチのバージョンがGCCの開発ブランチにマージされました。これは、既存のビルトイン関数 `__builtin_call_with_static_chain` と組み合わせて使用できる2つの新しいビルトイン関数を実装するもので、トランポリンを必要とせずに、以下の例(Godboltの例を参照)を記述できます。

```c

typedef int cb_f(int y);

static int process(cb_f *cb, void *data)

{

return __builtin_call_with_static_chain(cb(5), data);

}

int main()

{

int x = 7;

int worker(int y)

{

return x + y;

}

return process(__builtin_call_code_address(worker),

__builtin_call_static_chain(worker));

}

```

実際、トランポリンが不要になれば、GCCが内部で行っていることは、このコードを上記の例に自動的に変換することだけです! 手動で書いたバージョンとの唯一の違いは、データポインタが特別なレジスタを介して渡されることです。このレジスタは、多くの他のプログラミング言語でスタティックチェーンを渡すために使用されており、そのためABIによってすでにこの用途のために予約されています。

適切な言語サポートがなくても、汎用的なワイドポインタ型を定義し、マクロを介してビルトイン関数をラップすることで、この例をもう少し使いやすくすることができます(Godboltの例を参照)。

```c

#define wide(T) struct wide_##T { typeof(T) *code; void *chain; }

typedef int cb_f(int y);

int baz(wide(cb_f) p, int x)

{

return CALL(p, (x));

}

int foo(int k)

{

int bar(int x) { return k + x; }

return baz(CLOSURE(cb_f, bar), 2 * k);

}

```

コンパイラは、これを3倍の乗算を実行する単一のアセンブリ命令へと最適化してくれます。

```assembly

foo:

lea eax, [rdi+rdi*2]

ret

```

ネストされた関数をサポートするコンパイラはGCC以外にもありますが、この機能はそれほど移植性が高くありません。将来的には、ClangとGCCの両方でネストされた関数を使用する方法や、ハックを用いて古いバージョンのGCCでもトランポリンの作成を回避する方法について議論する予定です。

原文(英語)を表示

Martin Uecker, 2026-07-14

When the address of a nested function is taken, GCC creates at

trampoline at run-time. This trampoline is placed

on the stack, which means that the stack has to be executable.

This is problematic because a non-executable stack is an important security feature.

For some time now, GCC has the option to place the trampoline on the heap,

which is better but still not ideal as the allocation has a higher cost and the

trampoline could leak when longjmp

is used.

Previously, I pointed out how this could be avoided with a new wide pointer type, and also talked about a preliminary patch to GCC that implements some core functionality that would be necessary for this.

Here, I want to give two updates.

First, GCC 16 was released. In GCC 16 it is guaranteed that a nested function that does not access variables of a parent function will not require a trampoline. In practice, this was already ensured when optimizing, but now it is also ensured when not optimizing and documented. This also means that you can safely return such a function from its parent and that you will get a warning when trying to return a function that does access the local context (at least in simple cases where this is obvious to the compiler) as in the following example (Godbolt Example)

typedef int cb_f(int);

cb_f *foo(int x)

{

int worker(int y)

{

return x + y; // capture

}

return worker;

}

cb_f *bar(int _x)

{

static int x;

x = _x;

int worker(int y)

{

return x + y; // no capture

}

return worker;

}

Nested function that do not access variables of the parent function can still access static variables, named constants, or types (if not variably modified). This is useful for writing callback functions (Godbolt Example).

typedef int cb_f(void *, int y);

int process(cb_f *cb, void *data)

{

return cb(data, 5);

}

int foo(int x)

{

struct { int x; } data = { .x = x };

int worker(void *_data, int y)

{

typeof(data) *data = _data;

return data->x + y;

}

return process(worker, &data);

}

Still, the true power of nested functions is being able to directly access the variables of the parent function. Also, the code above seems like we just simulate such functions by manually adding some boilerplate code - the compiler should certainly be able to help us with this. This is what my second update is about.

Recently, a version of

my patch was merged into the development branch of GCC that implements

the two new built-in functions that can be used together with the existing

built-in __builtin_call_with_static_chain

to write this

example

(Godbolt Example)

without requiring trampolines.

typedef int cb_f(int y);

static int process(cb_f *cb, void *data)

{

return __builtin_call_with_static_chain(cb(5), data);

}

int main()

{

int x = 7;

int worker(int y)

{

return x + y;

}

return process(__builtin_call_code_address(worker),

__builtin_call_static_chain(worker));

}

In fact, with trampolines out of the picture, all that GCC does under the hood is to automatically transform this into the example above! The only difference to the manually written version that the data pointer is passed via a special register. This register is used by many other programming language for passing the static chain and therefor already reserved for this use by the ABI.

Even without proper language support, we can still make this example a bit nicer by defining a generic wide pointer type and wrapping the built-ins via macros. (Godbolt Example)

#define wide(T) struct wide_##T { typeof(T) *code; void *chain; }

typedef int cb_f(int y);

int baz(wide(cb_f) p, int x)

{

return CALL(p, (x));

}

int foo(int k)

{

int bar(int x) { return k + x; }

return baz(CLOSURE(cb_f, bar), 2 * k);

}

The compiler will happily optimize this to a single assembly instruction that implements a multiplication by three.

foo:

lea eax, [rdi+rdi*2]

ret

While there are compilers other than GCC that support nested functions, this feature is not really portable. In the future, I will discuss how one can use nested functions with both clang and GCC and how one can, with a hack, avoid creating trampolines even on older versions of GCC.

← 一覧に戻る