Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
49791d6
Make ZEND_AST_CONST_ENUM_INIT a 4-children node
arnaud-lb Jan 12, 2026
4af8948
Store enum case id in ZEND_AST_CONST_ENUM_INIT
arnaud-lb Jan 12, 2026
73fd3f1
Store enum case id in instance
arnaud-lb Jan 12, 2026
81e585d
Expose enum case_id internally
arnaud-lb Jan 12, 2026
24be83d
Generate C enum for internal enums
arnaud-lb Jan 12, 2026
73611c6
Port ext/random
arnaud-lb Jan 12, 2026
c35423f
Z_PARAM_ENUM()
arnaud-lb Jan 12, 2026
ed7d319
ext/dom
arnaud-lb Jan 12, 2026
aace901
ext/pcntl
arnaud-lb Jan 12, 2026
9426a7a
ext/reflection
arnaud-lb Jan 12, 2026
8ab1441
ext/uri
arnaud-lb Jan 12, 2026
891cca6
ext/standard
arnaud-lb Jan 12, 2026
9aaf909
ext/bcmath
arnaud-lb Jan 12, 2026
8f1db64
Remove underscore
arnaud-lb Jan 13, 2026
68a7ecc
Switch to int
arnaud-lb Jan 13, 2026
606555b
Fix build
arnaud-lb Jan 13, 2026
a3240be
Mark generated files
arnaud-lb Jan 13, 2026
3f8e6c8
Header guards
arnaud-lb Jan 13, 2026
eb41ca4
WS
arnaud-lb Jan 13, 2026
b7e01d5
Remove default case
arnaud-lb Jan 13, 2026
4a904d2
Include _decl.h from the extension main header
arnaud-lb Jan 13, 2026
40e4f8a
Switch over enum constants
arnaud-lb Jan 13, 2026
1772929
Check that decl files are up to date
arnaud-lb Jan 14, 2026
2213e4a
Do not incorporate version into hash
arnaud-lb Jan 28, 2026
b9493af
Fix wrong default
arnaud-lb Jan 28, 2026
17d763d
Code style
arnaud-lb Jan 28, 2026
b4d932b
Refactor bcmatch to avoid the conversion from zend_enum_RoundingMode …
arnaud-lb Jan 28, 2026
840860d
UPGRADING.INTERNALS
arnaud-lb Jan 28, 2026
286d44e
More specific regex
arnaud-lb Jan 29, 2026
4c6cb49
Make C enum generateion opt-in
arnaud-lb Jan 30, 2026
bebdc23
UPGRADING.INTERNALS
arnaud-lb Feb 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

# Collapse generated files within git and pull request diff.
**/*_arginfo.h linguist-generated -diff
**/*_decl.h linguist-generated -diff
/main/debug_gdb_scripts.c linguist-generated -diff
/Zend/zend_vm_execute.h linguist-generated -diff
/Zend/zend_vm_handlers.h linguist-generated -diff
Expand Down
9 changes: 9 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,20 @@ PHP 8.6 INTERNALS UPGRADE NOTES
automatically unwrap references when the result of the call is stored in an
IS_TMP_VAR variable. This may be achieved by calling the
zend_return_unwrap_ref() function.
. The php_math_round_mode_from_enum() function now takes a
zend_enum_RoundingMode parameter.
. Added Z_PARAM_ENUM().
. Added zend_enum_fetch_case_id().

========================
2. Build system changes
========================

. build/gen_stub.php may now generate a _decl.h file in addition to
the _arginfo.h file, if the stub declares enums and is annotated with
@generate-c-enums. For each enum the file will contain a C enum. Enum values
can be compared to the result of zend_enum_fetch_case_id(zend_object*).

========================
3. Module changes
========================
Expand Down
7 changes: 7 additions & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -2009,6 +2009,13 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string
#define Z_PARAM_OBJ_OF_CLASS_OR_LONG_OR_NULL(dest_obj, _ce, dest_long, is_null) \
Z_PARAM_OBJ_OF_CLASS_OR_LONG_EX(dest_obj, _ce, dest_long, is_null, 1)

