2022-04-03

[php-src読書録]その4: ADDとTMPVAR

php-srcのコードリーディングした内容をコツコツ残すテスト。その4

デバッグの効率化

今までgdbを使っていたんだけどTUIモードというのがあるのを初めて知った。 gdb -tui で実行するとTUIモードでの実行になる。 gdb を実行してから C-x C-a でもOK。 tuiモードを使わないとステップ実行のたびに list を実行するなどしてソースコードを表示する必要があって大変…

そして、それよりもVSCodeのGUIデバッガの方がはるかに楽だった。 C/C++ のプラグインをインストールして launch.json をこんな感じで設定してデバッグ実行すればOK

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) Launch",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/sapi/cli/php",
            "args": ["test.php"],
            "stopAtEntry": true,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": true,
            "MIMode": "gdb",
            "miDebuggerPath": "/usr/local/bin/gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        }
    ]
}

ADDとTMPVAR

今回はこちらのコードを追っていく

<?php

$i = 1;
$i = 1 + $i;

opcode

$_main:
     ; (lines=4, args=0, vars=1, tmps=3)
     ; (before optimizer)
     ; /path/to/4.php:1-5
     ; return  [] RANGE[0..0]
0000 ASSIGN CV0($i) int(1)
0001 T2 = ADD int(1) CV0($i)
0002 ASSIGN CV0($i) T2
0003 RETURN int(1)

ASSIGNは前回説明済みなので割愛。

ADDは今回のケースでは ZEND_ADD_SPEC_CONST_TMPVARCV_HANDLER が呼ばれる。 ハンドラの命名としては、第1オペランドがCONST => 定数、第2オペランドがTMPVARCV => 変数のときに呼ばれるハンドラという意味。

今回のケースの場合、以下の処理が実行される

result = EX_VAR(opline->result.var);
fast_long_add_function(result, op1, op2);
ZEND_VM_NEXT_OPCODE();

fast_long_add_functionの中はインラインアセンブリで記述されている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_operators.h#L634-L650

  1. movqでraxレジストリにop1->valueの値をコピー
  2. addqでraxレジストリにop2->valueの値を加算
  3. joでオーバフローがある場合はoverflowラベルにジャンプ
  4. movqでresult->valueにraxレジストリの値をコピー
  5. IS_LONGの値をresult->value.type_infoにコピー

zend_compile_expr_inner()zend_compile_binary_op() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L10113

zend_compile_binary_op() はleft_node, right_nodeのコンパイルを行う
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L8578-L8579

その後、 zend_emit_op_tmp() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L8656

TMPVARは内部的な一時変数を意味し、以下の3つが作成される

  1. $i = 1 の評価値: ASSIGN
  2. $i + 1の評価値: BINARY_OP
  3. $i = (2の値)の評価値: ASSIGN

opcodesをdumpしたときのtmps=3がTMPVARの数になる。ちなみにvarsはユーザーが定義した変数(CV)の数、argsは関数の引数の数。

(lines=4, args=0, vars=1, tmps=3)

評価値の変数設定は zend_emit_op_tmp()zend_make_tmp_result() 関数で実行される
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L2109

zend_make_tmp_result() はこんな感じ https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L2067-L2072

typeに IS_TMP_VAR を設定しつつ、 get_temporary_variable()result.var にインデックスをセットする。 get_temporary_variable()compiler_globals.active_op_array->T というTMPVarの数を管理する変数をインクリメントして返す。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L483

ここではインデックスをセットしておいて pass_two() でexecute_dataからのオフセットに変換している
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_opcode.c#L1147:L1147

op_array->last_var を加算しているのはスタックフレームは

zend_execute_data => CVの領域 => TMPVARの領域

という感じで並ぶので、CVの領域サイズ+TMPVARのインデックスがTMPVARの変数領域になるため。

  1. $i = 1 の評価値: ASSIGN
  2. $i + 1の評価値: BINARY_OP
  3. $i = (2の値)の評価値: ASSIGN

sizeof(zend_execute_data)が80で関数の引数ゼロ、CV1つ、一時変数3つなので、オフセットは以下のようになる

execute_dataからのオフセット 変数
80 $i
96 TMPVAR1
112 TMPVAR2

各処理のoplineをデバッグするとこんな感じ

処理 op1.var op2.var result.var
$i = 1 80: CV0 128: literals[0] 0
T1 = 1 + $i 112: literals[1] 80: CV0 112: T1
$i = T1 80: CV0 112: T1 2
RETURN 64 0 0

result.varで $i = 1$i = TMP1result.var がインデックスからオフセットに変換されていないのは未使用だから。 ↓ここでstmtなexprに関しては未使用になる。(pass_twoでは IS_UNUSED ではオフセット変換されない)
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L743:L743

評価値を利用する場合と未使用な場合でハンドラが違うので、未使用な場合はresult.varの変換がそもそも不要、という感じっぽい。

ちなみに、2行目でliterals[1]のオフセットが112なのは、opcodesのサイズが128(zend_op x 4), zend_opのサイズが32, zvalのサイズが16なのでoplineからの距離が 128 + 16 - 32 = 112 となるため

このエントリーをはてなブックマークに追加