Slide 1

Slide 1 text

ClangにReflection提案っぽい ものを実装してみた話 @HO_RI9191 C++ MIX #3

Slide 2

Slide 2 text

お詫び ● 今日はMeta classの話はしません ● MetaClassっぽいものは実装はしましたがReflectionの話でスライドがいっぱいに なってしまいました

Slide 3

Slide 3 text

何したの? ● C++コンパイラのお勉強がてらReflection提案(P1240)とMetaclass提案 (P0707) を雑にClangに実装してみた ○ https://github.com/ho-ri1991/clang-reflection-metaclass ● コンパイラ初心者なのでガチプロの方はマサカリをご準備ください ※真面目にメンテが必要なプロジェクトの場合は Clang Toolingなどで頑張る方が良いと思います c.f. CppCon 2018 “Building a C++ Reflection System in One Weekend Using Clang and LLVM” ※提案者(Andrew Sutton)の実装はこれ https://github.com/asutton/clang (読んでいないが、間違いなくこっちのほうが良い実装になっていると思います)

Slide 4

Slide 4 text

Clang概観

Slide 5

Slide 5 text

Clang/LLVM https://www.aosabook.org/en/llvm.html#fig.llvm.lcom

Slide 6

Slide 6 text

Clang Parser Lexer AST CodeGen Sema main.cpp “int” “main” (“ ...... LLVM IR https://github.com/llvm-mirror/clang/tree/master/include/clang

Slide 7

Slide 7 text

Clang Parser Lexer AST CodeGen Sema main.cpp “int” “main” “(“ “)” LLVM IR clang -Xclang -dump-tokens -fsyntax-only main.cpp

Slide 8

Slide 8 text

Clang clang main.cpp -Xclang -ast-dump -fsyntax-only Parser Lexer AST CodeGen Sema main.cpp “int” “main” “(“ “)” LLVM IR

Slide 9

Slide 9 text

Clang clang main.cpp -S -emit-llvm -o - Parser Lexer AST CodeGen Sema main.cpp “int” “main” “(“ “)” LLVM IR

Slide 10

Slide 10 text

Reflection

Slide 11

Slide 11 text

Reflection? ● プログラムがプログラム自身の構造を読み取れるようにする言語機能 ● メタプログラミングを可能にする ○ ソースコードをデータとして取り込む ○ 何らかの操作をしてコード生成をする ● Dynamic Reflectionはいろんな言語で入っている ○ なんだかんだあると便利だがコンパイル時計算とかには使えない

Slide 12

Slide 12 text

Reflection in C++ ● ご存知のようにコンパイル時計算の需要がある ○ Type traitsとかでいろいろ情報がとれるし 頑張ればいろいろ情報がとれる ○ Conceptも入る ● ただ、取れないものも多い ○ クラス名 ○ メンバ変数・関数、enumフィールドのリスト ○ 足りないものが多い

Slide 13

Slide 13 text

Reflection TS (N4766, P0385) isocpp.org/std/status Approved for publication @ Kona meeting https://isocpp.org/files/img/wg21-timeline-2019-04.png

Slide 14

Slide 14 text

Reflection TS (N4766, P0385) ● Reflection TSではStatic Reflectionを入れようとしている ○ より包括的なリフレクションのサポート ● コンパイル時にクラス・名前空間などのメタ情報を取れるようになる ○ クラス名、Enumerator名、名前空間名 ○ メンバ変数/関数のリスト (名前と型)、public/private、ポインタなど ○ 基本は名前がついているものから取れる ● 式の構文木は取れない ● TMPをベースとした設計 ● 今回は省略

Slide 15

Slide 15 text

Reflection TSの構文 ● reflexpr ( operand ) という構文 ○ メタ情報が入った実装依存の型が返ってくる ※非表示スライド

Slide 16

Slide 16 text

具体的に ※非表示スライド

Slide 17

Slide 17 text

Reflection TSの構文 ● 名前を持ったローカルではないものであればReflectionを取れる ○ 何が取れるかは今後変わると思います 1+1とかの式はダメ function-call-expr func-type-conv-exprだけ 関数名、テンプレート名はダメ ローカルなEntityもダメ 関数引数、ラムダキャプチャは OK ※非表示スライド

Slide 18

Slide 18 text

Value Based Reflection (P1240) ● 最近のconstexpr強化提案を踏まえてconstexprベースのStatic Reflectionにし ようとしている ○ TMPはコンパイル時にメモリを食いまくる ○ TMPはできれば読みたくない ○ constexpr vectorやExpansion Statementを前提として作っている

Slide 19

Slide 19 text

Value Based Reflectionの構文 ● reflexpr ( operand ) という構文 ○ メタ情報が入った実装依存の値が返ってくる

Slide 20

Slide 20 text

Enum to String (Value Based Reflection) (P1240)

Slide 21

Slide 21 text

Enum to String (Value Based Reflection) Enumのメタ情報を保持した値

Slide 22

Slide 22 text

Enum to String (Value Based Reflection) EnumのreflectoinからEnumメンバのreflectionが入っ たconstexpr vector(P0784)を構築してくれるconstexpr 関数 Enumのメタ情報を保持した値

Slide 23

Slide 23 text

Enum to String (Value Based Reflection) Enumメンバの情報が入った constexprな vector(P0784)が返ってくる reflectionを値・型などに戻す演算子 Enumのメタ情報を保持した値

Slide 24

Slide 24 text

Enum to String (Value Based Reflection) Expansion Statement (P1306) (コンパイル時vectorやtupleのloop expansion) Enumメンバの情報が入った constexprな vector(P0784)が返ってくる Enumのメタ情報を保持した値

Slide 25

Slide 25 text

Enum to String (Value Based Reflection)

Slide 26

Slide 26 text

Enum to String (Value Based Reflection)

Slide 27

Slide 27 text

Value Based Reflectionを実装してみる

Slide 28

Slide 28 text

Value Based Reflectionを実装してみる Enumのメタ情報を保持した値

Slide 29

Slide 29 text

Enumのメタ情報を保持した値 ? ● コンパイラ内部のASTノードのポインタ = std::intptr_t (※実装例の一つ) ○ P1240, P0721では関係ない整数型への暗黙の型変換を防ぐために enum classやstructしてる clang main.cpp -Xclang -ast-dump -fsyntax-only

Slide 30

Slide 30 text

Enumのメタ情報を保持した値 ? ● コンパイラ内部のASTノードのポインタ = std::intptr_t (※実装例の一つ) ○ P1240, P0721では関係ない整数型への暗黙の型変換を防ぐために enum classやstructしてる clang main.cpp -Xclang -ast-dump -fsyntax-only ASTノードのアドレス

Slide 31

Slide 31 text

Enumのメタ情報を保持した値 ? ASTノードのポインタを返すようにする このASTポインタを用いてReflectionのライブラリ関数群を実装する

Slide 32

Slide 32 text

構文を追加する Parser Lexer AST CodeGen Sema main.cpp “int” “main” “{“ ...... LLVM IR このあたりを拡張する Parser, Semaは省略 ※予約後にする場合はLexerも必要 ※Reflectionはコンパイル時計算なので  コード生成は触らなくてもできる ※実際どうやるのが正攻法かはわからない

Slide 33

Slide 33 text

Parser ● Parse*()というメンバ関数が大量に生えている(再帰下降パーサ) ● ParseAssignmentExpressionが走る直前にreflexprかどうかチェックする ○ reflexprだったらreflexpr(T);まで読んでからSemaのメンバ関数を呼ぶ ○ 具体的な方針は “Syntax macros - a case study in extending Clang - LLVM Cauldron 2016” https://clang.llvm.org/doxygen/classclang_1_1Parser.html ※非表示スライド

Slide 34

Slide 34 text

Sema ● ActOn*()/Build*()というメンバ関数が大量に生えている(AST組み立て) ● reflexpr用の意味解析メンバ関数を生やす ○ Parserから呼ばれてreflexpr用のASTノードを作る https://clang.llvm.org/doxygen/classclang_1_1Sema.html ※非表示スライド

Slide 35

Slide 35 text

AST (Node) ● clang::Stmt ○ If/For/BinaryOp/IntegerLiteralなどの文/式を表すASTノードの基底クラス ● clang::Decl ○ Enum/EnumField/class/struct/variable/Functionなどを表すASTノードの基底クラス clang::EnumDecl clang::Decl clang::EnumConstantDecl

Slide 36

Slide 36 text

sizeof/alignofを参考にする ● reflexprの構造はsizeofとだいたい同じ clang main.cpp -Xclang -ast-dump -fsyntax-only

Slide 37

Slide 37 text

sizeof/alignofのASTノード

Slide 38

Slide 38 text

reflexprのASTノード ● siezof/alignofのコピペ clang::Reflexpr

Slide 39

Slide 39 text

コンパイル時評価方法を実装する コンパイル時計算のときにASTノードのポインタを返すようにする このASTを用いてReflectionのライブラリ関数群を実装する

Slide 40

Slide 40 text

Constexpr関数/変数の評価 ● Constexprの評価は再帰的にASTノードを評価することでできる ○ 各ノードの評価方法を実装する( c.f. clang/lib/AST/ExprConstant.cpp) ● if statmentの具体例は次のページ

Slide 41

Slide 41 text

※実際のコードとは少し違います if condition Then else

Slide 42

Slide 42 text

コンパイル時評価方法を実装する ● 戻り値の型はstd::uintptr_t ● 値は型T(もしくは式)のAST(もしくはそれ導出できる内部ハンドル)

Slide 43

Slide 43 text

リフレクションライブラリ関数 ● ASTポインタを整数にしたものをポインタに戻してから計算を行う ○ 本当はちょっと面倒なことがいくつかあるが略 ※enum_sizeなんてものは  多分作られないと思います

Slide 44

Slide 44 text

Enum to String in Value Base Reflection Expansion Statement (P1306) (コンパイル時vectorやtupleのloop expansion)

Slide 45

Slide 45 text

Expansion Statement (P1360) ● Reflection自体は最終的なバイナリには残ってはいけない ○ コンパイラ内のASTのポインタをバイナリに残してもアクセスできない ● loop expansionしないと実行時にASTが必要になってしまう

Slide 46

Slide 46 text

偽Expansion Statementを実装する ● constexpr vectorを作るのはちょっと大変 ● やりたいことはメタ情報のリストを返すリフレクションのloop expansionなのでそれ だけ対応する ● やることは単純 ○ forのbodyをパースしてbodyのASTを作る ○ リストサイズの分だけその bodyをコピーする ○ ループ変数の初期値をそれぞれ書き換える ■ 実際には細かいところで結構ハマった

Slide 47

Slide 47 text

Reflection TSを越えて ● identifierを生成して変数・関数宣言 ○ 今はプリプロセッサマクロでしかできない ○ Type/Value Based Reflection (P0385/P1240)提案に含まれている ● より簡単なCode Injectionの機構 (P0712, p0633) ● Metaclass ○ constexpr関数でクラスの生成規則を作る

Slide 48

Slide 48 text

References ● clang ○ https://clang.llvm.org/doxygen/index.html ○ https://clang.llvm.org/docs/InternalsManual.html ○ https://llvm.org/devmtg/2016-09/slides/Rink-SyntaxMacros.pdf ○ https://www.youtube.com/watch?v=DUiUBt-fqEY ● Reflection ○ Reflection TS : n4750 ○ Type Based Reflection : p0385 ○ Value Based Reflection : p1240 ○ Expansion Statements : p1306 ○ p0712 ○ p0993 ○ https://github.com/asutton/clang