From 4a761a5097725460bf33cb8d36e67e9d472bdadb Mon Sep 17 00:00:00 2001 From: azjezz Date: Sun, 14 Jun 2026 16:43:16 +0100 Subject: [PATCH 1/5] Zend: store the zend_type kind discriminator in a dedicated field zend_type packed the "what does ptr point at" discriminator (_ZEND_TYPE_NAME_BIT / _ZEND_TYPE_LITERAL_NAME_BIT / _ZEND_TYPE_LIST_BIT) into the high bits of type_mask, sharing the word with the MAY_BE_* mask and the arg-info extra flags. Only two bits were left free, which is not enough room for additional type kinds. Move the discriminator into a new `uint32_t kind` field. On 64-bit this occupies the existing 4 bytes of struct padding, so sizeof(zend_type) stays 16 (resolves the long-standing TODO); on 32-bit zend_type grows from 8 to 12 bytes. This frees type_mask bits 22-24 and gives the kind field room for future kinds. Pure refactor, no behaviour change. The bit constants keep their values and are simply read from / written to `kind` now. ZEND_TYPE_IS_SET() additionally checks `kind`, and the JIT's inlined typed-property IS_SET check ORs the kind field in. Signed-off-by: azjezz --- Zend/zend_API.c | 4 ++-- Zend/zend_compile.c | 3 +-- Zend/zend_types.h | 41 +++++++++++++++++------------------ ext/opcache/jit/zend_jit_ir.c | 16 +++++++++----- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 65834adbafff..11489b41cb69 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2937,7 +2937,7 @@ static void zend_convert_internal_arg_info_type(zend_type *type, bool persistent // temporary workaround, we support union types by splitting // the type name on `|` characters if necessary. const char *class_name = ZEND_TYPE_LITERAL_NAME(*type); - type->type_mask &= ~_ZEND_TYPE_LITERAL_NAME_BIT; + type->kind &= ~_ZEND_TYPE_LITERAL_NAME_BIT; size_t num_types = 1; const char *p = class_name; @@ -2951,7 +2951,7 @@ static void zend_convert_internal_arg_info_type(zend_type *type, bool persistent zend_string *str = zend_string_init_interned(class_name, strlen(class_name), persistent); zend_alloc_ce_cache(str); ZEND_TYPE_SET_PTR(*type, str); - type->type_mask |= _ZEND_TYPE_NAME_BIT; + type->kind |= _ZEND_TYPE_NAME_BIT; } else { /* Union type */ zend_type_list *list = pemalloc(ZEND_TYPE_LIST_SIZE(num_types), persistent); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 105f99d24171..746fa95af1d3 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7621,8 +7621,7 @@ static zend_type zend_compile_typename_ex( if (ZEND_TYPE_IS_COMPLEX(single_type)) { if (!ZEND_TYPE_IS_COMPLEX(type) && !is_composite) { /* The first class type can be stored directly as the type ptr payload. */ - ZEND_TYPE_SET_PTR(type, ZEND_TYPE_NAME(single_type)); - ZEND_TYPE_FULL_MASK(type) |= _ZEND_TYPE_NAME_BIT; + ZEND_TYPE_SET_PTR_AND_KIND(type, ZEND_TYPE_NAME(single_type), _ZEND_TYPE_NAME_BIT); } else { if (type_list->num_types == 0) { /* Switch from single name to name list. */ diff --git a/Zend/zend_types.h b/Zend/zend_types.h index cfff3b942c4b..0b36c170ef90 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -119,7 +119,7 @@ typedef struct { * are only supported since C++20). */ void *ptr; uint32_t type_mask; - /* TODO: We could use the extra 32-bit of padding on 64-bit systems. */ + uint32_t kind; } zend_type; typedef struct { @@ -129,11 +129,11 @@ typedef struct { #define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25 #define _ZEND_TYPE_MASK ((1u << 25) - 1) -/* Only one of these bits may be set. */ -#define _ZEND_TYPE_NAME_BIT (1u << 24) -// Used to signify that type.ptr is not a `zend_string*` but a `const char*`, -#define _ZEND_TYPE_LITERAL_NAME_BIT (1u << 23) -#define _ZEND_TYPE_LIST_BIT (1u << 22) +/* These bits live in zend_type.kind (not type_mask). Only one may be set. + * Signifies what type.ptr points at: */ +#define _ZEND_TYPE_NAME_BIT (1u << 24) /* a `zend_string*` (class name) */ +#define _ZEND_TYPE_LITERAL_NAME_BIT (1u << 23) /* a `const char*` (literal class name) */ +#define _ZEND_TYPE_LIST_BIT (1u << 22) /* a `zend_type_list*` */ #define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT) /* For BC behaviour with iterable type */ #define _ZEND_TYPE_ITERABLE_BIT (1u << 21) @@ -149,21 +149,21 @@ typedef struct { #define _ZEND_TYPE_NULLABLE_BIT 0x2u #define ZEND_TYPE_IS_SET(t) \ - (((t).type_mask & _ZEND_TYPE_MASK) != 0) + (((t).type_mask & _ZEND_TYPE_MASK) != 0 || (t).kind != 0) /* If a type is complex it means it's either a list with a union or intersection, * or the void pointer is a class name */ #define ZEND_TYPE_IS_COMPLEX(t) \ - ((((t).type_mask) & _ZEND_TYPE_KIND_MASK) != 0) + ((((t).kind) & _ZEND_TYPE_KIND_MASK) != 0) #define ZEND_TYPE_HAS_NAME(t) \ - ((((t).type_mask) & _ZEND_TYPE_NAME_BIT) != 0) + ((((t).kind) & _ZEND_TYPE_NAME_BIT) != 0) #define ZEND_TYPE_HAS_LITERAL_NAME(t) \ - ((((t).type_mask) & _ZEND_TYPE_LITERAL_NAME_BIT) != 0) + ((((t).kind) & _ZEND_TYPE_LITERAL_NAME_BIT) != 0) #define ZEND_TYPE_HAS_LIST(t) \ - ((((t).type_mask) & _ZEND_TYPE_LIST_BIT) != 0) + ((((t).kind) & _ZEND_TYPE_LIST_BIT) != 0) #define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \ ((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0) @@ -248,8 +248,7 @@ typedef struct { #define ZEND_TYPE_SET_PTR_AND_KIND(t, _ptr, kind_bit) do { \ (t).ptr = (_ptr); \ - (t).type_mask &= ~_ZEND_TYPE_KIND_MASK; \ - (t).type_mask |= (kind_bit); \ + (t).kind = (kind_bit); \ } while (0) #define ZEND_TYPE_SET_LIST(t, list) \ @@ -284,10 +283,10 @@ typedef struct { #endif #define ZEND_TYPE_INIT_NONE(extra_flags) \ - _ZEND_TYPE_PREFIX { NULL, (extra_flags) } + _ZEND_TYPE_PREFIX { NULL, (extra_flags), 0 } #define ZEND_TYPE_INIT_MASK(_type_mask) \ - _ZEND_TYPE_PREFIX { NULL, (_type_mask) } + _ZEND_TYPE_PREFIX { NULL, (_type_mask), 0 } #define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ( (code) == IS_ITERABLE ? _ZEND_TYPE_ITERABLE_BIT : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code))))) \ @@ -295,28 +294,28 @@ typedef struct { #define ZEND_TYPE_INIT_PTR(ptr, type_kind, allow_null, extra_flags) \ _ZEND_TYPE_PREFIX { (void *) (ptr), \ - (type_kind) | ((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags) } + (((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags)), (type_kind) } #define ZEND_TYPE_INIT_PTR_MASK(ptr, type_mask) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (type_mask) } + _ZEND_TYPE_PREFIX { (void *) (ptr), (type_mask), 0 } #define ZEND_TYPE_INIT_UNION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_UNION_BIT) | (extra_flags) } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_UNION_BIT) | (extra_flags), _ZEND_TYPE_LIST_BIT } #define ZEND_TYPE_INIT_INTERSECTION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags) } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags), _ZEND_TYPE_LIST_BIT } #define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \ ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags) #define ZEND_TYPE_INIT_CLASS_MASK(class_name, type_mask) \ - ZEND_TYPE_INIT_PTR_MASK(class_name, _ZEND_TYPE_NAME_BIT | (type_mask)) + _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), _ZEND_TYPE_NAME_BIT } #define ZEND_TYPE_INIT_CLASS_CONST(class_name, allow_null, extra_flags) \ ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_LITERAL_NAME_BIT, allow_null, extra_flags) #define ZEND_TYPE_INIT_CLASS_CONST_MASK(class_name, type_mask) \ - ZEND_TYPE_INIT_PTR_MASK(class_name, (_ZEND_TYPE_LITERAL_NAME_BIT | (type_mask))) + _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), _ZEND_TYPE_LITERAL_NAME_BIT } typedef union _zend_value { zend_long lval; /* long value */ diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index cf43d3ad840f..6808e8225555 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -16115,9 +16115,11 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, // JIT: if (ZEND_TYPE_IS_SET(property_info->type)) prop_info_ref = ir_LOAD_L( ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*) * 2)); - if_typed = ir_IF(ir_AND_U32( - ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))), - ir_CONST_U32(_ZEND_TYPE_MASK))); + if_typed = ir_IF(ir_OR_U32( + ir_AND_U32( + ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))), + ir_CONST_U32(_ZEND_TYPE_MASK)), + ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.kind))))); ir_IF_FALSE(if_typed); ir_END_list(merge); ir_IF_TRUE(if_typed); @@ -16144,9 +16146,11 @@ static int zend_jit_fetch_static_prop(zend_jit_ctx *jit, const zend_op *opline, // JIT: if (ZEND_TYPE_IS_SET(property_info->type)) prop_info_ref = ir_LOAD_L( ir_ADD_OFFSET(ir_LOAD_A(jit_EX(run_time_cache)), cache_slot + sizeof(void*) * 2)); - if_typed = ir_IF(ir_AND_U32( - ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))), - ir_CONST_U32(_ZEND_TYPE_MASK))); + if_typed = ir_IF(ir_OR_U32( + ir_AND_U32( + ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.type_mask))), + ir_CONST_U32(_ZEND_TYPE_MASK)), + ir_LOAD_U32(ir_ADD_OFFSET(prop_info_ref, offsetof(zend_property_info, type.kind))))); ir_IF_FALSE(if_typed); ir_END_list(merge); ir_IF_TRUE(if_typed); From 4ebadfb752c42b56bb4140ba11a29725490d7239 Mon Sep 17 00:00:00 2001 From: azjezz Date: Sun, 14 Jun 2026 18:50:11 +0100 Subject: [PATCH 2/5] Zend: make zend_type.kind a discriminator enum, not a bit flag The kind field added in the previous commit was modelled as one-hot bit flags (NAME, LITERAL_NAME, LIST), but exactly one is ever set at a time: it discriminates what type.ptr points at, it is not a bitset like type_mask. Treating it as a bitmask was misleading and forced bit ops where a plain compare suffices. Renumber the values sequentially (NONE=0, NAME=1, LITERAL_NAME=2, LIST=3) and turn the predicates into equality comparisons: IS_COMPLEX kind != NONE HAS_NAME kind == NAME HAS_LITERAL_NAME kind == LITERAL_NAME HAS_LIST kind == LIST Assignment replaces read-modify-write at the few mutation sites (zend_convert_internal_arg_info_type no longer clears then ORs). No behaviour change. Signed-off-by: azjezz --- Zend/zend_API.c | 3 +-- Zend/zend_compile.c | 2 +- Zend/zend_types.h | 39 +++++++++++++++++++-------------------- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 11489b41cb69..bbd17fa68cd2 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2937,7 +2937,6 @@ static void zend_convert_internal_arg_info_type(zend_type *type, bool persistent // temporary workaround, we support union types by splitting // the type name on `|` characters if necessary. const char *class_name = ZEND_TYPE_LITERAL_NAME(*type); - type->kind &= ~_ZEND_TYPE_LITERAL_NAME_BIT; size_t num_types = 1; const char *p = class_name; @@ -2951,7 +2950,7 @@ static void zend_convert_internal_arg_info_type(zend_type *type, bool persistent zend_string *str = zend_string_init_interned(class_name, strlen(class_name), persistent); zend_alloc_ce_cache(str); ZEND_TYPE_SET_PTR(*type, str); - type->kind |= _ZEND_TYPE_NAME_BIT; + type->kind = _ZEND_TYPE_KIND_NAME; } else { /* Union type */ zend_type_list *list = pemalloc(ZEND_TYPE_LIST_SIZE(num_types), persistent); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 746fa95af1d3..82b19b5e784d 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7621,7 +7621,7 @@ static zend_type zend_compile_typename_ex( if (ZEND_TYPE_IS_COMPLEX(single_type)) { if (!ZEND_TYPE_IS_COMPLEX(type) && !is_composite) { /* The first class type can be stored directly as the type ptr payload. */ - ZEND_TYPE_SET_PTR_AND_KIND(type, ZEND_TYPE_NAME(single_type), _ZEND_TYPE_NAME_BIT); + ZEND_TYPE_SET_PTR_AND_KIND(type, ZEND_TYPE_NAME(single_type), _ZEND_TYPE_KIND_NAME); } else { if (type_list->num_types == 0) { /* Switch from single name to name list. */ diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 0b36c170ef90..f50116270c0e 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -129,12 +129,6 @@ typedef struct { #define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25 #define _ZEND_TYPE_MASK ((1u << 25) - 1) -/* These bits live in zend_type.kind (not type_mask). Only one may be set. - * Signifies what type.ptr points at: */ -#define _ZEND_TYPE_NAME_BIT (1u << 24) /* a `zend_string*` (class name) */ -#define _ZEND_TYPE_LITERAL_NAME_BIT (1u << 23) /* a `const char*` (literal class name) */ -#define _ZEND_TYPE_LIST_BIT (1u << 22) /* a `zend_type_list*` */ -#define _ZEND_TYPE_KIND_MASK (_ZEND_TYPE_LIST_BIT|_ZEND_TYPE_NAME_BIT|_ZEND_TYPE_LITERAL_NAME_BIT) /* For BC behaviour with iterable type */ #define _ZEND_TYPE_ITERABLE_BIT (1u << 21) /* Whether the type list is arena allocated */ @@ -147,23 +141,28 @@ typedef struct { #define _ZEND_TYPE_MAY_BE_MASK ((1u << 18) - 1) /* Must have same value as MAY_BE_NULL */ #define _ZEND_TYPE_NULLABLE_BIT 0x2u +/* zend_type.kind discriminates what type.ptr points at. It is a plain value, + * not a bitmask: exactly one of these applies at a time. */ +#define _ZEND_TYPE_KIND_NONE 0u /* no payload; ptr is NULL */ +#define _ZEND_TYPE_KIND_NAME 1u /* a `zend_string*` (class name) */ +#define _ZEND_TYPE_KIND_LITERAL_NAME 2u /* a `const char*` (literal class name) */ +#define _ZEND_TYPE_KIND_LIST 3u /* a `zend_type_list*` */ #define ZEND_TYPE_IS_SET(t) \ - (((t).type_mask & _ZEND_TYPE_MASK) != 0 || (t).kind != 0) + (((t).type_mask & _ZEND_TYPE_MASK) != 0 || (t).kind != _ZEND_TYPE_KIND_NONE) -/* If a type is complex it means it's either a list with a union or intersection, - * or the void pointer is a class name */ +/* A complex type carries a ptr payload: a class name or a type list. */ #define ZEND_TYPE_IS_COMPLEX(t) \ - ((((t).kind) & _ZEND_TYPE_KIND_MASK) != 0) + ((t).kind != _ZEND_TYPE_KIND_NONE) #define ZEND_TYPE_HAS_NAME(t) \ - ((((t).kind) & _ZEND_TYPE_NAME_BIT) != 0) + ((t).kind == _ZEND_TYPE_KIND_NAME) #define ZEND_TYPE_HAS_LITERAL_NAME(t) \ - ((((t).kind) & _ZEND_TYPE_LITERAL_NAME_BIT) != 0) + ((t).kind == _ZEND_TYPE_KIND_LITERAL_NAME) #define ZEND_TYPE_HAS_LIST(t) \ - ((((t).kind) & _ZEND_TYPE_LIST_BIT) != 0) + ((t).kind == _ZEND_TYPE_KIND_LIST) #define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \ ((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0) @@ -252,7 +251,7 @@ typedef struct { } while (0) #define ZEND_TYPE_SET_LIST(t, list) \ - ZEND_TYPE_SET_PTR_AND_KIND(t, list, _ZEND_TYPE_LIST_BIT) + ZEND_TYPE_SET_PTR_AND_KIND(t, list, _ZEND_TYPE_KIND_LIST) /* FULL_MASK() includes the MAY_BE_* type mask, as well as additional metadata bits. * The PURE_MASK() only includes the MAY_BE_* type mask. */ @@ -300,22 +299,22 @@ typedef struct { _ZEND_TYPE_PREFIX { (void *) (ptr), (type_mask), 0 } #define ZEND_TYPE_INIT_UNION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_UNION_BIT) | (extra_flags), _ZEND_TYPE_LIST_BIT } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_UNION_BIT) | (extra_flags), _ZEND_TYPE_KIND_LIST } #define ZEND_TYPE_INIT_INTERSECTION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags), _ZEND_TYPE_LIST_BIT } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags), _ZEND_TYPE_KIND_LIST } #define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \ - ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_NAME_BIT, allow_null, extra_flags) + ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_KIND_NAME, allow_null, extra_flags) #define ZEND_TYPE_INIT_CLASS_MASK(class_name, type_mask) \ - _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), _ZEND_TYPE_NAME_BIT } + _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), _ZEND_TYPE_KIND_NAME } #define ZEND_TYPE_INIT_CLASS_CONST(class_name, allow_null, extra_flags) \ - ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_LITERAL_NAME_BIT, allow_null, extra_flags) + ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_KIND_LITERAL_NAME, allow_null, extra_flags) #define ZEND_TYPE_INIT_CLASS_CONST_MASK(class_name, type_mask) \ - _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), _ZEND_TYPE_LITERAL_NAME_BIT } + _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), _ZEND_TYPE_KIND_LITERAL_NAME } typedef union _zend_value { zend_long lval; /* long value */ From 62f92e08e82e7ebe24dffec9c09adeb957e52b52 Mon Sep 17 00:00:00 2001 From: azjezz Date: Sun, 14 Jun 2026 19:25:13 +0100 Subject: [PATCH 3/5] Zend: make zend_type_kind a real enum, drop the leading underscore Address review feedback. Signed-off-by: azjezz --- Zend/zend_API.c | 2 +- Zend/zend_compile.c | 2 +- Zend/zend_types.h | 47 ++++++++++++++++++++++++--------------------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index bbd17fa68cd2..f9499c49d72b 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2950,7 +2950,7 @@ static void zend_convert_internal_arg_info_type(zend_type *type, bool persistent zend_string *str = zend_string_init_interned(class_name, strlen(class_name), persistent); zend_alloc_ce_cache(str); ZEND_TYPE_SET_PTR(*type, str); - type->kind = _ZEND_TYPE_KIND_NAME; + type->kind = ZEND_TYPE_KIND_NAME; } else { /* Union type */ zend_type_list *list = pemalloc(ZEND_TYPE_LIST_SIZE(num_types), persistent); diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 82b19b5e784d..26cf551a5378 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -7621,7 +7621,7 @@ static zend_type zend_compile_typename_ex( if (ZEND_TYPE_IS_COMPLEX(single_type)) { if (!ZEND_TYPE_IS_COMPLEX(type) && !is_composite) { /* The first class type can be stored directly as the type ptr payload. */ - ZEND_TYPE_SET_PTR_AND_KIND(type, ZEND_TYPE_NAME(single_type), _ZEND_TYPE_KIND_NAME); + ZEND_TYPE_SET_PTR_AND_KIND(type, ZEND_TYPE_NAME(single_type), ZEND_TYPE_KIND_NAME); } else { if (type_list->num_types == 0) { /* Switch from single name to name list. */ diff --git a/Zend/zend_types.h b/Zend/zend_types.h index f50116270c0e..50e6c50e4579 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -113,13 +113,22 @@ typedef void (*copy_ctor_func_t)(zval *pElement); * ZEND_TYPE_INIT_*() should be used for construction. */ +/* zend_type.kind discriminates what type.ptr points at. Exactly one value + * applies at a time; it is a plain enum, not a bitmask like type_mask. */ +typedef enum { + ZEND_TYPE_KIND_NONE = 0, /* no payload; ptr is NULL */ + ZEND_TYPE_KIND_NAME = 1, /* a `zend_string*` (class name) */ + ZEND_TYPE_KIND_LITERAL_NAME = 2, /* a `const char*` (literal class name) */ + ZEND_TYPE_KIND_LIST = 3, /* a `zend_type_list*` */ +} zend_type_kind; + typedef struct { /* Not using a union here, because there's no good way to initialize them * in a way that is supported in both C and C++ (designated initializers * are only supported since C++20). */ void *ptr; uint32_t type_mask; - uint32_t kind; + zend_type_kind kind; } zend_type; typedef struct { @@ -141,28 +150,22 @@ typedef struct { #define _ZEND_TYPE_MAY_BE_MASK ((1u << 18) - 1) /* Must have same value as MAY_BE_NULL */ #define _ZEND_TYPE_NULLABLE_BIT 0x2u -/* zend_type.kind discriminates what type.ptr points at. It is a plain value, - * not a bitmask: exactly one of these applies at a time. */ -#define _ZEND_TYPE_KIND_NONE 0u /* no payload; ptr is NULL */ -#define _ZEND_TYPE_KIND_NAME 1u /* a `zend_string*` (class name) */ -#define _ZEND_TYPE_KIND_LITERAL_NAME 2u /* a `const char*` (literal class name) */ -#define _ZEND_TYPE_KIND_LIST 3u /* a `zend_type_list*` */ #define ZEND_TYPE_IS_SET(t) \ - (((t).type_mask & _ZEND_TYPE_MASK) != 0 || (t).kind != _ZEND_TYPE_KIND_NONE) + (((t).type_mask & _ZEND_TYPE_MASK) != 0 || (t).kind != ZEND_TYPE_KIND_NONE) /* A complex type carries a ptr payload: a class name or a type list. */ #define ZEND_TYPE_IS_COMPLEX(t) \ - ((t).kind != _ZEND_TYPE_KIND_NONE) + ((t).kind != ZEND_TYPE_KIND_NONE) #define ZEND_TYPE_HAS_NAME(t) \ - ((t).kind == _ZEND_TYPE_KIND_NAME) + ((t).kind == ZEND_TYPE_KIND_NAME) #define ZEND_TYPE_HAS_LITERAL_NAME(t) \ - ((t).kind == _ZEND_TYPE_KIND_LITERAL_NAME) + ((t).kind == ZEND_TYPE_KIND_LITERAL_NAME) #define ZEND_TYPE_HAS_LIST(t) \ - ((t).kind == _ZEND_TYPE_KIND_LIST) + ((t).kind == ZEND_TYPE_KIND_LIST) #define ZEND_TYPE_IS_ITERABLE_FALLBACK(t) \ ((((t).type_mask) & _ZEND_TYPE_ITERABLE_BIT) != 0) @@ -251,7 +254,7 @@ typedef struct { } while (0) #define ZEND_TYPE_SET_LIST(t, list) \ - ZEND_TYPE_SET_PTR_AND_KIND(t, list, _ZEND_TYPE_KIND_LIST) + ZEND_TYPE_SET_PTR_AND_KIND(t, list, ZEND_TYPE_KIND_LIST) /* FULL_MASK() includes the MAY_BE_* type mask, as well as additional metadata bits. * The PURE_MASK() only includes the MAY_BE_* type mask. */ @@ -282,10 +285,10 @@ typedef struct { #endif #define ZEND_TYPE_INIT_NONE(extra_flags) \ - _ZEND_TYPE_PREFIX { NULL, (extra_flags), 0 } + _ZEND_TYPE_PREFIX { NULL, (extra_flags), ZEND_TYPE_KIND_NONE } #define ZEND_TYPE_INIT_MASK(_type_mask) \ - _ZEND_TYPE_PREFIX { NULL, (_type_mask), 0 } + _ZEND_TYPE_PREFIX { NULL, (_type_mask), ZEND_TYPE_KIND_NONE } #define ZEND_TYPE_INIT_CODE(code, allow_null, extra_flags) \ ZEND_TYPE_INIT_MASK(((code) == _IS_BOOL ? MAY_BE_BOOL : ( (code) == IS_ITERABLE ? _ZEND_TYPE_ITERABLE_BIT : ((code) == IS_MIXED ? MAY_BE_ANY : (1 << (code))))) \ @@ -296,25 +299,25 @@ typedef struct { (((allow_null) ? _ZEND_TYPE_NULLABLE_BIT : 0) | (extra_flags)), (type_kind) } #define ZEND_TYPE_INIT_PTR_MASK(ptr, type_mask) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (type_mask), 0 } + _ZEND_TYPE_PREFIX { (void *) (ptr), (type_mask), ZEND_TYPE_KIND_NONE } #define ZEND_TYPE_INIT_UNION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_UNION_BIT) | (extra_flags), _ZEND_TYPE_KIND_LIST } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_UNION_BIT) | (extra_flags), ZEND_TYPE_KIND_LIST } #define ZEND_TYPE_INIT_INTERSECTION(ptr, extra_flags) \ - _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags), _ZEND_TYPE_KIND_LIST } + _ZEND_TYPE_PREFIX { (void *) (ptr), (_ZEND_TYPE_INTERSECTION_BIT) | (extra_flags), ZEND_TYPE_KIND_LIST } #define ZEND_TYPE_INIT_CLASS(class_name, allow_null, extra_flags) \ - ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_KIND_NAME, allow_null, extra_flags) + ZEND_TYPE_INIT_PTR(class_name, ZEND_TYPE_KIND_NAME, allow_null, extra_flags) #define ZEND_TYPE_INIT_CLASS_MASK(class_name, type_mask) \ - _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), _ZEND_TYPE_KIND_NAME } + _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), ZEND_TYPE_KIND_NAME } #define ZEND_TYPE_INIT_CLASS_CONST(class_name, allow_null, extra_flags) \ - ZEND_TYPE_INIT_PTR(class_name, _ZEND_TYPE_KIND_LITERAL_NAME, allow_null, extra_flags) + ZEND_TYPE_INIT_PTR(class_name, ZEND_TYPE_KIND_LITERAL_NAME, allow_null, extra_flags) #define ZEND_TYPE_INIT_CLASS_CONST_MASK(class_name, type_mask) \ - _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), _ZEND_TYPE_KIND_LITERAL_NAME } + _ZEND_TYPE_PREFIX { (void *) (class_name), (type_mask), ZEND_TYPE_KIND_LITERAL_NAME } typedef union _zend_value { zend_long lval; /* long value */ From 5fa9abfb7c4af6af69b29a6f113edda8c8af8355 Mon Sep 17 00:00:00 2001 From: Seifeddine Gmati <29315886+azjezz@users.noreply.github.com> Date: Sun, 14 Jun 2026 20:00:25 +0100 Subject: [PATCH 4/5] Zend: give zend_type_kind enum a tag name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tim Düsterhus --- Zend/zend_types.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Zend/zend_types.h b/Zend/zend_types.h index 50e6c50e4579..ec49dbca70a9 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -115,7 +115,7 @@ typedef void (*copy_ctor_func_t)(zval *pElement); /* zend_type.kind discriminates what type.ptr points at. Exactly one value * applies at a time; it is a plain enum, not a bitmask like type_mask. */ -typedef enum { +typedef enum zend_type_kind { ZEND_TYPE_KIND_NONE = 0, /* no payload; ptr is NULL */ ZEND_TYPE_KIND_NAME = 1, /* a `zend_string*` (class name) */ ZEND_TYPE_KIND_LITERAL_NAME = 2, /* a `const char*` (literal class name) */ From 947657f07b99e2f560b4212bd5473f7e6eeda9ae Mon Sep 17 00:00:00 2001 From: Seifeddine Gmati <29315886+azjezz@users.noreply.github.com> Date: Sun, 14 Jun 2026 20:00:46 +0100 Subject: [PATCH 5/5] Zend: drop redundant explicit zend_type_kind enumerator values MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tim Düsterhus --- Zend/zend_types.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Zend/zend_types.h b/Zend/zend_types.h index ec49dbca70a9..0258341e52da 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -116,10 +116,10 @@ typedef void (*copy_ctor_func_t)(zval *pElement); /* zend_type.kind discriminates what type.ptr points at. Exactly one value * applies at a time; it is a plain enum, not a bitmask like type_mask. */ typedef enum zend_type_kind { - ZEND_TYPE_KIND_NONE = 0, /* no payload; ptr is NULL */ - ZEND_TYPE_KIND_NAME = 1, /* a `zend_string*` (class name) */ - ZEND_TYPE_KIND_LITERAL_NAME = 2, /* a `const char*` (literal class name) */ - ZEND_TYPE_KIND_LIST = 3, /* a `zend_type_list*` */ + ZEND_TYPE_KIND_NONE, /* no payload; ptr is NULL */ + ZEND_TYPE_KIND_NAME, /* a `zend_string*` (class name) */ + ZEND_TYPE_KIND_LITERAL_NAME, /* a `const char*` (literal class name) */ + ZEND_TYPE_KIND_LIST, /* a `zend_type_list*` */ } zend_type_kind; typedef struct {