#define Z_PARAM_ENUM(dest, _ce) \
{ \
zend_object *_tmp = NULL; \
Z_PARAM_OBJ_OF_CLASS(_tmp, _ce); \
dest = zend_enum_fetch_case_id(_tmp); \
}

/* old "p" */
#define Z_PARAM_PATH_EX(dest, dest_len, check_null, deref) \
Z_PARAM_PROLOGUE(deref, 0); \
Expand Down
9 changes: 6 additions & 3 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -995,10 +995,13 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
zend_ast *class_name_ast = ast->child[0];
zend_string *class_name = zend_ast_get_str(class_name_ast);

zend_ast *case_name_ast = ast->child[1];
zend_ast *case_id_ast = ast->child[1];
int case_id = (int)Z_LVAL_P(zend_ast_get_zval(case_id_ast));

zend_ast *case_name_ast = ast->child[2];
zend_string *case_name = zend_ast_get_str(case_name_ast);

zend_ast *case_value_ast = ast->child[2];
zend_ast *case_value_ast = ast->child[3];

zval case_value_zv;
ZVAL_UNDEF(&case_value_zv);
Expand All @@ -1009,7 +1012,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
}

zend_class_entry *ce = zend_lookup_class(class_name);
zend_enum_new(result, ce, case_name, case_value_ast != NULL ? &case_value_zv : NULL);
zend_enum_new(result, ce, case_id, case_name, case_value_ast != NULL ? &case_value_zv : NULL);
zval_ptr_dtor_nogc(&case_value_zv);
break;
}
Expand Down
6 changes: 3 additions & 3 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,15 @@ enum _zend_ast_kind {
ZEND_AST_CONST_ELEM,
ZEND_AST_CLASS_CONST_GROUP,

// Pseudo node for initializing enums
ZEND_AST_CONST_ENUM_INIT,

/* 4 child nodes */
ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_FOREACH,
ZEND_AST_ENUM_CASE,
ZEND_AST_PROP_ELEM,

// Pseudo node for initializing enums
ZEND_AST_CONST_ENUM_INIT,

/* 5 child nodes */

/* 6 child nodes */
Expand Down
10 changes: 8 additions & 2 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -9756,6 +9756,11 @@ static void zend_compile_enum_case(zend_ast *ast)
ZVAL_STR_COPY(&class_name_zval, enum_class_name);
zend_ast *class_name_ast = zend_ast_create_zval(&class_name_zval);

zval case_id_zval;
int case_id = zend_enum_next_case_id(enum_class);
ZVAL_LONG(&case_id_zval, case_id);
zend_ast *case_id_ast = zend_ast_create_zval(&case_id_zval);

zval case_name_zval;
ZVAL_STR_COPY(&case_name_zval, enum_case_name);
zend_ast *case_name_ast = zend_ast_create_zval(&case_name_zval);
Expand All @@ -9773,7 +9778,8 @@ static void zend_compile_enum_case(zend_ast *ast)
ZSTR_VAL(enum_class_name));
}

zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_ast);
zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT,
class_name_ast, case_id_ast, case_name_ast, case_value_ast);

