Skip to content

Commit a11a36a

Browse files
authored
Merge pull request #2 from phan/optimize-array-shape-cache
Add phan_array_shape_cache_key()
2 parents 54f297d + f4f32d6 commit a11a36a

4 files changed

Lines changed: 122 additions & 0 deletions

File tree

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,27 @@ if (function_exists('phan_unique_union_id')) {
9797
return UnionType::generateUniqueIdFallback($this->type_set, $this->real_type_set);
9898
```
9999

100+
### `phan_array_shape_cache_key(array $field_types, bool $is_nullable): string`
101+
102+
Generate the stable cache key used by `ArrayShapeType::fromFieldTypes()` without allocating intermediate PHP structures.
103+
104+
**Description**:
105+
Produces a deterministic ASCII key incorporating field identifiers and their union type ids, plus the nullable flag. This replaces the PHP logic that built a temporary array and called `serialize()`.
106+
107+
**Performance advantages**:
108+
- Avoids repeated `serialize()` calls and temporary arrays.
109+
- Reuses `generateUniqueId()`/`phan_unique_union_id()` from C without bouncing through PHP.
110+
- Emits a compact key suited for static caches.
111+
112+
**Usage in Phan**:
113+
```php
114+
if (function_exists('phan_array_shape_cache_key')) {
115+
$cache_key = phan_array_shape_cache_key($field_types, $is_nullable);
116+
} else {
117+
$cache_key = ArrayShapeType::generateCacheKeyFallback($field_types, $is_nullable);
118+
}
119+
```
120+
100121
## Benchmark
101122

102123
Test with varying array sizes of Type objects:

phan_helpers.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "ext/hash/php_hash_xxhash.h"
2828
#include "Zend/zend_smart_str.h"
2929
#include "Zend/zend_sort.h"
30+
#include "Zend/zend_interfaces.h"
3031
#include "php_phan_helpers.h"
3132
#include "phan_helpers_arginfo.h"
3233
#include "Zend/zend_exceptions.h"
@@ -135,6 +136,18 @@ static void phan_swap_zend_long(void *a, void *b)
135136
*(zend_long *)b = tmp;
136137
}
137138

139+
static void phan_smart_str_append_long(smart_str *str, zend_long value)
140+
{
141+
zend_string *tmp = zend_long_to_str(value);
142+
smart_str_append(str, tmp);
143+
zend_string_release_ex(tmp, 0);
144+
}
145+
146+
static void phan_smart_str_append_size(smart_str *str, size_t value)
147+
{
148+
phan_smart_str_append_long(str, (zend_long)value);
149+
}
150+
138151
/* {{{ PHP_FUNCTION(phan_unique_union_id)
139152
* Builds the unique union identifier composed of sorted object ids.
140153
*
@@ -222,6 +235,87 @@ PHP_FUNCTION(phan_unique_union_id)
222235
}
223236
/* }}} */
224237

238+
/* {{{ PHP_FUNCTION(phan_array_shape_cache_key)
239+
* Builds a deterministic cache key for ArrayShapeType::fromFieldTypes().
240+
*
241+
* Arguments:
242+
* array $field_types - map of field keys to UnionType/AnnotatedUnionType instances
243+
* bool $is_nullable - whether the array shape is nullable
244+
*
245+
* Returns:
246+
* string - cache key
247+
*/
248+
PHP_FUNCTION(phan_array_shape_cache_key)
249+
{
250+
zval *field_types;
251+
zend_bool is_nullable;
252+
253+
ZEND_PARSE_PARAMETERS_START(2, 2)
254+
Z_PARAM_ARRAY(field_types)
255+
Z_PARAM_BOOL(is_nullable)
256+
ZEND_PARSE_PARAMETERS_END();
257+
258+
HashTable *ht = Z_ARRVAL_P(field_types);
259+
smart_str key = {0};
260+
zval *entry;
261+
zend_ulong idx;
262+
zend_string *str_key;
263+
264+
smart_str_appendc(&key, is_nullable ? '1' : '0');
265+
266+
ZEND_HASH_FOREACH_KEY_VAL(ht, idx, str_key, entry) {
267+
smart_str_appendc(&key, '|');
268+
if (str_key) {
269+
smart_str_appendc(&key, 'S');
270+
smart_str_appendc(&key, ':');
271+
phan_smart_str_append_size(&key, ZSTR_LEN(str_key));
272+
smart_str_appendc(&key, ':');
273+
smart_str_append(&key, str_key);
274+
} else {
275+
smart_str_appendc(&key, 'I');
276+
smart_str_appendc(&key, ':');
277+
phan_smart_str_append_long(&key, (zend_long)idx);
278+
}
279+
280+
smart_str_appendc(&key, ':');
281+
282+
if (UNEXPECTED(Z_TYPE_P(entry) != IS_OBJECT)) {
283+
zval tmp;
284+
ZVAL_COPY(&tmp, entry);
285+
convert_to_string(&tmp);
286+
phan_smart_str_append_size(&key, Z_STRLEN(tmp));
287+
smart_str_appendc(&key, ':');
288+
smart_str_append(&key, Z_STR(tmp));
289+
zval_ptr_dtor(&tmp);
290+
continue;
291+
}
292+
293+
zval retval;
294+
ZVAL_UNDEF(&retval);
295+
zend_call_method_with_0_params(Z_OBJ_P(entry), Z_OBJCE_P(entry), NULL, "generateuniqueid", &retval);
296+
if (UNEXPECTED(Z_TYPE_P(&retval) == IS_UNDEF)) {
297+
smart_str_free(&key);
298+
RETURN_EMPTY_STRING();
299+
}
300+
if (UNEXPECTED(Z_TYPE(retval) != IS_STRING)) {
301+
convert_to_string(&retval);
302+
}
303+
304+
phan_smart_str_append_size(&key, Z_STRLEN(retval));
305+
smart_str_appendc(&key, ':');
306+
smart_str_append(&key, Z_STR(retval));
307+
zval_ptr_dtor(&retval);
308+
} ZEND_HASH_FOREACH_END();
309+
310+
smart_str_0(&key);
311+
if (!key.s) {
312+
RETURN_EMPTY_STRING();
313+
}
314+
315+
RETURN_STR(key.s);
316+
}
317+
/* }}} */
318+
225319
/* Forward declarations for XXH3-128-based hashing with normalization */
226320
static void phan_hash_node_xxh3(smart_str *str, zval *node);
227321
static void phan_hash_value_xxh3(smart_str *str, zval *val);
@@ -482,6 +576,7 @@ PHP_FUNCTION(phan_ast_hash)
482576
static const zend_function_entry phan_helpers_functions[] = {
483577
PHP_FE(phan_unique_types, arginfo_phan_unique_types)
484578
PHP_FE(phan_unique_union_id, arginfo_phan_unique_union_id)
579+
PHP_FE(phan_array_shape_cache_key, arginfo_phan_array_shape_cache_key)
485580
PHP_FE(phan_ast_hash, arginfo_phan_ast_hash)
486581
PHP_FE_END
487582
};

phan_helpers_arginfo.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_phan_unique_union_id, 0, 1, IS_S
1010
ZEND_ARG_TYPE_INFO(0, real_type_list, IS_ARRAY, 0)
1111
ZEND_END_ARG_INFO()
1212

13+
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_phan_array_shape_cache_key, 0, 2, IS_STRING, 0)
14+
ZEND_ARG_TYPE_INFO(0, field_types, IS_ARRAY, 0)
15+
ZEND_ARG_TYPE_INFO(0, is_nullable, _IS_BOOL, 0)
16+
ZEND_END_ARG_INFO()
17+
1318
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_phan_ast_hash, 0, 1, IS_STRING, 0)
1419
ZEND_ARG_INFO(0, node)
1520
ZEND_END_ARG_INFO()

php_phan_helpers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern zend_module_entry phan_helpers_module_entry;
2626

2727
PHP_FUNCTION(phan_unique_types);
2828
PHP_FUNCTION(phan_unique_union_id);
29+
PHP_FUNCTION(phan_array_shape_cache_key);
2930
PHP_MINFO_FUNCTION(phan_helpers);
3031

3132
#endif /* PHP_PHAN_HELPERS_H */

0 commit comments

Comments
 (0)