Skip to content
Open
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
452b8c6
Make ZEND_AST_CONST_ENUM_INIT a 4-children node
arnaud-lb Jan 12, 2026
c2d5e59
Store enum case id in ZEND_AST_CONST_ENUM_INIT
arnaud-lb Jan 12, 2026
30addd5
Store enum case id in instance
arnaud-lb Jan 12, 2026
156c340
Expose enum case_id internally
arnaud-lb Jan 12, 2026
5bd0b89
Generate C enum for internal enums
arnaud-lb Jan 12, 2026
ffd98fa
Port ext/random
arnaud-lb Jan 12, 2026
79fdec2
Z_PARAM_ENUM()
arnaud-lb Jan 12, 2026
1bcd8ff
ext/dom
arnaud-lb Jan 12, 2026
fd3c8bf
ext/pcntl
arnaud-lb Jan 12, 2026
ca6abed
ext/reflection
arnaud-lb Jan 12, 2026
988c778
ext/uri
arnaud-lb Jan 12, 2026
7361cd0
ext/standard
arnaud-lb Jan 12, 2026
bb9f446
ext/bcmath
arnaud-lb Jan 12, 2026
bd388af
Remove underscore
arnaud-lb Jan 13, 2026
f4cc6d6
Switch to int
arnaud-lb Jan 13, 2026
366a8fc
Fix build
arnaud-lb Jan 13, 2026
d01df3c
Mark generated files
arnaud-lb Jan 13, 2026
95a2fd3
Header guards
arnaud-lb Jan 13, 2026
f1d17ea
WS
arnaud-lb Jan 13, 2026
4e1f5c6
Remove default case
arnaud-lb Jan 13, 2026
4d590e5
Include _decl.h from the extension main header
arnaud-lb Jan 13, 2026
64915dd
Switch over enum constants
arnaud-lb Jan 13, 2026
40036ff
Check that decl files are up to date
arnaud-lb Jan 14, 2026
664bb2c
Do not incorporate version into hash
arnaud-lb Jan 28, 2026
ec7858a
Fix wrong default
arnaud-lb Jan 28, 2026
e424425
Code style
arnaud-lb Jan 28, 2026
2bb15f6
Refactor bcmatch to avoid the conversion from zend_enum_RoundingMode …
arnaud-lb Jan 28, 2026
da4bcc3
UPGRADING.INTERNALS
arnaud-lb Jan 28, 2026
6c869d9
More specific regex
arnaud-lb Jan 29, 2026
63ac898
Review
arnaud-lb Jan 29, 2026
b9e03ae
Make C enum generateion opt-in
arnaud-lb Jan 30, 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 @@ -56,11 +56,20 @@ PHP 8.6 INTERNALS UPGRADE NOTES
node.
. The zend_exception_save() and zend_exception_restore() functions were
removed.
. 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
@generte-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 @@ -9673,6 +9673,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 @@ -9690,7 +9695,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 @@ -12556,7 +12562,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