2022-04-11

[php-src読書録]その11: classとコンストラクタ

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

今回はいよいよ(?)class。

コンストラクタと初期化部分から

<?php

class Hoge
{
  public $fuga;

  public function __construct($fuga)
  {
    $this->fuga = $fuga;
  }
}

new Hoge(111);

opcode

$_main:
     ; (lines=5, args=0, vars=1, tmps=3)
     ; (before optimizer)
     ; /path/to/11.php:1-20
     ; return  [] RANGE[0..0]
0000 V1 = NEW 1 string("Hoge")
0001 SEND_VAL_EX int(111) 1
0002 DO_FCALL
0003 ASSIGN CV0($hoge) V1
0004 RETURN int(1)
LIVE RANGES:
     1: 0001 - 0003 (new)

Hoge::__construct:
     ; (lines=4, args=1, vars=1, tmps=1)
     ; (before optimizer)
     ; /path/to/11.php:7-10
     ; return  [] RANGE[0..0]
0000 CV0($fuga) = RECV 1
0001 ASSIGN_OBJ THIS string("fuga")
0002 OP_DATA CV0($fuga)
0003 RETURN null

class定義は zend_compile_class_decl() でコンパイルされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7646:L7646

だいたいこんな感じな処理をしていて、zend_class_entryの設定を行っている。

zend_class_entry *ce = zend_arena_alloc(&CG(arena), sizeof(zend_class_entry));
// ... 
ce->type = ZEND_USER_CLASS;
ce->name = name;
zend_initialize_class_data(ce, 1);
ce->ce_flags |= decl->flags;
// ...
ce->info.user.filename = zend_string_copy(zend_get_compiled_filename());
ce->info.user.line_start = decl->start_lineno;
ce->info.user.line_end = decl->end_lineno;

zend_compile_stmt(stmt_ast);

zend_compile_stmt() でプロパティやメソッドのコンパイルが行われる。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7744

zend_compile_prop_group() が呼ばれる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L10024

zend_compile_prop_group()zend_compile_prop_decl() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7410:L7410

zend_compile_prop_decl()zend_declare_typed_property() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7395:L7395

property_info->offset をセットしつつ default_properties_count をインクリメント
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_API.c#L4138-L4140

色々設定してproperties_info のハッシュテーブルにセット
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_API.c#L4172-L4190

コンストラクタは zend_compile_func_decl() でコンパイルされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7172:L7172

メソッドや関数をコンパイルする共通関数で、__construct というメソッドがコンパイルされる

メソッドの場合 zend_begin_method_decl() が呼ばれる。 zend_begin_method_decl()zend_add_magic_method() を呼び出す
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L7100:L7100

コンストラクタの場合は ce->constructorzend_function の値がセットされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_API.c#L2613-L2614


zend_compile_new() はNEWをemitしている
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L4675-L4684

その後、 zend_compile_call_common() が呼ばれ、引数のコンパイルと呼び出し処理のopcodeがemitされる。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L4686

ZEND_DO_FCALLがこの辺でemitされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L3713:L3713

コンストラクタ内のプロパティ代入はこのあたりでコンパイルされる
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_compile.c#L3242-L3251

opcode

ZEND_NEWZEND_NEW_SPEC_CONST_UNUSED_HANDLER でハンドリングされる

ce = zend_fetch_class_by_name(Z_STR_P(RT_CONSTANT(opline, opline->op1)), Z_STR_P(RT_CONSTANT(opline, opline->op1) + 1), ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
constructor = Z_OBJ_HT_P(result)->get_constructor(Z_OBJ_P(result));

call = zend_vm_stack_push_call_frame(
	ZEND_CALL_FUNCTION | ZEND_CALL_RELEASE_THIS | ZEND_CALL_HAS_THIS,
	constructor,
	opline->extended_value,
	Z_OBJ_P(result));

objectの初期化
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_API.c#L1659-L1666

クラスごとにプロパティの領域が変わるが、オブジェクトごとに確保すべきプロパティの領域は zend_object_properties_size() で取得している
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_objects_API.h#L81-L86

プロパティのzvalをコピーして初期化
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_API.c#L1507-L1528

ZEND_ASSIGN_OBJZEND_ASSIGN_OBJ_SPEC_UNUSED_CONST_OP_DATA_CV_HANDLER で処理される だいたいこんな感じな処理をしている。

object = &EX(This);
value = _get_zval_ptr_cv_BP_VAR_R((opline+1)->op1.var EXECUTE_DATA_CC);
value = zobj->handlers->write_property(zobj, name, value, (IS_CONST == IS_CONST) ? CACHE_ADDR(opline->extended_value) : NULL);	

opline+1はOP_DATAの値を取得している。

write_property は関数ポインタで zend_std_write_property() がセットされる。 この関数内の zend_get_property_offset()properties_info のハッシュテーブルからオフセットを取得する。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_object_handlers.c#L774:L774

そのオフセットを使って OBJ_PROP(zobj, property_offset) でプロパティのポインタを取得し、zend_assign_to_variable() でプロパティに値をセットする。
https://github.com/php/php-src/blob/PHP-8.1.4/Zend/zend_object_handlers.c#L799-L800

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