zval value_zv;
zend_const_expr_to_zval(&value_zv, &const_enum_init_ast, /* allow_dynamic */ false);
Expand Down Expand Up @@ -12669,7 +12675,7 @@ static void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */
zend_eval_const_expr(&ast->child[1]);
return;
case ZEND_AST_CONST_ENUM_INIT:
zend_eval_const_expr(&ast->child[2]);
zend_eval_const_expr(&ast->child[3]);
return;
case ZEND_AST_PROP:
case ZEND_AST_NULLSAFE_PROP:
Expand Down
65 changes: 50 additions & 15 deletions Zend/zend_enum.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,16 @@ static zend_arg_info zarginfo_class_UnitEnum_cases[sizeof(arginfo_class_UnitEnum
static zend_arg_info zarginfo_class_BackedEnum_from[sizeof(arginfo_class_BackedEnum_from)/sizeof(zend_internal_arg_info)];
static zend_arg_info zarginfo_class_BackedEnum_tryFrom[sizeof(arginfo_class_BackedEnum_tryFrom)/sizeof(zend_internal_arg_info)];

zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv)
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, int case_id, zend_string *case_name, zval *backing_value_zv)
{
zend_object *zobj = zend_objects_new(ce);
zend_enum_obj *intern = zend_object_alloc(sizeof(*intern), ce);

zend_object_std_init(&intern->std, ce);
object_properties_init(&intern->std, ce);

intern->case_id = case_id;

zend_object *zobj = &intern->std;
GC_ADD_FLAGS(zobj, GC_NOT_COLLECTABLE);
ZVAL_OBJ(result, zobj);

Expand Down Expand Up @@ -170,6 +177,7 @@ void zend_register_enum_ce(void)
zend_ce_backed_enum->interface_gets_implemented = zend_implement_backed_enum;

memcpy(&zend_enum_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
zend_enum_object_handlers.offset = XtOffsetOf(zend_enum_obj, std);
zend_enum_object_handlers.clone_obj = NULL;
zend_enum_object_handlers.compare = zend_objects_not_comparable;
}
Expand Down Expand Up @@ -539,16 +547,18 @@ ZEND_API zend_class_entry *zend_register_internal_enum(
}

static zend_ast_ref *create_enum_case_ast(
zend_string *class_name, zend_string *case_name, zval *value) {
zend_string *class_name, int case_id, zend_string *case_name,
zval *value) {
// TODO: Use custom node type for enum cases?
size_t size = sizeof(zend_ast_ref) + zend_ast_size(3)
+ (value ? 3 : 2) * sizeof(zend_ast_zval);
const size_t num_children = ZEND_AST_CONST_ENUM_INIT >> ZEND_AST_NUM_CHILDREN_SHIFT;
size_t size = sizeof(zend_ast_ref) + zend_ast_size(num_children)
+ (value ? num_children : num_children-1) * sizeof(zend_ast_zval);
char *p = pemalloc(size, 1);
zend_ast_ref *ref = (zend_ast_ref *) p; p += sizeof(zend_ast_ref);
GC_SET_REFCOUNT(ref, 1);
GC_TYPE_INFO(ref) = GC_CONSTANT_AST | GC_PERSISTENT | GC_IMMUTABLE;

zend_ast *ast = (zend_ast *) p; p += zend_ast_size(3);
zend_ast *ast = (zend_ast *) p; p += zend_ast_size(num_children);
ast->kind = ZEND_AST_CONST_ENUM_INIT;
ast->attr = 0;
ast->lineno = 0;
Expand All @@ -563,24 +573,47 @@ static zend_ast_ref *create_enum_case_ast(
ast->child[1] = (zend_ast *) p; p += sizeof(zend_ast_zval);
ast->child[1]->kind = ZEND_AST_ZVAL;
ast->child[1]->attr = 0;
ZEND_ASSERT(ZSTR_IS_INTERNED(case_name));
ZVAL_STR(zend_ast_get_zval(ast->child[1]), case_name);
ZVAL_LONG(zend_ast_get_zval(ast->child[1]), case_id);
Z_LINENO_P(zend_ast_get_zval(ast->child[1])) = 0;

ast->child[2] = (zend_ast *) p; p += sizeof(zend_ast_zval);
ast->child[2]->kind = ZEND_AST_ZVAL;
ast->child[2]->attr = 0;
ZEND_ASSERT(ZSTR_IS_INTERNED(case_name));
ZVAL_STR(zend_ast_get_zval(ast->child[2]), case_name);
Z_LINENO_P(zend_ast_get_zval(ast->child[2])) = 0;

if (value) {
ast->child[2] = (zend_ast *) p; p += sizeof(zend_ast_zval);
ast->child[2]->kind = ZEND_AST_ZVAL;
ast->child[2]->attr = 0;
ast->child[3] = (zend_ast *) p; p += sizeof(zend_ast_zval);
ast->child[3]->kind = ZEND_AST_ZVAL;
ast->child[3]->attr = 0;
ZEND_ASSERT(!Z_REFCOUNTED_P(value));
ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[2]), value);
Z_LINENO_P(zend_ast_get_zval(ast->child[2])) = 0;
ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[3]), value);
Z_LINENO_P(zend_ast_get_zval(ast->child[3])) = 0;
} else {
ast->child[2] = NULL;
ast->child[3] = NULL;
}

