diff --git a/Zend/zend.c b/Zend/zend.c
index 2d8a0f455f8b4..7451b9a40dad8 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -1195,6 +1195,7 @@ void zend_shutdown(void) /* {{{ */
 	free(GLOBAL_CONSTANTS_TABLE);
 	zend_shutdown_strtod();
 	zend_attributes_shutdown();
+	zend_type_free_interned_trees();
 
 #ifdef ZTS
 	GLOBAL_FUNCTION_TABLE = NULL;
diff --git a/Zend/zend_API.c b/Zend/zend_API.c
index 5bc4b4a04509f..78cfb19a2ba8b 100644
--- a/Zend/zend_API.c
+++ b/Zend/zend_API.c
@@ -2898,7 +2898,246 @@ ZEND_API void zend_add_magic_method(zend_class_entry *ce, zend_function *fptr, z
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arg_info_toString, 0, 0, IS_STRING, 0)
 ZEND_END_ARG_INFO()
 
-static zend_always_inline void zend_normalize_internal_type(zend_type *type) {
+ZEND_API void zend_type_free_interned_trees(void) {
+	zend_type_node *tree = NULL;
+	ZEND_HASH_FOREACH_PTR(CG(type_trees), tree) {
+		if (tree->kind != ZEND_TYPE_SIMPLE) {
+			pefree(tree->compound.types, 1);
+		} else {
+			if (ZEND_TYPE_HAS_NAME(tree->simple_type)) {
+				if (!ZSTR_IS_INTERNED(ZEND_TYPE_NAME(tree->simple_type))) {
+					zend_string_release_ex(ZEND_TYPE_NAME(tree->simple_type), 1);
+				}
+			}
+		}
+		pefree(tree, 1);
+	} ZEND_HASH_FOREACH_END();
+	zend_hash_destroy(CG(type_trees));
+	pefree(CG(type_trees), 1);
+	CG(type_trees) = NULL;
+}
+
+static int compare_simple_types(const zend_type a, const zend_type b) {
+	const uint32_t a_mask = ZEND_TYPE_FULL_MASK(a);
+	const uint32_t b_mask = ZEND_TYPE_FULL_MASK(b);
+
+	if (a_mask != b_mask) {
+		return a_mask < b_mask ? -1 : 1;
+	}
+
+	const bool a_has_name = ZEND_TYPE_HAS_NAME(a);
+	const bool b_has_name = ZEND_TYPE_HAS_NAME(b);
+
+	if (a_has_name && b_has_name) {
+		const zend_string *a_name = ZEND_TYPE_NAME(a);
+		const zend_string *b_name = ZEND_TYPE_NAME(b);
+		const int cmp = ZSTR_VAL(a_name) - ZSTR_VAL(b_name);
+		if (cmp != 0) {
+			return cmp;
+		}
+	}
+
+	const bool a_nullable = ZEND_TYPE_ALLOW_NULL(a);
+	const bool b_nullable = ZEND_TYPE_ALLOW_NULL(b);
+
+	if (a_nullable != b_nullable) {
+		return a_nullable ? 1 : -1;
+	}
+
+	// Types are equal
+	return 0;
+}
+
+static int compare_type_nodes(const void *a_, const void *b_) {
+	const zend_type_node *a = *(zend_type_node **)a_;
+	const zend_type_node *b = *(zend_type_node **)b_;
+
+	if (a->kind != b->kind) {
+		return (int)a->kind - (int)b->kind;
+	}
+
+	if (a->kind == ZEND_TYPE_SIMPLE) {
+		return compare_simple_types(a->simple_type, b->simple_type);
+	}
+
+	if (a->compound.num_types != b->compound.num_types) {
+		return (int)a->compound.num_types - (int)b->compound.num_types;
+	}
+
+	for (uint32_t i = 0; i < a->compound.num_types; i++) {
+		const int cmp = compare_type_nodes(&a->compound.types[i], &b->compound.types[i]);
+		if (cmp != 0) {
+			return cmp;
+		}
+	}
+
+	return 0;
+}
+
+zend_ulong zend_type_node_hash(const zend_type_node *node) {
+	zend_ulong hash = 2166136261u; // FNV-1a offset basis
+
+	hash ^= (zend_ulong)node->kind;
+	hash *= 16777619;
+
+	switch (node->kind) {
+		case ZEND_TYPE_SIMPLE: {
+			const zend_type type = node->simple_type;
+			hash ^= (zend_ulong)ZEND_TYPE_FULL_MASK(type);
+			hash *= 16777619;
+
+			if (ZEND_TYPE_HAS_NAME(type)) {
+				zend_string *name = ZEND_TYPE_NAME(type);
+				hash ^= zend_string_hash_val(name);
+				hash *= 16777619;
+			}
+
+			break;
+		}
+
+		case ZEND_TYPE_UNION:
+		case ZEND_TYPE_INTERSECTION: {
+			for (uint32_t i = 0; i < node->compound.num_types; ++i) {
+				const zend_ulong child_hash = zend_type_node_hash(node->compound.types[i]);
+				hash ^= child_hash;
+				hash *= 16777619;
+			}
+			break;
+		}
+	}
+
+	return hash;
+}
+
+bool zend_type_node_equals(const zend_type_node *a, const zend_type_node *b) {
+	if (a == b) return true;
+	if (a->kind != b->kind) return false;
+
+	if (a->kind == ZEND_TYPE_SIMPLE) {
+		const zend_type at = a->simple_type;
+		const zend_type bt = b->simple_type;
+
+		if (ZEND_TYPE_FULL_MASK(at) != ZEND_TYPE_FULL_MASK(bt)) {
+			return false;
+		}
+
+		const bool a_has_name = ZEND_TYPE_HAS_NAME(at);
+		const bool b_has_name = ZEND_TYPE_HAS_NAME(bt);
+		if (a_has_name != b_has_name) {
+			return false;
+		}
+
+		if (a_has_name) {
+			const zend_string *a_name = ZEND_TYPE_NAME(at);
+			const zend_string *b_name = ZEND_TYPE_NAME(bt);
+			if (!zend_string_equals_ci(a_name, b_name)) {
+				return false;
+			}
+		}
+
+		return true;
+	}
+
+	// Compound type: union or intersection
+	if (a->compound.num_types != b->compound.num_types) {
+		return false;
+	}
+
+	for (uint32_t i = 0; i < a->compound.num_types; ++i) {
+		if (!zend_type_node_equals(a->compound.types[i], b->compound.types[i])) {
+			return false;
+		}
+	}
+
+	return true;
+}
+
+static zend_type_node *intern_type_node(zend_type_node *node) {
+	const zend_ulong hash = zend_type_node_hash(node);
+	zend_type_node *existing;
+
+	if (CG(type_trees) == NULL) {
+		CG(type_trees) = pemalloc(sizeof(HashTable), 1);
+		zend_hash_init(CG(type_trees), 64, NULL, NULL, 1);
+	}
+
+	if ((existing = zend_hash_index_find_ptr(CG(type_trees), hash))) {
+		if (zend_type_node_equals(existing, node)) {
+			if (node->kind != ZEND_TYPE_SIMPLE) {
+				pefree(node->compound.types, 1);
+			}
+			pefree(node, 1);
+			return existing; // reuse interned node
+		}
+	}
+
+	if (node->kind == ZEND_TYPE_SIMPLE) {
+		if (ZEND_TYPE_HAS_NAME(node->simple_type)) {
+			const zend_string *name = ZEND_TYPE_NAME(node->simple_type);
+			zend_string *new_name = zend_string_init_interned(ZSTR_VAL(name), ZSTR_LEN(name), 1);
+			ZEND_TYPE_SET_PTR(node->simple_type, new_name);
+		}
+	}
+
+	zend_hash_index_add_new_ptr(CG(type_trees), hash, node);
+	return node;
+}
+
+ZEND_API zend_type_node *zend_type_to_interned_tree(const zend_type type) {
+	if (type.type_mask == 0) {
+		return NULL;
+	}
+
+	if (!ZEND_TYPE_HAS_LIST(type)) {
+		zend_type_node *node = pemalloc(sizeof(zend_type_node), 1);
+		node->kind = ZEND_TYPE_SIMPLE;
+		node->simple_type = type;
+		return intern_type_node(node);
+	}
+
+	zend_type_list *list = ZEND_TYPE_LIST(type);
+	const zend_type_node_kind kind = ZEND_TYPE_IS_INTERSECTION(type) ?
+		ZEND_TYPE_INTERSECTION : ZEND_TYPE_UNION;
+
+	zend_type_node **children = NULL;
+	uint32_t num_children = 0;
+
+	zend_type *subtype;
+
+	children = pemalloc(list->num_types * sizeof(zend_type_node *), 1);
+
+	ZEND_TYPE_LIST_FOREACH(list, subtype) {
+		zend_type_node *child = zend_type_to_interned_tree(*subtype);
+
+		if (child->kind == kind) {
+			for (uint32_t i = 0; i < child->compound.num_types; i++) {
+				children[num_children++] = child->compound.types[i];
+			}
+		} else {
+			children[num_children++] = child;
+		}
+	} ZEND_TYPE_LIST_FOREACH_END();
+
+	qsort(children, num_children, sizeof(zend_type_node*), compare_type_nodes);
+
+	size_t deduped_count = 0;
+	for (size_t i = 0; i < num_children; i++) {
+		if (i == 0 || children[i] != children[i - 1]) {
+			children[deduped_count++] = children[i];
+		}
+	}
+
+	zend_type_node *node = pemalloc(sizeof(zend_type_node), 1);
+	node->kind = kind;
+	node->compound.num_types = deduped_count;
+	node->compound.types = pemalloc(sizeof(zend_type_node *) * deduped_count, 1);
+	memcpy(node->compound.types, children, sizeof(zend_type_node *) * deduped_count);
+	pefree(children, 1);
+
+	return intern_type_node(node);
+}
+
+static zend_always_inline zend_type_node *zend_normalize_internal_type(zend_type *type) {
 	ZEND_ASSERT(!ZEND_TYPE_HAS_LITERAL_NAME(*type));
 	if (ZEND_TYPE_PURE_MASK(*type) != MAY_BE_ANY) {
 		ZEND_ASSERT(!ZEND_TYPE_CONTAINS_CODE(*type, IS_RESOURCE) && "resource is not allowed in a zend_type");
@@ -2921,6 +3160,8 @@ static zend_always_inline void zend_normalize_internal_type(zend_type *type) {
 			} ZEND_TYPE_FOREACH_END();
 		}
 	} ZEND_TYPE_FOREACH_END();
+
+	return zend_type_to_interned_tree(*type);
 }
 
 /* registers all functions in *library_functions in the function hash */
