52
JIT のコードを読んでみた 内山 雄司 (@y__uti) 2016-12-11 7 回闇 PHP 勉強会

JIT のコードを読んでみた

  • Upload
    y-uti

  • View
    3.129

  • Download
    0

Embed Size (px)

Citation preview

JIT のコードを読んでみた内山 雄司 (@y__uti)

2016-12-11 第7回闇PHP勉強会

自己紹介内山雄司 (@y__uti)

◦ http://y-uti.hatenablog.jp/ (phpusers-ja)

仕事◦ 受託開発の会社 (株式会社ピコラボ) でプログラマをしています

興味◦ プログラミング言語処理系

◦ 機械学習

2016-12-11 第七回闇PHP勉強会 2

JIT for PHP project

2016-12-11 第七回闇PHP勉強会 3

http://news.php.net/php.internals/95531

(第 107 回 PHP 勉強会での発表資料より)

ベンチマーク結果

2016-12-11 第七回闇PHP勉強会 4

0.00

0.05

0.10

0.15

0.20

0.25

0.30

0.35

0.40

実行時間

(秒)

5.6.27 7.0.12 7.1.0RC4 JIT (0edf1e9)

Intel Core i5-3337U 1.80GHz

2GB Memory

CentOS 7 (VM on Windows7)

各 10 回の実行の平均

JIT による速度向上

2016-12-11 第七回闇PHP勉強会 5

http://news.php.net/php.internals/96613

開発者 (Dmitry Stogov氏) による 2016-10-26 の投稿◦ bench.phpでは 3 倍の速度向上

◦ "real-life apps" では大きな差はない

本日の発表内容どのように実装されているかソースコードを追ってみました

1. プログラム実行時に JIT コンパイルを行う仕掛け◦ OPcacheを拡張して opcode handler を差し替え

◦ 関数の実行ごとに JIT 起動条件を確認する

2. JIT コンパイルの処理内容◦ CFG, SSA, call-graph 等の情報に基づき DynASMを利用してコード生成

◦ 生成されたコードを実行するように opcode handler を再度差し替え

この一枚で理解できてしまったガチ勢はマサカリの準備を!

2016-12-11 第七回闇PHP勉強会 6

JIT コンパイルの仕掛け

2016-12-11 第七回闇PHP勉強会 7

PHP の処理の流れ以下のコマンドを実行すると何が起きるのか

2016-12-11 第七回闇PHP勉強会 8

$ php foo.php

処理の流れ1. sapi/cli/php_cli.c の main 関数から処理が始まり

2. Zend/zend.cの zend_execute_scripts関数が呼ばれ

3. zend_compile_file関数でコンパイルして

4. zend_execute関数で実行する

zend_execute_scripts関数in Zend/zend.c

PHP 処理系によるプログラム実行の「かなめ」

◦ ファイルをバイトコード命令列にコンパイルする

2016-12-11 第七回闇PHP勉強会 9

op_array = zend_compile_file(file_handle, type);

zend_execute(op_array, retval);

◦ バイトコード命令列を実行する

op_array構造体in Zend/zend_compile.h

バイトコード命令列 + 各種情報◦ 関数ごとに一つ

◦ トップレベルに書かれたコード用に一つ

2016-12-11 第七回闇PHP勉強会 10

struct _zend_op {const void *handler;znode_op op1;znode_op op2;znode_op result;uint32_t extended_value;uint32_t lineno;zend_uchar opcode;zend_uchar op1_type;zend_uchar op2_type;zend_uchar result_type;

};

struct _zend_op_array {zend_uchar type;zend_uchar arg_flags[3];

...

uint32_t last;zend_op *opcodes;

...

};

一対多lastが命令数を表す

execute_ex関数in Zend/zend_vm_execute.h

各バイトコード命令 (zend_op構造体) の handler を実行

2016-12-11 第七回闇PHP勉強会 11

...while (1) {

...if (UNEXPECTED(

(ret = ((opcode_handler_t)OPLINE->handler)()) != 0)){

...return;

}...

}...

読みやすさのため、一部のマクロを展開して掲載しています

JIT コンパイル付きの実行op_array (関数) ごとに以下の処理を行う

2016-12-11 第七回闇PHP勉強会 12

コンパイルする?

元のコードを実行

コード生成

生成されたコードを実行

開始

終了

No Yes

JITコンパイルの準備(通常の) コンパイル時に handler を書き換える

