C++コンパイラは、一時オブジェクトを作成する必要がある場合があります。一時オブジェクトは次の場面で使用されます:
- 参照の初期化。
- 標準型変換を含む式の評価。
- 引数渡し。
- 関数の戻り値。
- throw式の評価。
非トリビアルなクラスでは、一時オブジェクトの作成と破棄に、処理時間とメモリ使用量の両面でコストがかかる場合があります。このような場合は、一時オブジェクトの導入を最小限に抑えるべきです。C++コンパイラは一部の一時オブジェクトを削除しますが、すべてを削除できるわけではありません。
一時オブジェクトがどこで導入されるかを検出するのは必ずしも容易ではありません。Herb Sutterがこちらの サンプルで説明しているとおりです。コンパイラはこの情報を持っていますが、一時オブジェクトが導入される場所をコンパイラから取得することは可能でしょうか。それが不可能な場合、それらを報告するようにコンパイラを簡単に変更できるでしょうか。
ここではClangを使用します。Clangは非常に柔軟で、その動作をカスタマイズする多くの方法を提供しています。実際、Clangの主要な設計概念の1つが、ライブラリベースのアーキテクチャの採用です。この設計では、フロントエンドのさまざまな部分を独立したライブラリに明確に分割し、それらをさまざまなニーズや用途に応じて組み合わせることができます。さらに、ライブラリベースのアプローチは優れたインターフェースを促進し、新しい開発者が参加しやすくします。全体像のごく一部を理解するだけで済むからです。
Clangコンパイラには3つのフェーズがあります:
- フロントエンド:ソースコードを解析し、エラーをチェックし、入力コードを表す言語固有の抽象構文木(AST)を構築する。
- オプティマイザ:フロントエンドが生成したASTに対して、いくつかの最適化を実行する。
- バックエンド:マシンが実行する最終コードを生成する。これはターゲットに依存する。

ここではフロントエンドフェーズに注目します。目標は、ソースファイルの抽象構文木(AST)を取得し、一時オブジェクトに関する有用な情報が報告されているかどうかを確認することです。
この最小限のソースコードのASTを見てみましょう:

ASTを生成するには、-cc1スイッチを使ってClangフロントエンドパーサーを実行します。
clang -cc1 -ast-dump test.cpp
GetTest関数に対して生成されたASTは次のとおりです:

このASTでは、2つの情報が一時オブジェクトに関連しています: nrvo および elidable。
Named Return Value Optimizationは、関数の戻り値を保持するために作成される一時オブジェクトを削除するコンパイラ最適化技術です。NRVOは、スタックベースの戻り値のコピーコンストラクタとデストラクタを削除します。これにより、冗長なコピーコンストラクタとデストラクタ呼び出しが最適化され、全体的なパフォーマンスが向上します。詳細については、その Wikiページ。
コピー省略は、オブジェクトの不要なコピーを削除するコンパイラ最適化技術です。詳細については、その Wikiページ。
ClangはNRVOがどこに適用されたかを教えてくれますが、一時オブジェクトがどこで作成されるかは明示的に報告しません。そこでASTダンパーのソースコードを調べ、それらを報告するようにしてみましょう。
ASTDumperはどのように機能するのか?
コンパイラはプログラムを解析し、解析結果を抽象構文木(AST)として表現します。ASTには、代入、変数参照、算術式など、さまざまな種類のノードがあります。ASTを生成した後、Clangはそれを走査して処理を実行するいくつかのフロントエンドアクションを呼び出します。ASTDumpActionはその1つで、ASTをコンソールへダンプします。
ASTDumperは次のように宣言されています:
class ASTDumper
: public ConstDeclVisitor, public ConstStmtVisitor,
public ConstCommentVisitor
構造を走査し、その構造内の各ノードに特定の処理を適用する必要がある場合には、Visitorパターンが推奨されるパターンです。
ASTが走査されるときに呼び出されるメソッドの一部は次のとおりです。それぞれが特定のASTノードに対応しています。
void VisitNamespaceDecl(const NamespaceDecl *D);
void VisitUsingDirectiveDecl(const UsingDirectiveDecl *D);
void VisitNamespaceAliasDecl(const NamespaceAliasDecl *D);
void VisitTypeAliasDecl(const TypeAliasDecl *D);
void VisitTypeAliasTemplateDecl(const TypeAliasTemplateDecl *D);
void VisitCXXRecordDecl(const CXXRecordDecl *D);
void VisitStaticAssertDecl(const StaticAssertDecl *D);
Clang ASTダンパーをハックする
私たちの目標は、作成される一時オブジェクトを報告することです。そのため、オブジェクト作成を追跡し、各オブジェクトの構築に関係するASTノードを特定する必要があります。
Clangでは、C++オブジェクトを作成する必要があるときにVisitCXXConstructExprが呼び出されます。その実装は次のとおりです:

ご覧のとおり、作成されたオブジェクトが一時オブジェクトかどうかを判定するテストはありません。しかし幸い、CXXConstructExprにはIsTemporaryObjectメソッドがあり、この式の結果が指定されたクラス型の一時オブジェクトであるかどうかを判定できます。
別の条件を追加するために、実装を変更してみましょう:

変更後に生成された新しいASTは次のとおりです:

数行のコードを追加しただけで、ASTDumperは一時オブジェクトがどこで作成されるかを明示的に報告するようになりました。
結論
LLVM/Clangは単なるコンパイラではありません。独自のC/C++/Objective-Cツールを開発するための強力な基盤でもあります。そのアーキテクチャは比較的理解しやすく、ニーズに合わせてカスタマイズできます。Clangのソースコードをダウンロードし、少し変更して再ビルドしてみてください。特に、コンパイラの仕組みを学びたい学生にとって有益です。