@@ -3190,7 +3431,7 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
 					new_arg_info[i].type = legacy_iterable;
 				}
 
-				zend_normalize_internal_type(&new_arg_info[i].type);
+				new_arg_info[i].type_tree = zend_normalize_internal_type(&new_arg_info[i].type);
 			}
 		}
 
@@ -4665,7 +4906,9 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z
 	property_info->type = type;
 
 	if (is_persistent_class(ce)) {
-		zend_normalize_internal_type(&property_info->type);
+		property_info->type_tree = zend_normalize_internal_type(&property_info->type);
+	} else {
+		property_info->type_tree = zend_type_to_interned_tree(property_info->type);
 	}
 
 	zend_hash_update_ptr(&ce->properties_info, name, property_info);
diff --git a/Zend/zend_API.h b/Zend/zend_API.h
index 6aeffce25d8e5..7962e75d6c494 100644
--- a/Zend/zend_API.h
+++ b/Zend/zend_API.h
@@ -128,46 +128,46 @@ typedef struct _zend_fcall_info_cache {
 
 /* Arginfo structures without type information */
 #define ZEND_ARG_INFO(pass_by_ref, name) \
-	{ #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL },
+	{ #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL },
 #define ZEND_ARG_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, default_value) \
-	{ #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value },
+	{ #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value },
 #define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) \
-	{ #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL },
+	{ #name, ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL },
 
 /* Arginfo structures with simple type information */
 #define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \
-	{ #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL },
+	{ #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL },
 #define ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, type_hint, allow_null, default_value) \
-	{ #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value },
+	{ #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value },
 #define ZEND_ARG_VARIADIC_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) \
-	{ #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL },
+	{ #name, ZEND_TYPE_INIT_CODE(type_hint, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL },
 
 /* Arginfo structures with complex type information */
 #define ZEND_ARG_TYPE_MASK(pass_by_ref, name, type_mask, default_value) \
-	{ #name, ZEND_TYPE_INIT_MASK(type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value },
+	{ #name, ZEND_TYPE_INIT_MASK(type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value },
 #define ZEND_ARG_OBJ_TYPE_MASK(pass_by_ref, name, class_name, type_mask, default_value) \
-	{ #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value },
+	{ #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value },
 #define ZEND_ARG_VARIADIC_OBJ_TYPE_MASK(pass_by_ref, name, class_name, type_mask) \
-	{ #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL },
+	{ #name, ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type_mask | _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL },
 
 /* Arginfo structures with object type information */
 #define ZEND_ARG_OBJ_INFO(pass_by_ref, name, class_name, allow_null) \
-	{ #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL },
+	{ #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL },
 #define ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(pass_by_ref, name, class_name, allow_null, default_value) \
-	{ #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), default_value },
+	{ #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, default_value },
 #define ZEND_ARG_VARIADIC_OBJ_INFO(pass_by_ref, name, class_name, allow_null) \
-	{ #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL },
+	{ #name, ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 1, 0)), NULL, NULL },
 
 /* Legacy arginfo structures */
 #define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) \
-	{ #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL },
+	{ #name, ZEND_TYPE_INIT_CODE(IS_ARRAY, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL },
 #define ZEND_ARG_CALLABLE_INFO(pass_by_ref, name, allow_null) \
-	{ #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL },
+	{ #name, ZEND_TYPE_INIT_CODE(IS_CALLABLE, allow_null, _ZEND_ARG_INFO_FLAGS(pass_by_ref, 0, 0)), NULL, NULL },
 
 #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX2(name, return_reference, required_num_args, class_name, allow_null, is_tentative_return_type) \
 	static const zend_internal_arg_info name[] = { \
 		{ (const char*)(uintptr_t)(required_num_args), \
-			ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL },
+			ZEND_TYPE_INIT_CLASS_CONST(#class_name, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL },
 
 #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(name, return_reference, required_num_args, class_name, allow_null) \
 	ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX2(name, return_reference, required_num_args, class_name, allow_null, 0)
@@ -180,7 +180,7 @@ typedef struct _zend_fcall_info_cache {
 
 #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX2(name, return_reference, required_num_args, type, is_tentative_return_type) \
 	static const zend_internal_arg_info name[] = { \
-		{ (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_MASK(type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL },
+		{ (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_MASK(type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL },
 
 #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(name, return_reference, required_num_args, type) \
 	ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX2(name, return_reference, required_num_args, type, 0)
@@ -190,7 +190,7 @@ typedef struct _zend_fcall_info_cache {
 
 #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX2(name, return_reference, required_num_args, class_name, type, is_tentative_return_type) \
 	static const zend_internal_arg_info name[] = { \
-		{ (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL },
+		{ (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CLASS_CONST_MASK(#class_name, type | _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL },
 
 #define ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(name, return_reference, required_num_args, class_name, type) \
 	ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX2(name, return_reference, required_num_args, class_name, type, 0)
@@ -200,7 +200,7 @@ typedef struct _zend_fcall_info_cache {
 
 #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX2(name, return_reference, required_num_args, type, allow_null, is_tentative_return_type) \
 	static const zend_internal_arg_info name[] = { \
-		{ (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL },
+		{ (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_CODE(type, allow_null, _ZEND_ARG_INFO_FLAGS(return_reference, 0, is_tentative_return_type)), NULL, NULL },
 
 #define ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(name, return_reference, required_num_args, type, allow_null) \
 	ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX2(name, return_reference, required_num_args, type, allow_null, 0)
@@ -213,7 +213,7 @@ typedef struct _zend_fcall_info_cache {
 
 #define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args)	\
 	static const zend_internal_arg_info name[] = { \
-		{ (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(return_reference, 0, 0)), NULL },
+		{ (const char*)(uintptr_t)(required_num_args), ZEND_TYPE_INIT_NONE(_ZEND_ARG_INFO_FLAGS(return_reference, 0, 0)), NULL, NULL },
 #define ZEND_BEGIN_ARG_INFO(name, _unused)	\
 	ZEND_BEGIN_ARG_INFO_EX(name, {}, ZEND_RETURN_VALUE, -1)
 #define ZEND_END_ARG_INFO()		};
@@ -448,6 +448,9 @@ ZEND_API zend_result zend_update_class_constant(zend_class_constant *c, const ze
 ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type);
 ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type);
 
+ZEND_API zend_type_node *zend_type_to_interned_tree(zend_type type);
+ZEND_API void zend_type_free_interned_trees(void);
+
 static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry *ce) {
 	if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) && ZEND_MAP_PTR(ce->mutable_data)) {
 		zend_class_mutable_data *mutable_data =
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c
index 8e4221673c4cf..ddb9ece5ae5d2 100644
--- a/Zend/zend_compile.c
+++ b/Zend/zend_compile.c
@@ -7348,6 +7348,7 @@ static zend_type zend_compile_typename_ex(
 	}
 
 	ast->attr = orig_ast_attr;
+
 	return type;
 }
 /* }}} */
@@ -7584,6 +7585,7 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
 		} else {
 			arg_infos->type = (zend_type) ZEND_TYPE_INIT_CODE(fallback_return_type, 0, 0);
 		}
+		arg_infos->type_tree = zend_type_to_interned_tree(arg_infos->type);
 		arg_infos++;
 		op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE;
 
@@ -7830,6 +7832,8 @@ static void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast, uint32
 					&prop->attributes, attributes_ast, 0, ZEND_ATTRIBUTE_TARGET_PROPERTY, ZEND_ATTRIBUTE_TARGET_PARAMETER);
 			}
 		}
+
+		arg_info->type_tree = zend_type_to_interned_tree(arg_info->type);
 	}
 
 	/* These are assigned at the end to avoid uninitialized memory in case of an error */
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h
index 224a68be749cb..2ac0e8eb52b3c 100644
--- a/Zend/zend_compile.h
+++ b/Zend/zend_compile.h
@@ -452,6 +452,7 @@ typedef struct _zend_property_info {
 	HashTable *attributes;
 	zend_class_entry *ce;
 	zend_type type;
+	zend_type_node *type_tree;
 	const zend_property_info *prototype;
 	zend_function **hooks;
 } zend_property_info;
@@ -479,6 +480,7 @@ typedef struct _zend_class_constant {
 typedef struct _zend_internal_arg_info {
 	const char *name;
 	zend_type type;
+	zend_type_node *type_tree;
 	const char *default_value;
 } zend_internal_arg_info;
 
@@ -486,6 +488,7 @@ typedef struct _zend_internal_arg_info {
 typedef struct _zend_arg_info {
 	zend_string *name;
 	zend_type type;
+	zend_type_node *type_tree;
 	zend_string *default_value;
 } zend_arg_info;
 
@@ -497,6 +500,7 @@ typedef struct _zend_arg_info {
 typedef struct _zend_internal_function_info {
 	uintptr_t required_num_args;
 	zend_type type;
+	zend_type_node *type_tree;
 	const char *default_value;
 } zend_internal_function_info;
 
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 6b6af2c225f79..7033ca1c9945d 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -1112,9 +1112,10 @@ static zend_always_inline bool zend_value_instanceof_static(zval *zv) {
 static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot(
 		void **cache_slot, zend_type *type)
 {
-	if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) {
-		return (zend_class_entry *) *cache_slot;
-	}
+	// todo: work with cache_slot
+	//if (EXPECTED(HAVE_CACHE_SLOT && *cache_slot)) {
+	//	return (zend_class_entry *) *cache_slot;
+	//}
 
 	zend_string *name = ZEND_TYPE_NAME(*type);
 	zend_class_entry *ce;
@@ -1140,71 +1141,47 @@ static zend_always_inline zend_class_entry *zend_fetch_ce_from_cache_slot(
 	return ce;
 }
 
-static bool zend_check_intersection_type_from_cache_slot(zend_type_list *intersection_type_list,
-	zend_class_entry *arg_ce, void ***cache_slot_ptr)
-{
-	void **cache_slot = *cache_slot_ptr;
-	zend_class_entry *ce;
-	zend_type *list_type;
-	bool status = true;
-	ZEND_TYPE_LIST_FOREACH(intersection_type_list, list_type) {
-		/* Only check classes if the type might be valid */
-		if (status) {
-			ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type);
-			/* If type is not an instance of one of the types taking part in the
-			 * intersection it cannot be a valid instance of the whole intersection type. */
-			if (!ce || !instanceof_function(arg_ce, ce)) {
-				status = false;
-			}
-		}
-		PROGRESS_CACHE_SLOT();
-	} ZEND_TYPE_LIST_FOREACH_END();
-	if (HAVE_CACHE_SLOT) {
-		*cache_slot_ptr = cache_slot;
-	}
-	return status;
-}
 
-static zend_always_inline bool zend_check_type_slow(
-		zend_type *type, zval *arg, zend_reference *ref, void **cache_slot,
+static bool zend_check_type_slow(
+		zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot,
 		bool is_return_type, bool is_internal)
 {
-	uint32_t type_mask;
-	if (ZEND_TYPE_IS_COMPLEX(*type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
-		zend_class_entry *ce;
-		if (UNEXPECTED(ZEND_TYPE_HAS_LIST(*type))) {
-			zend_type *list_type;
-			if (ZEND_TYPE_IS_INTERSECTION(*type)) {
-				return zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*type), Z_OBJCE_P(arg), &cache_slot);
-			} else {
-				ZEND_TYPE_LIST_FOREACH(ZEND_TYPE_LIST(*type), list_type) {
-					if (ZEND_TYPE_IS_INTERSECTION(*list_type)) {
-						if (zend_check_intersection_type_from_cache_slot(ZEND_TYPE_LIST(*list_type), Z_OBJCE_P(arg), &cache_slot)) {
-							return true;
-						}
-						/* The cache_slot is progressed in zend_check_intersection_type_from_cache_slot() */
-					} else {
-						ZEND_ASSERT(!ZEND_TYPE_HAS_LIST(*list_type));
-						ce = zend_fetch_ce_from_cache_slot(cache_slot, list_type);
-						/* Instance of a single type part of a union is sufficient to pass the type check */
-						if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
-							return true;
-						}
-						PROGRESS_CACHE_SLOT();
+	if (EXPECTED(type_tree != NULL) && type_tree->kind != ZEND_TYPE_SIMPLE) {
+		switch (type_tree->kind) {
+			case ZEND_TYPE_UNION: {
+				for (uint32_t i = 0; i < type_tree->compound.num_types; i++) {
+					if (zend_check_type_slow(type, type_tree->compound.types[i], arg, ref, cache_slot, is_return_type, is_internal)) {
+						return true;
 					}
-				} ZEND_TYPE_LIST_FOREACH_END();
+				}
+				return false;
 			}
-		} else {
-			ce = zend_fetch_ce_from_cache_slot(cache_slot, type);
-			/* If we have a CE we check if it satisfies the type constraint,
-			 * otherwise it will check if a standard type satisfies it. */
-			if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
+
+			case ZEND_TYPE_INTERSECTION: {
+				for (uint32_t i = 0; i < type_tree->compound.num_types; i++) {
+					if (!zend_check_type_slow(type, type_tree->compound.types[i], arg, ref, cache_slot, is_return_type, is_internal)) {
+						return false;
+					}
+				}
 				return true;
 			}
+
+			default:
+				return false;
 		}
 	}
 
-	type_mask = ZEND_TYPE_FULL_MASK(*type);
+	if (type_tree != NULL && ZEND_TYPE_IS_COMPLEX(type_tree->simple_type) && EXPECTED(Z_TYPE_P(arg) == IS_OBJECT)) {
+		const zend_class_entry *ce = zend_fetch_ce_from_cache_slot(cache_slot, &type_tree->simple_type);
+		/* If we have a CE we check if it satisfies the type constraint,
+		 * otherwise it will check if a standard type satisfies it. */
+		if (ce && instanceof_function(Z_OBJCE_P(arg), ce)) {
+			return true;
+		}
+		PROGRESS_CACHE_SLOT();
+	}
+
+	const uint32_t type_mask = ZEND_TYPE_FULL_MASK(*type);
 	if ((type_mask & MAY_BE_CALLABLE) &&
 		zend_is_callable(arg, is_internal ? IS_CALLABLE_SUPPRESS_DEPRECATIONS : 0, NULL)) {
 		return 1;
@@ -1232,7 +1209,7 @@ static zend_always_inline bool zend_check_type_slow(
 }
 
 static zend_always_inline bool zend_check_type(
-		zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope,
+		zend_type *type, zend_type_node *type_tree, zval *arg, void **cache_slot, zend_class_entry *scope,
 		bool is_return_type, bool is_internal)
 {
 	zend_reference *ref = NULL;
@@ -1247,14 +1224,14 @@ static zend_always_inline bool zend_check_type(
 		return 1;
 	}
 
-	return zend_check_type_slow(type, arg, ref, cache_slot, is_return_type, is_internal);
+	return zend_check_type_slow(type, type_tree, arg, ref, cache_slot, is_return_type, is_internal);
 }
 
 ZEND_API bool zend_check_user_type_slow(
-		zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type)
+		zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type)
 {
 	return zend_check_type_slow(
-		type, arg, ref, cache_slot, is_return_type, /* is_internal */ false);
+		type, type_tree, arg, ref, cache_slot, is_return_type, /* is_internal */ false);
 }
 
 static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot)
@@ -1265,7 +1242,7 @@ static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint
 	cur_arg_info = &zf->common.arg_info[arg_num-1];
 
 	if (ZEND_TYPE_IS_SET(cur_arg_info->type)
-			&& UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, cache_slot, zf->common.scope, 0, 0))) {
+			&& UNEXPECTED(!zend_check_type(&cur_arg_info->type, cur_arg_info->type_tree, arg, cache_slot, zf->common.scope, 0, 0))) {
 		zend_verify_arg_error(zf, cur_arg_info, arg_num, arg);
 		return 0;
 	}
@@ -1277,7 +1254,7 @@ static zend_always_inline bool zend_verify_variadic_arg_type(
 		zend_function *zf, zend_arg_info *arg_info, uint32_t arg_num, zval *arg, void **cache_slot)
 {
 	ZEND_ASSERT(ZEND_TYPE_IS_SET(arg_info->type));
-	if (UNEXPECTED(!zend_check_type(&arg_info->type, arg, cache_slot, zf->common.scope, 0, 0))) {
+	if (UNEXPECTED(!zend_check_type(&arg_info->type, arg_info->type_tree, arg, cache_slot, zf->common.scope, 0, 0))) {
 		zend_verify_arg_error(zf, arg_info, arg_num, arg);
 		return 0;
 	}
@@ -1302,7 +1279,7 @@ static zend_never_inline ZEND_ATTRIBUTE_UNUSED bool zend_verify_internal_arg_typ
 		}
 
 		if (ZEND_TYPE_IS_SET(cur_arg_info->type)
-				&& UNEXPECTED(!zend_check_type(&cur_arg_info->type, arg, /* cache_slot */ NULL, fbc->common.scope, 0, /* is_internal */ 1))) {
+				&& UNEXPECTED(!zend_check_type(&cur_arg_info->type, cur_arg_info->type_tree, arg, /* cache_slot */ NULL, fbc->common.scope, 0, /* is_internal */ 1))) {
 			return 0;
 		}
 		arg++;
@@ -1508,7 +1485,7 @@ ZEND_API bool zend_verify_internal_return_type(zend_function *zf, zval *ret)
 		return 1;
 	}
 
-	if (UNEXPECTED(!zend_check_type(&ret_info->type, ret, /* cache_slot */ NULL, NULL, 1, /* is_internal */ 1))) {
+	if (UNEXPECTED(!zend_check_type(&ret_info->type, ret_info->type_tree, ret, /* cache_slot */ NULL, NULL, 1, /* is_internal */ 1))) {
 		zend_verify_internal_return_error(zf, ret);
 		return 0;
 	}
diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h
index 3b8ed89ec4f38..5e40229591c67 100644
--- a/Zend/zend_execute.h
+++ b/Zend/zend_execute.h
@@ -105,7 +105,7 @@ ZEND_API ZEND_COLD void zend_verify_never_error(
 		const zend_function *zf);
 ZEND_API bool zend_verify_ref_array_assignable(zend_reference *ref);
 ZEND_API bool zend_check_user_type_slow(
-		zend_type *type, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type);
+		zend_type *type, zend_type_node *type_tree, zval *arg, zend_reference *ref, void **cache_slot, bool is_return_type);
 
 #if ZEND_DEBUG
 ZEND_API bool zend_internal_call_should_throw(zend_function *fbc, zend_execute_data *call);
diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c
index 1f55521fb72f1..435651dfc4917 100644
--- a/Zend/zend_execute_API.c
+++ b/Zend/zend_execute_API.c
@@ -148,6 +148,7 @@ void init_executor(void) /* {{{ */
 
 	EG(function_table) = CG(function_table);
 	EG(class_table) = CG(class_table);
+	EG(type_trees) = CG(type_trees);
 
 	EG(in_autoload) = NULL;
 	EG(error_handling) = EH_NORMAL;
diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h
index 079bfb99caccf..c551ef9f5c48a 100644
--- a/Zend/zend_globals.h
+++ b/Zend/zend_globals.h
@@ -94,6 +94,7 @@ struct _zend_compiler_globals {
 
 	HashTable *function_table;	/* function symbol table */
 	HashTable *class_table;		/* class table */
+	HashTable *type_trees;		/* type trees table */
 
 	HashTable *auto_globals;
 
@@ -191,6 +192,7 @@ struct _zend_executor_globals {
 	HashTable *function_table;	/* function symbol table */
 	HashTable *class_table;		/* class table */
 	HashTable *zend_constants;	/* constants table */
+	HashTable *type_trees		/* type trees table */;
 
 	zval          *vm_stack_top;
 	zval          *vm_stack_end;
diff --git a/Zend/zend_types.h b/Zend/zend_types.h
index f839cec3b3667..89d9c3908cc49 100644
--- a/Zend/zend_types.h
+++ b/Zend/zend_types.h
@@ -142,6 +142,27 @@ typedef struct {
 	zend_type types[1];
 } zend_type_list;
 
+typedef struct _zend_type_node zend_type_node;
+
+typedef enum {
+	ZEND_TYPE_SIMPLE,
+	ZEND_TYPE_UNION,
+	ZEND_TYPE_INTERSECTION,
+} zend_type_node_kind;
+
+struct _zend_type_node {
+	zend_type_node_kind kind;
+
+	union {
+		zend_type simple_type;
+
+		struct {
+			uint32_t num_types;
+			zend_type_node **types;
+		} compound;
+	};
+};
+
 #define _ZEND_TYPE_EXTRA_FLAGS_SHIFT 25
 #define _ZEND_TYPE_MASK ((1u << 25) - 1)
 /* Only one of these bits may be set. */
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 6c87b81bc3bd7..5831afd35bf8d 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -4427,7 +4427,7 @@ ZEND_VM_COLD_CONST_HANDLER(124, ZEND_VERIFY_RETURN_TYPE, CONST|TMP|VAR|UNUSED|CV
 		}
 
 		SAVE_OPLINE();
-		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
+		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) {
 			zend_verify_return_error(EX(func), retval_ptr);
 			HANDLE_EXCEPTION();
 		}
diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h
index 06b4ad6b1494a..2c03b7f252d3c 100644
--- a/Zend/zend_vm_execute.h
+++ b/Zend/zend_vm_execute.h
@@ -10803,7 +10803,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYP
 		}
 
 		SAVE_OPLINE();
-		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
+		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) {
 			zend_verify_return_error(EX(func), retval_ptr);
 			HANDLE_EXCEPTION();
 		}
@@ -21540,7 +21540,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_TMP_UN
 		}
 
 		SAVE_OPLINE();
-		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
+		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) {
 			zend_verify_return_error(EX(func), retval_ptr);
 			HANDLE_EXCEPTION();
 		}
@@ -30020,7 +30020,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_VAR_UN
 		}
 
 		SAVE_OPLINE();
-		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
+		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) {
 			zend_verify_return_error(EX(func), retval_ptr);
 			HANDLE_EXCEPTION();
 		}
@@ -37814,7 +37814,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_UNUSED
 		}
 
 		SAVE_OPLINE();
-		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
+		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) {
 			zend_verify_return_error(EX(func), retval_ptr);
 			HANDLE_EXCEPTION();
 		}
@@ -50615,7 +50615,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_VERIFY_RETURN_TYPE_SPEC_CV_UNU
 		}
 
 		SAVE_OPLINE();
-		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, retval_ptr, ref, cache_slot, 1, 0))) {
+		if (UNEXPECTED(!zend_check_type_slow(&ret_info->type, ret_info->type_tree, retval_ptr, ref, cache_slot, 1, 0))) {
 			zend_verify_return_error(EX(func), retval_ptr);
 			HANDLE_EXCEPTION();
 		}
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index 8caa0cbd4398e..8d2086fab2100 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -4803,6 +4803,7 @@ static zend_result accel_finish_startup_preload(bool in_child)
 #endif
 		php_request_shutdown(NULL); /* calls zend_shared_alloc_unlock(); */
 		EG(class_table) = NULL;
+		EG(type_trees) = NULL;
 		EG(function_table) = NULL;
 		PG(report_memleaks) = orig_report_memleaks;
 #ifdef ZTS
diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c
index 177feea3afa6e..308f06d31bb03 100644
--- a/ext/opcache/jit/zend_jit_helpers.c
+++ b/ext/opcache/jit/zend_jit_helpers.c
@@ -1902,7 +1902,7 @@ static bool ZEND_FASTCALL zend_jit_verify_arg_slow(zval *arg, zend_arg_info *arg
 	const zend_op *opline = EX(opline);
 	void **cache_slot = CACHE_ADDR(opline->extended_value);
 	bool ret = zend_check_user_type_slow(
-		&arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ false);
+		&arg_info->type, arg_info->type_tree, arg, /* ref */ NULL, cache_slot, /* is_return_type */ false);
 	if (UNEXPECTED(!ret)) {
 		zend_verify_arg_error(EX(func), arg_info, opline->op1.num, arg);
 		return 0;
@@ -1919,7 +1919,7 @@ static void ZEND_FASTCALL zend_jit_verify_return_slow(zval *arg, const zend_op_a
 		}
 	}
 	if (UNEXPECTED(!zend_check_user_type_slow(
-			&arg_info->type, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) {
+			&arg_info->type, arg_info->type_tree, arg, /* ref */ NULL, cache_slot, /* is_return_type */ true))) {
 		zend_verify_return_error((zend_function*)op_array, arg);
 	}
 }
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index e3f87ee1e1636..4558e5e747cb0 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -1202,8 +1202,8 @@ static void register_ZendTestClass_dnf_property(zend_class_entry *ce) {
 // The types are upgraded to DNF types in `register_dynamic_function_entries()`
 static zend_internal_arg_info arginfo_zend_test_internal_dnf_arguments[] = {
 	// first entry is a zend_internal_function_info (see zend_compile.h): {argument_count, return_type, unused}
-	{(const char*)(uintptr_t)(1), {0}, NULL},
-	{"arg", {0}, NULL}
+	{(const char*)(uintptr_t)(1), {0}, NULL, NULL},
+	{"arg", {0}, NULL, NULL}
 };
 
 static ZEND_NAMED_FUNCTION(zend_test_internal_dnf_arguments)