2016-12-11 第七回闇PHP勉強会 13

コンパイルする?

元のコードを実行

コード生成

生成されたコードを実行

開始

終了

No Yes

handler

handler

OPcacheの処理起動時に zend_compile_file関数を置き換える

in ext/opcache/ZendAccelerator.c

2016-12-11 第七回闇PHP勉強会 14

static int accel_startup(zend_extension *extension){

.../* Override compiler */accelerator_orig_compile_file = zend_compile_file;zend_compile_file = persistent_compile_file;...

}

persistent_compile_file関数がやること◦ コンパイルしたデータ構造をキャッシュ (本来の仕事)

◦ OPcache独自の最適化 (おまけ?)

◦ JIT コンパイルのための handler 書き換え (JIT を有効にしたときのみ)[New!]

persistent_compile_file関数in ext/opcache/ZendAccelerator.c

2016-12-11 第七回闇PHP勉強会 15

/* zend_compile() replacement */zend_op_array*persistent_compile_file(zend_file_handle *file_handle, int type){

...// キャッシュ済みでなければ

if (!persistent_script) {

// コンパイルして

persistent_script = opcache_compile_file(...);

if (persistent_script) {// キャッシュする

persistent_script = cache_script_in_shared_memory(...);}...

JIT までの道のりin ext/opcache/ZendAccelerator.c

2016-12-11 第七回闇PHP勉強会 16

static zend_persistent_script *cache_script_in_shared_memory( ... )

in ext/opcache/zend_persist.c

zend_persistent_script *zend_accel_script_persist( ... )

static void zend_persist_op_array_ex( ... )

in ext/opcache/zend_persist.c

in ext/opcache/jit/zend_jit.c

ZEND_API int zend_jit_op_array(zend_op_array *op_array, zend_script *script)

◦ この関数で handler を差し替えている

JIT コンパイルのトリガーin ext/opcache/jit/zend_jit.h

いくつかの選択肢が提供されている◦ ZEND_JIT_ON_SCRIPT_LOAD スクリプトのロード時

◦ ZEND_JIT_ON_FIRST_EXEC 最初の実行

◦ ZEND_JIT_ON_PROF_REQUEST 実行頻度 (割合) の高い関数

◦ ZEND_JIT_ON_HOT_COUNTERS 実行回数が閾値を超えたとき

◦ ZEND_JIT_ON_DOC_COMMENT DocCommentの指定に従う

2016-12-11 第七回闇PHP勉強会 17

ZEND_JIT_ON_FIRST_EXECin ext/opcache/jit/zend_jit.c

2016-12-11 第七回闇PHP勉強会 18

opline->handler = (const void*)zend_runtime_jit;

zend_runtime_jit関数は無条件に JIT コンパイルを開始

in ext/opcache/jit/zend_jit.c

static void ZEND_FASTCALL zend_runtime_jit(void){

...opline->handler = ZEND_FUNC_INFO(op_array); // 本来の handler を復元

...

/* perform real JIT for this function */zend_real_jit_func(op_array, NULL, NULL); // JIT コンパイルを実行

...}

ZEND_JIT_ON_PROF_REQUESTin ext/opcache/jit/zend_jit.c

2016-12-11 第七回闇PHP勉強会 19

opline->handler = zend_jit_profile_helper;

zend_jit_profile_helperは op_arrayごとの実行回数を数える

in ext/opcache/jit/zend_jit_vm_helpers.c

void ZEND_FASTCALL zend_jit_profile_helper(void){

zend_op_array *op_array = (zend_op_array*)EX(func);const void *handler =

(const void*)ZEND_FUNC_INFO(op_array); // 本来の handler を取得++(ZEND_COUNTER_INFO(op_array)); // この op_array の実行回数++zend_jit_profile_counter; // 全 op_array の実行回数の総和

return ((zend_vm_opcode_handler_t)handler)();// 本来の処理を実行

}

ZEND_JIT_ON_PROF_REQUESTin ext/opcache/jit/zend_jit.c

OPcacheが deactivate されるときに JIT コンパイルを行う

2016-12-11 第七回闇PHP勉強会 20

void zend_jit_check_funcs(HashTable *function_table, zend_bool is_method) {

...zend_ulong counter = (zend_ulong)ZEND_COUNTER_INFO(op_array);if (((double)counter / (double)zend_jit_profile_counter)

> ZEND_JIT_PROF_THRESHOLD) {zend_real_jit_func(op_array, NULL, NULL);

}...

}

◦ 実行回数の比率が閾値を超えた op_arrayを JIT の対象とする

◦ 最初のリクエストの実行で判断

ZEND_JIT_ON_HOT_COUNTERSin ext/opcache/jit/zend_jit.c

2016-12-11 第七回闇PHP勉強会 21

return zend_jit_setup_hot_counters(op_array);

基本ブロック単位で実行回数を計測する

in ext/opcache/jit/zend_jit.c

static int zend_jit_setup_hot_counters(zend_op_array *op_array){

... // Control Flow Graph を構築

opline->handler = (const void*)zend_jit_func_counter_helper;for (i = 0; i < cfg.blocks_count; i++) {

...op_array->opcodes[cfg.blocks[i].start].handler =

(const void*)zend_jit_loop_counter_helper;}

}

ZEND_JIT_ON_HOT_COUNTERSin ext/opcache/jit/zend_jit_vm_helpers.c

実行のたびに hot counter を減じて 0 以下になったらコンパイル

2016-12-11 第七回闇PHP勉強会 22

void ZEND_FASTCALL zend_jit_func_counter_helper(void){

...zend_jit_hot_counters[n] -= ZEND_JIT_HOT_FUNC_COST;if (UNEXPECTED(zend_jit_hot_counters[n] <= 0)) {

zend_jit_hot_counters[n] = ZEND_JIT_HOT_COUNTER_INIT;zend_jit_hot_func(execute_data, opline); // handler を復元して JIT 実行

} else {zend_vm_opcode_handler_t *handlers =

(zend_vm_opcode_handler_t*)ZEND_FUNC_INFO(&EX(func)->op_array);handlers[opline - EX(func)->op_array.opcodes]();

}}

◦ zend_jit_loop_counter_helperも同様の実装

ZEND_JIT_ON_SCRIPT_LOADin ext/opcache/jit/zend_jit.c

2016-12-11 第七回闇PHP勉強会 23

return zend_real_jit_func(op_array, script, NULL);

OPcacheが op_arrayを処理するタイミングでコンパイルする

その意味で、これは "Just In Time" ではない

ZEND_JIT_ON_DOC_COMMENTin ext/opcache/jit/zend_jit.c

2016-12-11 第七回闇PHP勉強会 24

if (zend_needs_manual_jit(op_array)) {return zend_real_jit_func(op_array, script, NULL);

} else {return SUCCESS;

}

OPcacheが op_arrayを処理するタイミングでコンパイルする

DocCommentに@jitを指定した関数のみ対象

これも "Just In Time" ではない

ここまでのまとめJIT のソースコードを追ってみました

1. プログラム実行時に JIT コンパイルを行う仕掛け◦ OPcacheを拡張して opcode handler を差し替え

◦ 関数の実行ごとに JIT 起動条件を確認する

2016-12-11 第七回闇PHP勉強会 25

JIT コンパイルの処理内容

2016-12-11 第七回闇PHP勉強会 26

zend_real_jit_funcin ext/opcache/jit/zend_jit.c

2016-12-11 第七回闇PHP勉強会 27

static int zend_real_jit_func(zend_op_array *op_array, zend_script *script, const zend_op *rt_opline)

{...

JIT コンパイルのための解析 (OPcacheの実装を利用)

◦ 制御フローグラフの構築

◦ データフロー解析 (ZEND_JIT_LEVEL_OPT_FUNC 以上)

◦ コールグラフの構築 (ZEND_JIT_LEVEL_OPT_FUNCS 以上)

コード生成◦ opcode ごとに機械語を生成

◦ コード生成の仕組みには DynASMを利用

コード生成の概要 [1/2]in ext/opcache/jit/zend_jit.c

JIT コンパイルの本体

2016-12-11 第七回闇PHP勉強会 28

static int zend_jit(zend_op_array *op_array, zend_ssa *ssa, const zend_op *rt_opline)

{... // op_array に含まれる各 opcode をコンパイルする

switch (opline->opcode) {...case ZEND_PRE_INC:case ZEND_PRE_DEC:case ZEND_POST_INC:case ZEND_POST_DEC:

if (!zend_jit_inc_dec(&dasm_state, opline, op_array, ssa)) {goto jit_failure;

}goto done;

}

コード生成の実装DynASMのフォーマットで記述

in ext/opcache/jit/zend_jit_x86.dasc

2016-12-11 第七回闇PHP勉強会 29

static int zend_jit_inc_dec(dasm_State **Dst, const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa) {

...|| if ((opline->opcode == ZEND_POST_INC || opline->opcode == ZEND_POST_DEC) &&|| opline->result_type != IS_UNUSED) {| ZVAL_COPY_VALUE

FP + opline->result.var, FP + opline->op1.var, MAY_BE_LONG, r0, eax, r1|| }if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {

| inc aword [FP + opline->op1.var]} else {

| dec aword [FP + opline->op1.var]}...

◦ dynasm.luaによって zend_jit_x86.c に変換される

DynASMis a Dynamic Assembler for code generation engines.

2016-12-11 第七回闇PHP勉強会 30

https://luajit.org/dynasm.html

生成されるコードDynASMのフォーマットでの記述から

in ext/opcache/jit/zend_jit_x86.dasc

2016-12-11 第七回闇PHP勉強会 31

if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {| inc aword [FP + opline->op1.var]

} else {| dec aword [FP + opline->op1.var]

}

if (opline->opcode == ZEND_PRE_INC || opline->opcode == ZEND_POST_INC) {//| inc aword [FP + opline->op1.var]dasm_put(Dst, 749, opline->op1.var);

} else {//| dec aword [FP + opline->op1.var]dasm_put(Dst, 755, opline->op1.var);

}

以下のような C のコードが生成される

in ext/opcache/jit/zend_jit_x86.c

大雑把な理解アセンブリのテンプレートエンジン

2016-12-11 第七回闇PHP勉強会 32

| inc aword [FP + opline->op1.var]

dasm_put(Dst, 749, opline->op1.var);

static const unsigned char dasm_actions[5499] = {248,10,198,4,37,237,0,128,60,37,237,0,15,132,...

"inc aword [FP + ]"...

}

zend_jit_x86.dasc

zend_jit_x86.c

+

テンプレートの定義

テンプレートの適用

dynasm.lua

コード生成の概要 [2/2]in ext/opcache/jit/zend_jit.c

JIT コンパイルの本体

2016-12-11 第七回闇PHP勉強会 33

... // すべての opcode を処理した後

handler = dasm_link_and_encode(&dasm_state, op_array, &ssa->cfg, rt_opline, NULL);

}

◦ 以後は生成された機械語をハンドラとして実行する

JIT コンパイルの例

2016-12-11 第七回闇PHP勉強会 34

サンプルプログラム0 から 9 までの和を計算して表示する

2016-12-11 第七回闇PHP勉強会 35

<?php

function f($a){

$sum = 0;for ($i = 0; $i < $a; ++$i) {

$sum += $i;}return $sum;

}

$a = 10;$ans = f($a);echo $ans;

JIT の動作の確認JIT コンパイルの結果を表示する

2016-12-11 第七回闇PHP勉強会 36

$ ./sapi/cli/php-d zend_extension=$(pwd)/modules/opcache.so-d opcache.enable_cli=1-d opcache.jit=15-d opcache.jit_buffer_size=32M-d opcache.jit_debug=3sample1.php 2>&1

◦ opcache.jit

◦ トリガー (10 の位) とレベル (1 の位) をまとめて指定

◦ opcache.jit_debug

◦ デバッグ情報の表示内容を指定

CFG 構築結果の表示ZEND_JIT_DEBUG_SSA (= 2) を立てるとフロー解析結果を表示

2016-12-11 第七回闇PHP勉強会 37

f: ; (lines=9, args=1, vars=3, tmps=1, ssa_vars=0); (JIT); /home/y-uti/php-jit-bench/sample-code/sample1.php:3-10

BB0: start lines=[0-0]; to=(BB1); level=0; children=(BB1)

CV0($a) = RECV 1 // 仮引数 $a に 1 番目の実引数を受け取る

BB1: follow entry lines=[1-3]; from=(BB0); to=(BB3); idom=BB0; level=1; children=(BB3)

CV1($sum) = QM_ASSIGN int(0) // $sum = 0CV2($i) = QM_ASSIGN int(0) // $i = 0JMP BB3 // 基本ブロック BB3 にジャンプ

BB2: target lines=[4-5]; from=(BB3)

...

関数 f の CFG

2016-12-11 第七回闇PHP勉強会 38

BB0: CV0($a) = RECV 1 // $a = 第 1 引数

BB1: CV1($sum) = QM_ASSIGN int(0) // $sum = 0CV2($i) = QM_ASSIGN int(0) // $i = 0JMP BB3 // jump to BB3

BB2: CV1($sum) = ADD CV1($sum) CV2($i) // $sum = $sum + $iPRE_INC CV2($i) // ++$i

BB3: T3 = IS_SMALLER CV2($i) CV0($a) // $i < $a ?JMPNZ T3 BB2 // if true jump to BB2

BB4: RETURN CV1($sum) // return $sum

生成コードの確認ZEND_JIT_DEBUG_ASM (= 1) を立てると生成されるコードを表示

2016-12-11 第七回闇PHP勉強会 39

JIT$f: ; (/home/y-uti/php-jit-bench/sample-code/sample1.php)sub $0x8, %rspcall ZEND_RECV_SPEC_HANDLERcmp $0x0, EG(exception)jnz JIT$$exception_handlerjmp .L1

.ENTRY1:sub $0x8, %rsp

.L1:call ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLERcall ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLERjmp .L3

.L2:mov $0x7fcd524d6c10, %r15call ZEND_ADD_SPEC_CV_CV_HANDLERcmp $0x0, EG(exception)jnz JIT$$exception_handlercall ZEND_PRE_INC_LONG_OR_DOUBLE_SPEC_TMPVARCV_RETVAL_UNUSED_HANDLER

...

ZEND_JIT_LEVEL_MINIMAL"Subroutine threading"

2016-12-11 第七回闇PHP勉強会 40

...

.L1:call ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLERcall ZEND_QM_ASSIGN_NOREF_SPEC_CONST_HANDLERjmp .L3

.L2:mov $0x7fcd524d6c10, %r15call ZEND_ADD_SPEC_CV_CV_HANDLERcmp $0x0, EG(exception)jnz JIT$$exception_handlercall ZEND_PRE_INC_LONG_OR_DOUBLE_SPEC_TMPVARCV_RETVAL_UNUSED_HANDLERcmp $0x0, EG(exception)jnz JIT$$exception_handler

.L3:

...

◦ PHP のハンドラ (in Zend/zend_vm_execute.h) 呼び出しを並べる

◦ JMP 系の命令は遷移先を直接指定する形に展開

ZEND_JIT_LEVEL_INLINE"Selective inline threading"

2016-12-11 第七回闇PHP勉強会 41

...

.L1:mov $0x0, 0x60(%r14) // val($sum) = 0mov $0x4, 0x68(%r14) // typeinfo($sum) = IS_LONG (= 4)mov $0x0, 0x70(%r14) // val($i) = 0mov $0x4, 0x78(%r14) // typeinfo($i) = IS_LONGjmp .L3

.L2:mov $0x7fd46c0d6c10, %r15call ZEND_ADD_SPEC_CV_CV_HANDLER // $sum = $sum + $i (これは展開されない)

cmp $0x0, EG(exception)jnz JIT$$exception_handlercmp $0x4, 0x78(%r14)jnz .L6

...

◦ 命令の処理を展開して高速に実行

◦ 型推論なし

ZEND_JIT_LEVEL_OPT_FUNC"Optimized JIT based on Type-Inference"

2016-12-11 第七回闇PHP勉強会 42

...

.L2:cmp $0x4, 0x68(%r14) // $sum の型が IS_LONG か調べるjnz .L10 // IS_LONG でなければ .L10 へcmp $0x4, 0x78(%r14) // $i の型が IS_LONG か調べるjnz .L8 // IS_LONG でなければ .L8 へ

mov 0x60(%r14), %rax // %rax = $sumadd 0x70(%r14), %rax // %rax = %rax + $ijo .L9 // オーバーフローしたら .L9 へ

mov %rax, 0x60(%r14) // $sum = %rax.L3:

cmp $0x4, 0x78(%r14) // $i の型が IS_LONG か調べるjnz .L13 // IS_LONG でなければ .L13 へ

inc 0x70(%r14) // ++$ijo .L12 // オーバーフローしたら .L12 へ

...

◦ 型推論 (SSA 形式のデータフロー解析) に基づく最適化

より積極的な最適化レベルZEND_JIT_LEVEL_OPT_FUNCS

◦ "Optimized JIT based on Type-Inference and call-tree"

ZEND_JIT_LEVEL_OPT_SCRIPT

◦ "Optimized JIT based on Type-Inference and inner-procedure analises"

(サンプルプログラムでは違いが見えなかったので割愛)

2016-12-11 第七回闇PHP勉強会 43

まとめJIT のソースコードを追ってみました

1. プログラム実行時に JIT コンパイルを行う仕掛け◦ OPcacheを拡張して opcode handler を差し替え

◦ 関数の実行ごとに JIT 起動条件を確認する

2. JIT コンパイルの処理内容◦ CFG, SSA, call-graph 等の情報に基づき DynASMを利用してコード生成

◦ 生成されたコードを実行するように opcode handler を再度差し替え

2016-12-11 第七回闇PHP勉強会 44

補足このスライドは下記のコードに基づいて作成しました

◦ Repository: https://github.com/zendtech/php-src.git

◦ Branch: jit-dynasm

◦ Commit: 39a5bd9

2016-12-11 第七回闇PHP勉強会 45

JIT コンパイルの例 (追加)

2016-12-11 第七回闇PHP勉強会 46

サンプルプログラム以下の PHP プログラムを考える

2016-12-11 第七回闇PHP勉強会 47

<?php

function f(){

$a = 1;++$a;++$a;++$a;return $a;

}

echo f();

◦ JIT コンパイラはどのようなコードを生成するか

前提として最適化コンパイラ (たとえば gcc) なら直接 4 を返せる

2016-12-11 第七回闇PHP勉強会 48

// sample2.c

int f(){int a = 1;++a;++a;++a;return a;

}

$ gcc -O -S sample2.c

$ cat sample2.s.file "sample2.c"

.text

.globl f

.type f, @functionf:.LFB0:

.cfi_startprocmovl $4, %eaxret.cfi_endproc

.LFE0:.size f, .-f.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-4)".section .note.GNU-stack,"",@progbits

ZEND_JIT_LEVEL_INLINE「データフロー解析を行わない」とは?

2016-12-11 第七回闇PHP勉強会 49

BB0: start exit lines=[0-4]; level=0

CV0($a) = QM_ASSIGN int(1)PRE_INC CV0($a)PRE_INC CV0($a)PRE_INC CV0($a)RETURN CV0($a)

◦ 最初の $a = 1 は右辺が定数なので整数だと分かる

◦ 後続の ++$a で $a が整数 (IS_LONG) だということは分からない

ZEND_JIT_LEVEL_INLINE以下のコードが生成される

2016-12-11 第七回闇PHP勉強会 50

JIT$f: ; (/home/y-uti/php-jit-bench/sample-code/sample2.php)sub $0x8, %rspmov $0x1, 0x50(%r14)mov $0x4, 0x58(%r14)cmp $0x4, 0x58(%r14)jnz .L5inc 0x50(%r14)jo .L4

.L1:cmp $0x4, 0x58(%r14)jnz .L11inc 0x50(%r14)jo .L10

.L2:cmp $0x4, 0x58(%r14)jnz .L17inc 0x50(%r14)jo .L16

...

ZEND_JIT_LEVEL_OPT_FUNCデータフロー解析を行う

2016-12-11 第七回闇PHP勉強会 51

BB0: start exit lines=[0-4]; level=0

#1.CV0($a) [long] RANGE[1..1] = QM_ASSIGN int(1)PRE_INC #1.CV0($a) [long] RANGE[1..1] -> #2.CV0($a) [long] RANGE[2..2]PRE_INC #2.CV0($a) [long] RANGE[2..2] -> #3.CV0($a) [long] RANGE[3..3]PRE_INC #3.CV0($a) [long] RANGE[3..3] -> #4.CV0($a) [long] RANGE[4..4]RETURN #4.CV0($a) [long] RANGE[4..4]

◦ 各命令で $a が取り得る型と値が解析されている

ZEND_JIT_LEVEL_OPT_FUNC以下のコードが生成される

2016-12-11 第七回闇PHP勉強会 52

JIT$f: ; (/home/y-uti/php-jit-bench/sample-code/sample2.php)sub $0x8, %rspmov $0x1, 0x50(%r14)mov $0x4, 0x58(%r14)inc 0x50(%r14)inc 0x50(%r14)inc 0x50(%r14)mov 0x10(%r14), %rcxtest %rcx, %rcxjz .L1mov 0x50(%r14), %rdxmov %rdx, (%rcx)mov $0x4, 0x8(%rcx)

.L1:

...

◦ 整数型でありオーバーフローもしないことが分かっている

◦ しかし、あくまでも「バイトコード命令ごとに」コード生成◦ gccのように 4 を返すような最適化はしない