return ref;
}

int zend_enum_next_case_id(zend_class_entry *enum_class)
{
ZEND_HASH_REVERSE_FOREACH_VAL(&enum_class->constants_table, zval *zv) {
zend_class_constant *c = Z_PTR_P(zv);
if (!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE)) {
continue;
}
ZEND_ASSERT(Z_TYPE(c->value) == IS_CONSTANT_AST);
zend_ast *ast = Z_ASTVAL(c->value);

ZEND_ASSERT(ast->kind == ZEND_AST_CONST_ENUM_INIT);
return Z_LVAL_P(zend_ast_get_zval(ast->child[1])) + 1;
} ZEND_HASH_FOREACH_END();

return 1;
}

ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value)
{
if (value) {
Expand All @@ -602,9 +635,11 @@ ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, z
ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF);
}

int case_id = zend_enum_next_case_id(ce);

zval ast_zv;
Z_TYPE_INFO(ast_zv) = IS_CONSTANT_AST;
Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_name, value);
Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_id, case_name, value);
zend_class_constant *c = zend_declare_class_constant_ex(
ce, case_name, &ast_zv, ZEND_ACC_PUBLIC, NULL);
ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE;
Expand Down
19 changes: 18 additions & 1 deletion Zend/zend_enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,25 @@ extern ZEND_API zend_class_entry *zend_ce_unit_enum;
extern ZEND_API zend_class_entry *zend_ce_backed_enum;
extern ZEND_API zend_object_handlers zend_enum_object_handlers;

typedef struct zend_enum_obj {
int case_id;
zend_object std;
} zend_enum_obj;

static inline zend_enum_obj *zend_enum_obj_from_obj(zend_object *zobj) {
ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
return (zend_enum_obj*)((char*)(zobj) - XtOffsetOf(zend_enum_obj, std));
}

void zend_enum_startup(void);
void zend_register_enum_ce(void);
void zend_enum_add_interfaces(zend_class_entry *ce);
zend_result zend_enum_build_backed_enum_table(zend_class_entry *ce);
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv);
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, int case_id, zend_string *case_name, zval *backing_value_zv);
void zend_verify_enum(const zend_class_entry *ce);
void zend_enum_register_funcs(zend_class_entry *ce);
void zend_enum_register_props(zend_class_entry *ce);
int zend_enum_next_case_id(zend_class_entry *enum_class);

ZEND_API zend_class_entry *zend_register_internal_enum(
const char *name, uint8_t type, const zend_function_entry *functions);
Expand All @@ -47,6 +58,12 @@ ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name
ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name);
ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try_from);

static zend_always_inline int zend_enum_fetch_case_id(zend_object *zobj)
{
ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assert is redundant with the one in zend_enum_obj_from_obj(). I also don't see how it could help with codegen.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I though about this when adding this assert, but if we consider functions as opaque APIs, we don't know that zend_enum_obj_from_obj() has redundant asserts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is fair, but in this case it is probably reasonable to expect zend_enum_obj_from_obj() to do the verification (if necessary), since that's the purpose of the function, especially since a failed assert is always a programmer error.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particularly strong feelings either way, though.

return zend_enum_obj_from_obj(zobj)->case_id;
}

static zend_always_inline zval *zend_enum_fetch_case_name(zend_object *zobj)
{
ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
Expand Down
Loading
Loading