Skip to content

Commit 8f1e75f

Browse files
authored
Release 1.0.16: Optimize ast\Node property setting. Document #[AllowsDynamicProperties] in stubs (#219)
Add `#[AllowsDynamicProperties]` to AST stubs Use OBJ_PROP_NUM for ast\Node and ast\Metadata building (Do it even for Metadata. The resulting assembly code should be smaller and it's an example of how to write other classes efficiently in the future) Mark 1.0.15 and 1.0.16 as stable
1 parent 6b05823 commit 8f1e75f

File tree

6 files changed

+98
-106
lines changed

6 files changed

+98
-106
lines changed

ast.c

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@
1212
#include "zend_language_scanner_defs.h"
1313
#include "zend_language_parser.h"
1414
#include "zend_exceptions.h"
15+
#include "zend_hash.h"
1516
#include "zend_smart_str.h"
1617
#if PHP_VERSION_ID >= 80200
1718
/* Used for AllowDynamicProperties */
1819
#include "zend_attributes.h"
1920
#endif
2021

22+
#ifndef ZEND_THIS
23+
#define ZEND_THIS getThis()
24+
#endif
25+
2126
#ifndef ZEND_ARG_INFO_WITH_DEFAULT_VALUE
2227
#define ZEND_ARG_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, default_value) \
2328
ZEND_ARG_INFO(pass_by_ref, name)
@@ -50,11 +55,23 @@
5055
#define ast_register_flag_constant(name, value) \
5156
REGISTER_NS_LONG_CONSTANT("ast\\flags", name, value, CONST_CS | CONST_PERSISTENT)
5257

53-
// The number of cache slots there are must be the same as AST_NUM_CACHE_SLOTS in php_ast.h
54-
#define AST_CACHE_SLOT_KIND &AST_G(cache_slots)[3 * 0]
55-
#define AST_CACHE_SLOT_FLAGS &AST_G(cache_slots)[3 * 1]
56-
#define AST_CACHE_SLOT_LINENO &AST_G(cache_slots)[3 * 2]
57-
#define AST_CACHE_SLOT_CHILDREN &AST_G(cache_slots)[3 * 3]
58+
// These are in the order the properties were declared with ast_declare_property.
59+
#define AST_NODE_PROP_KIND(object) OBJ_PROP_NUM((object), 0)
60+
#define AST_NODE_PROP_FLAGS(object) OBJ_PROP_NUM((object), 1)
61+
#define AST_NODE_PROP_LINENO(object) OBJ_PROP_NUM((object), 2)
62+
#define AST_NODE_PROP_CHILDREN(object) OBJ_PROP_NUM((object), 3)
63+
64+
#define AST_NODE_SET_PROP_KIND(object, num) ZVAL_LONG(AST_NODE_PROP_KIND((object)), (num))
65+
#define AST_NODE_SET_PROP_FLAGS(object, num) ZVAL_LONG(AST_NODE_PROP_FLAGS((object)), (num))
66+
#define AST_NODE_SET_PROP_LINENO(object, num) ZVAL_LONG(AST_NODE_PROP_LINENO((object)), (num))
67+
// Set the ast\Node->children array to the given reference-counted array without incrementing the reference count of the array.
68+
// Do not use this macro with immutable arrays.
69+
#define AST_NODE_SET_PROP_CHILDREN_ARRAY(object, array) ZVAL_ARR(AST_NODE_PROP_CHILDREN((object)), (array))
70+
71+
#define AST_METADATA_PROP_KIND(object) OBJ_PROP_NUM((object), 0)
72+
#define AST_METADATA_PROP_NAME(object) OBJ_PROP_NUM((object), 1)
73+
#define AST_METADATA_PROP_FLAGS(object) OBJ_PROP_NUM((object), 2)
74+
#define AST_METADATA_PROP_FLAGS_COMBINABLE(object) OBJ_PROP_NUM((object), 3)
5875

5976
#define AST_CURRENT_VERSION 90
6077

@@ -334,22 +351,20 @@ static const ast_flag_info flag_info[] = {
334351
{ ZEND_AST_CONDITIONAL, 1, conditional_flags },
335352
};
336353

337-
// NOTE(tandre) in php 8, to get a writeable pointer to a property, OBJ_PROP_NUM(AST_CACHE_SLOT_PROPNAME) can be used.
338-
339-
static inline void ast_update_property(zval *object, zend_string *name, zval *value, void **cache_slot) {
354+
static inline void ast_update_property(zval *object, zend_string *name, zval *value) {
340355
#if PHP_VERSION_ID < 80000
341356
zval name_zv;
342357
ZVAL_STR(&name_zv, name);
343-
Z_OBJ_HT_P(object)->write_property(object, &name_zv, value, cache_slot);
358+
Z_OBJ_HT_P(object)->write_property(object, &name_zv, value, NULL);
344359
#else
345-
Z_OBJ_HT_P(object)->write_property(Z_OBJ_P(object), name, value, cache_slot);
360+
Z_OBJ_HT_P(object)->write_property(Z_OBJ_P(object), name, value, NULL);
346361
#endif
347362
}
348363

349-
static inline void ast_update_property_long(zval *object, zend_string *name, zend_long value_raw, void **cache_slot) {
364+
static inline void ast_update_property_long(zval *object, zend_string *name, zend_long value_raw) {
350365
zval value_zv;
351366
ZVAL_LONG(&value_zv, value_raw);
352-
ast_update_property(object, name, &value_zv, cache_slot);
367+
ast_update_property(object, name, &value_zv);
353368
}
354369

355370
static zend_ast *get_ast(zend_string *code, zend_arena **ast_arena, zend_string *filename) {
@@ -617,30 +632,28 @@ static void ast_to_zval(zval *zv, zend_ast *ast, ast_state_info_t *state);
617632
static void ast_create_virtual_node_ex(
618633
zval *zv, zend_ast_kind kind, zend_ast_attr attr, uint32_t lineno,
619634
ast_state_info_t *state, uint32_t num_children, ...) {
620-
zval tmp_zv;
621635
va_list va;
622636
uint32_t i;
623637

624638
object_init_ex(zv, ast_node_ce);
625639

626-
ast_update_property_long(zv, AST_STR(str_kind), kind, AST_CACHE_SLOT_KIND);
627-
628-
ast_update_property_long(zv, AST_STR(str_flags), attr, AST_CACHE_SLOT_FLAGS);
640+
zend_object *obj = Z_OBJ_P(zv);
629641

630-
ast_update_property_long(zv, AST_STR(str_lineno), lineno, AST_CACHE_SLOT_LINENO);
642+
AST_NODE_SET_PROP_KIND(obj, kind);
643+
AST_NODE_SET_PROP_FLAGS(obj, attr);
644+
AST_NODE_SET_PROP_LINENO(obj, lineno);
631645

632-
array_init_size(&tmp_zv, num_children);
633-
Z_DELREF(tmp_zv);
634-
ast_update_property(zv, AST_STR(str_children), &tmp_zv, AST_CACHE_SLOT_CHILDREN);
646+
array_init_size(AST_NODE_PROP_CHILDREN(obj), num_children);
647+
HashTable *children = Z_ARRVAL_P(AST_NODE_PROP_CHILDREN(obj));
635648

636649
va_start(va, num_children);
637650
for (i = 0; i < num_children; i++) {
638651
zval *child_zv = va_arg(va, zval *);
639652
zend_string *child_name = ast_kind_child_name(kind, i);
640653
if (child_name) {
641-
zend_hash_add_new(Z_ARRVAL(tmp_zv), child_name, child_zv);
654+
zend_hash_add_new(children, child_name, child_zv);
642655
} else {
643-
zend_hash_next_index_insert(Z_ARRVAL(tmp_zv), child_zv);
656+
zend_hash_next_index_insert(children, child_zv);
644657
}
645658
}
646659
va_end(va);
@@ -870,7 +883,7 @@ static void ast_fill_children_ht(HashTable *ht, zend_ast *ast, ast_state_info_t
870883
}
871884

872885
static void ast_to_zval(zval *zv, zend_ast *ast, ast_state_info_t *state) {
873-
zval tmp_zv, children_zv;
886+
zval tmp_zv;
874887

875888
if (ast == NULL) {
876889
ZVAL_NULL(zv);
@@ -939,7 +952,7 @@ static void ast_to_zval(zval *zv, zend_ast *ast, ast_state_info_t *state) {
939952
ast_to_zval(zv, ast->child[1], state);
940953
// The property visibility is on the AST_PROP_GROUP node.
941954
// Add it to the AST_PROP_DECL node for old
942-
ast_update_property_long(zv, AST_STR(str_flags), ast->attr, AST_CACHE_SLOT_FLAGS);
955+
AST_NODE_SET_PROP_FLAGS(Z_OBJ_P(zv), ast->attr);
943956
return;
944957
}
945958
break;
@@ -1007,48 +1020,50 @@ static void ast_to_zval(zval *zv, zend_ast *ast, ast_state_info_t *state) {
10071020

10081021
object_init_ex(zv, ast_node_ce);
10091022

1010-
ast_update_property_long(zv, AST_STR(str_kind), ast->kind, AST_CACHE_SLOT_KIND);
1023+
zend_object *obj = Z_OBJ_P(zv);
1024+
1025+
AST_NODE_SET_PROP_KIND(obj, ast->kind);
10111026

1012-
ast_update_property_long(zv, AST_STR(str_lineno), zend_ast_get_lineno(ast), AST_CACHE_SLOT_LINENO);
1027+
AST_NODE_SET_PROP_LINENO(obj, zend_ast_get_lineno(ast));
10131028

1014-
array_init(&children_zv);
1015-
Z_DELREF(children_zv);
1016-
ast_update_property(zv, AST_STR(str_children), &children_zv, AST_CACHE_SLOT_CHILDREN);
1029+
array_init(AST_NODE_PROP_CHILDREN(obj));
1030+
HashTable *children = Z_ARRVAL_P(AST_NODE_PROP_CHILDREN(obj));
10171031

10181032
if (ast_kind_is_decl(ast->kind)) {
10191033
zend_ast_decl *decl = (zend_ast_decl *) ast;
10201034

1021-
ast_update_property_long(zv, AST_STR(str_flags), decl->flags, AST_CACHE_SLOT_FLAGS);
1035+
AST_NODE_SET_PROP_FLAGS(obj, decl->flags);
10221036

1023-
ast_update_property_long(zv, AST_STR(str_endLineno), decl->end_lineno, NULL);
1037+
// This is an undeclared dynamic property and has no cache slot.
1038+
ast_update_property_long(zv, AST_STR(str_endLineno), decl->end_lineno);
10241039

10251040
if (decl->name) {
10261041
ZVAL_STR(&tmp_zv, decl->name);
1042+
Z_TRY_ADDREF(tmp_zv);
10271043
} else {
10281044
ZVAL_NULL(&tmp_zv);
10291045
}
10301046

1031-
Z_TRY_ADDREF(tmp_zv);
1032-
zend_hash_add_new(Z_ARRVAL(children_zv), AST_STR(str_name), &tmp_zv);
1047+
zend_hash_add_new(children, AST_STR(str_name), &tmp_zv);
10331048

10341049
if (decl->doc_comment) {
10351050
ZVAL_STR(&tmp_zv, decl->doc_comment);
1051+
Z_TRY_ADDREF(tmp_zv);
10361052
} else {
10371053
ZVAL_NULL(&tmp_zv);
10381054
}
10391055

1040-
Z_TRY_ADDREF(tmp_zv);
1041-
zend_hash_add_new(Z_ARRVAL(children_zv), AST_STR(str_docComment), &tmp_zv);
1056+
zend_hash_add_new(children, AST_STR(str_docComment), &tmp_zv);
10421057
} else {
10431058
#if PHP_VERSION_ID < 70100
10441059
if (ast->kind == ZEND_AST_CLASS_CONST_DECL) {
10451060
ast->attr = ZEND_ACC_PUBLIC;
10461061
}
10471062
#endif
1048-
ast_update_property_long(zv, AST_STR(str_flags), ast->attr, AST_CACHE_SLOT_FLAGS);
1063+
AST_NODE_SET_PROP_FLAGS(obj, ast->attr);
10491064
}
10501065

1051-
ast_fill_children_ht(Z_ARRVAL(children_zv), ast, state);
1066+
ast_fill_children_ht(children, ast, state);
10521067
#if PHP_VERSION_ID < 70400
10531068
if (ast->kind == ZEND_AST_PROP_DECL && state->version >= 70) {
10541069
zval type_zval;
@@ -1064,7 +1079,7 @@ static void ast_to_zval(zval *zv, zend_ast *ast, ast_state_info_t *state) {
10641079
ast_create_virtual_node_ex(
10651080
zv, ZEND_AST_PROP_GROUP, ast->attr, zend_ast_get_lineno(ast), state, 2, &type_zval, &prop_group_zval);
10661081
}
1067-
ast_update_property_long(&prop_group_zval, AST_STR(str_flags), 0, AST_CACHE_SLOT_FLAGS);
1082+
AST_NODE_SET_PROP_FLAGS(obj, 0);
10681083
}
10691084
#endif
10701085
#if PHP_VERSION_ID < 80000
@@ -1073,7 +1088,7 @@ static void ast_to_zval(zval *zv, zend_ast *ast, ast_state_info_t *state) {
10731088
zval attributes_zval;
10741089
ZVAL_COPY_VALUE(&const_decl_zval, zv);
10751090
ZVAL_NULL(&attributes_zval);
1076-
ast_update_property_long(zv, AST_STR(str_flags), 0, AST_CACHE_SLOT_FLAGS);
1091+
AST_NODE_SET_PROP_FLAGS(obj, 0);
10771092
// For version 80, create an AST_CLASS_CONST_GROUP wrapping the created AST_CLASS_CONST_DECL
10781093
ast_create_virtual_node_ex(
10791094
zv, ZEND_AST_CLASS_CONST_GROUP, ast->attr, zend_ast_get_lineno(ast), state, 2, &const_decl_zval, &attributes_zval);
@@ -1255,21 +1270,21 @@ static inline const ast_flag_info *ast_get_flag_info(uint16_t ast_kind) {
12551270

12561271
static void ast_build_metadata(zval *result) {
12571272
size_t i;
1258-
array_init(result);
1273+
array_init_size(result, ast_kinds_count);
12591274
for (i = 0; i < ast_kinds_count; i++) {
12601275
zend_ast_kind kind = ast_kinds[i];
12611276
const ast_flag_info *flag_info = ast_get_flag_info(kind);
12621277
zval info_zv, tmp_zv;
1278+
zend_object *obj;
12631279

12641280
object_init_ex(&info_zv, ast_metadata_ce);
1281+
obj = Z_OBJ(info_zv);
12651282

12661283
/* kind */
1267-
ast_update_property_long(&info_zv, AST_STR(str_kind), kind, NULL);
1284+
ZVAL_LONG(AST_METADATA_PROP_KIND(obj), kind);
12681285

12691286
/* name */
1270-
ZVAL_STRING(&tmp_zv, ast_kind_to_name(kind));
1271-
Z_TRY_DELREF(tmp_zv);
1272-
ast_update_property(&info_zv, AST_STR(str_name), &tmp_zv, NULL);
1287+
ZVAL_STRING(AST_METADATA_PROP_NAME(obj), ast_kind_to_name(kind));
12731288

12741289
/* flags */
12751290
array_init(&tmp_zv);
@@ -1279,12 +1294,10 @@ static void ast_build_metadata(zval *result) {
12791294
add_next_index_string(&tmp_zv, *flag);
12801295
}
12811296
}
1282-
Z_TRY_DELREF(tmp_zv);
1283-
ast_update_property(&info_zv, AST_STR(str_flags), &tmp_zv, NULL);
1297+
ZVAL_ARR(AST_METADATA_PROP_FLAGS(obj), Z_ARRVAL(tmp_zv));
12841298

12851299
/* flagsCombinable */
1286-
ZVAL_BOOL(&tmp_zv, flag_info && flag_info->combinable);
1287-
ast_update_property(&info_zv, AST_STR(str_flagsCombinable), &tmp_zv, NULL);
1300+
ZVAL_BOOL(AST_METADATA_PROP_FLAGS_COMBINABLE(obj), flag_info && flag_info->combinable);
12881301

12891302
add_index_zval(result, kind, &info_zv);
12901303
}
@@ -1340,27 +1353,27 @@ PHP_METHOD(ast_Node, __construct) {
13401353
Z_PARAM_LONG_EX(lineno, linenoNull, 1, 0)
13411354
ZEND_PARSE_PARAMETERS_END();
13421355

1343-
zval *zv = getThis();
1356+
zend_object *obj = Z_OBJ_P(ZEND_THIS);
13441357

13451358
switch (num_args) {
13461359
case 4:
13471360
if (!linenoNull) {
1348-
ast_update_property_long(zv, AST_STR(str_lineno), lineno, AST_CACHE_SLOT_LINENO);
1361+
AST_NODE_SET_PROP_LINENO(obj, lineno);
13491362
}
13501363
/* Falls through - break missing intentionally */
13511364
case 3:
13521365
if (children != NULL) {
1353-
ast_update_property(zv, AST_STR(str_children), children, AST_CACHE_SLOT_CHILDREN);
1366+
ZVAL_COPY(AST_NODE_PROP_CHILDREN(obj), children);
13541367
}
13551368
/* Falls through - break missing intentionally */
13561369
case 2:
13571370
if (!flagsNull) {
1358-
ast_update_property_long(zv, AST_STR(str_flags), flags, AST_CACHE_SLOT_FLAGS);
1371+
AST_NODE_SET_PROP_FLAGS(obj, flags);
13591372
}
13601373
/* Falls through - break missing intentionally */
13611374
case 1:
13621375
if (!kindNull) {
1363-
ast_update_property_long(zv, AST_STR(str_kind), kind, AST_CACHE_SLOT_KIND);
1376+
AST_NODE_SET_PROP_KIND(obj, kind);
13641377
}
13651378
/* Falls through - break missing intentionally */
13661379
case 0:
@@ -1381,7 +1394,6 @@ PHP_MINFO_FUNCTION(ast) {
13811394
}
13821395

13831396
PHP_RINIT_FUNCTION(ast) {
1384-
memset(AST_G(cache_slots), 0, sizeof(void *) * AST_NUM_CACHE_SLOTS);
13851397
ZVAL_UNDEF(&AST_G(metadata));
13861398
return SUCCESS;
13871399
}

ast.stub.php

Lines changed: 8 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,70 +3,38 @@
33
/** @generate-function-entries */
44

55
/**
6-
* USE ast_stub.php INSTEAD IF YOU ARE LOOKING FOR DOCUMENTATION OR STUBS FOR YOUR IDE.
6+
* ========================================================================================
7+
* | USE ast_stub.php INSTEAD IF YOU ARE LOOKING FOR DOCUMENTATION OR STUBS FOR YOUR IDE. |
8+
* ========================================================================================
79
*
810
* This is a stub file meant only for use with https://github.com/php/php-src/blob/master/build/gen_stub.php
911
* to generate Reflection information (ReflectionParameter, ReflectionFunction, ReflectionMethod, etc.)
1012
*/
1113

1214
namespace ast;
1315

14-
/**
15-
* Parses code file and returns AST root node.
16-
*
17-
* @param string $filename Code file to parse
18-
* @param int $version AST version
19-
* @return Node Root node of AST
20-
*
21-
* @see https://github.com/nikic/php-ast for version information
22-
*/
16+
// XXX: @param in doc comments will cause build/gen_stub.php to emit an error if there is already a real type in the latest php versions.
17+
// Use ast_stub.php instead for documentation.
18+
2319
function parse_code(string $code, int $version, string $filename = 'string code'): \ast\Node {}
2420

2521
function parse_file(string $filename, int $version): \ast\Node {}
2622

27-
/**
28-
* @param int $kind AST_* constant value defining the kind of an AST node
29-
* @return string String representation of AST kind value
30-
*/
3123
function get_kind_name(int $kind): string {}
3224

33-
/**
34-
* @param int $kind AST_* constant value defining the kind of an AST node
35-
* @return bool Returns true if AST kind uses flags
36-
*/
3725
function kind_uses_flags(int $kind): bool {}
3826

39-
/**
40-
* Provides metadata for the AST kinds.
41-
*
42-
* The returned array is a map from AST kind to a Metadata object.
43-
*
44-
* @return Metadata[] Metadata about AST kinds
45-
*/
4627
function get_metadata(): array {}
4728

48-
/**
49-
* Returns currently supported AST versions.
50-
*
51-
* @param bool $exclude_deprecated Whether to exclude deprecated versions
52-
* @return int[] Array of supported AST versions
53-
*/
5429
function get_supported_versions(bool $exclude_deprecated = false): array {}
5530

31+
// In php 8.2+, ast\Node implements the attribute AllowDynamicProperties
5632
/**
5733
* This class describes a single node in a PHP AST.
5834
*/
35+
#[AllowDynamicProperties]
5936
class Node
6037
{
61-
/**
62-
* A constructor which accepts any types for the properties.
63-
* For backwards compatibility reasons, all values are optional and can be any type, and properties default to null
64-
*
65-
* @param int|null $kind
66-
* @param int|null $flags
67-
* @param array|null $children
68-
* @param int|null $lineno
69-
*/
7038
public function __construct(?int $kind = null, ?int $flags = null, ?array $children = null, ?int $lineno = null) {
7139
}
7240
}

ast_arginfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: 41024915fb5255fdbd22c2864f1d3793de262593 */
2+
* Stub hash: d7ef66b95afbb59ff4fba8ab475785c699c07173 */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_ast_parse_code, 0, 2, ast\\Node, 0)
55
ZEND_ARG_TYPE_INFO(0, code, IS_STRING, 0)

ast_stub.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,11 @@ function get_supported_versions($exclude_deprecated = false): array
278278
{
279279
}
280280

281+
// In php 8.2+, this class has the attribute AllowDynamicProperties
281282
/**
282283
* This class describes a single node in a PHP AST.
283284
*/
285+
#[AllowDynamicProperties]
284286
class Node
285287
{
286288
/** @var int AST Node Kind. Values are one of ast\AST_* constants. */

0 commit comments

Comments
 (0)