Skip to content

Commit d833254

Browse files
committed
Update this with latest changes from teds pecl
- Change offsetUnset into a helper method to remove valid offsets - Add insert() method to add 0 or more elements into a valid offset - Make the object iteration behavior with shift/unshift behave consistently. Iterators for a deque of size `n` now only has `n+1` iteration states (one state for each offset, plus one state for iterators being at the end of the deque) - Switch from tracking an iteration offset to tracking an intrusive doubly linked list of active iterators for each Deque instance. This has better results for foreach.phpt when shifting from the start of a deque during iteration.
1 parent ef4ae51 commit d833254

16 files changed

+697
-288
lines changed

ext/collections/collections_deque.c

Lines changed: 364 additions & 235 deletions
Large diffs are not rendered by default.

ext/collections/collections_deque.stub.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
* This supports amortized constant time pushing and popping onto the front or back of the Deque.
1717
*
1818
* Naming is based on https://www.php.net/spldoublylinkedlist
19-
* and on array_push/pop/unshift/shift.
19+
* and on array_push/pop/unshift/shift and array_key_first.
2020
*/
21-
final class Deque implements IteratorAggregate, Countable, JsonSerializable, ArrayAccess
21+
final class Deque implements \IteratorAggregate, \Countable, \JsonSerializable, \ArrayAccess
2222
{
2323
/** Construct the Deque from the values of the Traversable/array, ignoring keys */
2424
public function __construct(iterable $iterator = []) {}
@@ -77,10 +77,15 @@ public function last(): mixed {}
7777
*/
7878
public function toArray(): array {}
7979

80+
/**
81+
* Insert 0 or more values at the given offset of the Deque.
82+
* @throws \OutOfBoundsException if the value of $offset is not within the bounds of this Deque.
83+
*/
84+
public function insert(int $offset, mixed ...$values): void {}
8085
// Must be mixed for compatibility with ArrayAccess
8186
/**
8287
* Returns the value at offset (int)$offset (relative to the start of the Deque)
83-
* @throws \OutOfBoundsException if the value of (int)$offset is not within the bounds of this vector
88+
* @throws \OutOfBoundsException if the value of (int)$offset is not within the bounds of this Deque.
8489
*/
8590
public function offsetGet(mixed $offset): mixed {}
8691
/**
@@ -90,11 +95,12 @@ public function offsetGet(mixed $offset): mixed {}
9095
public function offsetExists(mixed $offset): bool {}
9196
/**
9297
* Sets the value at offset $offset (relative to the start of the Deque) to $value
93-
* @throws \OutOfBoundsException if the value of (int)$offset is not within the bounds of this vector
98+
* @throws \OutOfBoundsException if the value of (int)$offset is not within the bounds of this Deque.
9499
*/
95100
public function offsetSet(mixed $offset, mixed $value): void {}
96101
/**
97-
* @throws \RuntimeException unconditionally because unset and null are different things, unlike SplFixedArray
102+
* Removes the value at (int)$offset from the deque.
103+
* @throws \OutOfBoundsException if the value of (int)$offset is not within the bounds of this Deque.
98104
*/
99105
public function offsetUnset(mixed $offset): void {}
100106

ext/collections/collections_deque_arginfo.h

Lines changed: 11 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Tyson Andre <tandre@php.net> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#ifndef COLLECTIONS_INTERNALITERATOR_H
18+
#define COLLECTIONS_INTERNALITERATOR_H
19+
20+
typedef struct _collections_intrusive_dllist_node {
21+
struct _collections_intrusive_dllist_node *prev;
22+
struct _collections_intrusive_dllist_node *next;
23+
} collections_intrusive_dllist_node;
24+
25+
typedef struct _collections_intrusive_dllist {
26+
struct _collections_intrusive_dllist_node *first;
27+
} collections_intrusive_dllist;
28+
29+
static zend_always_inline void collections_intrusive_dllist_prepend(collections_intrusive_dllist *list, collections_intrusive_dllist_node *node) {
30+
collections_intrusive_dllist_node *first = list->first;
31+
ZEND_ASSERT(node != first);
32+
node->next = first;
33+
node->prev = NULL;
34+
list->first = node;
35+
36+
if (first) {
37+
ZEND_ASSERT(first->prev == NULL);
38+
first->prev = node;
39+
}
40+
}
41+
42+
static zend_always_inline void collections_intrusive_dllist_remove(collections_intrusive_dllist *list, const collections_intrusive_dllist_node *node) {
43+
collections_intrusive_dllist_node *next = node->next;
44+
collections_intrusive_dllist_node *prev = node->prev;
45+
ZEND_ASSERT(node != next);
46+
ZEND_ASSERT(node != prev);
47+
ZEND_ASSERT(next != prev || next == NULL);
48+
if (next) {
49+
next->prev = prev;
50+
}
51+
if (list->first == node) {
52+
list->first = next;
53+
ZEND_ASSERT(prev == NULL);
54+
} else if (prev) {
55+
prev->next = next;
56+
}
57+
}
58+
#endif

ext/collections/collections_util.c

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Tyson Andre <tandre@php.net> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#include "collections_util.h"
18+
19+
/* Override get_properties_for and use the default implementation of get_properties. See https://github.com/php/php-src/issues/9697#issuecomment-1273613175 */
20+
HashTable* collections_noop_empty_array_get_properties_for(zend_object *obj, zend_prop_purpose purpose) {
21+
(void)obj;
22+
(void)purpose;
23+
return NULL;
24+
}
25+
26+
HashTable* collections_noop_get_gc(zend_object *obj, zval **table, int *n) {
27+
/* Zend/zend_gc.c does not initialize table or n. So we need to set n to 0 at minimum. */
28+
*n = 0;
29+
(void) table;
30+
(void) obj;
31+
/* Nothing needs to be garbage collected */
32+
return NULL;
33+
}
34+
35+
HashTable *collections_internaliterator_get_gc(zend_object_iterator *iter, zval **table, int *n)
36+
{
37+
*table = &iter->data;
38+
*n = 1;
39+
return NULL;
40+
}
41+

ext/collections/collections_util.h

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| https://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: Tyson Andre <tandre@php.net> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
#ifndef COLLECTIONS_UTIL_H
17+
#define COLLECTIONS_UTIL_H
18+
19+
#include "Zend/zend.h"
20+
21+
#define COLLECTIONS_MAX_ZVAL_COLLECTION_SIZE HT_MAX_SIZE
22+
#define empty_entry_list ((const void*) (((uint8_t*)NULL) + 16))
23+
static zend_always_inline uint32_t collections_next_pow2_capacity_uint32(uint32_t nSize, uint32_t min) {
24+
if (nSize < min) {
25+
return min;
26+
}
27+
/* Note that for values such as 63 or 31 of the form ((2^n) - 1),
28+
* subtracting and xor are the same things for numbers in the range of 0 to the max. */
29+
#ifdef ZEND_WIN32
30+
unsigned long index;
31+
if (BitScanReverse(&index, nSize - 1)) {
32+
return 0x2u << ((31 - index) ^ 0x1f);
33+
}
34+
/* nSize is ensured to be in the valid range, fall back to it
35+
* rather than using an undefined bit scan result. */
36+
return nSize;
37+
#elif (defined(__GNUC__) || __has_builtin(__builtin_clz)) && defined(PHP_HAVE_BUILTIN_CLZ)
38+
return 0x2u << (__builtin_clz(nSize - 1) ^ 0x1f);
39+
#endif
40+
nSize -= 1;
41+
nSize |= (nSize >> 1);
42+
nSize |= (nSize >> 2);
43+
nSize |= (nSize >> 4);
44+
nSize |= (nSize >> 8);
45+
nSize |= (nSize >> 16);
46+
return nSize + 1;
47+
}
48+
49+
static zend_always_inline size_t collections_next_pow2_capacity(size_t nSize, size_t min) {
50+
#if SIZEOF_SIZE_T <= 4
51+
return collections_next_pow2_capacity_uint32(nSize, min);
52+
#else
53+
if (nSize < min) {
54+
return min;
55+
}
56+
/* Note that for values such as 63 or 31 of the form ((2^n) - 1),
57+
* subtracting and xor are the same things for numbers in the range of 0 to the max. */
58+
#ifdef ZEND_WIN32
59+
unsigned long index;
60+
if (BitScanReverse64(&index, nSize - 1)) {
61+
return 0x2u << ((63 - index) ^ 0x3f);
62+
}
63+
/* nSize is ensured to be in the valid range, fall back to it
64+
* rather than using an undefined bit scan result. */
65+
return nSize;
66+
#elif (defined(__GNUC__) || __has_builtin(__builtin_clz)) && defined(PHP_HAVE_BUILTIN_CLZ)
67+
#if SIZEOF_SIZE_T > SIZEOF_INT
68+
return 0x2u << (__builtin_clzl(nSize - 1) ^ (sizeof(long) * 8 - 1));
69+
#else
70+
return 0x2u << (__builtin_clz(nSize - 1) ^ 0x1f);
71+
#endif
72+
#else
73+
nSize -= 1;
74+
nSize |= (nSize >> 1);
75+
nSize |= (nSize >> 2);
76+
nSize |= (nSize >> 4);
77+
nSize |= (nSize >> 8);
78+
nSize |= (nSize >> 16);
79+
nSize |= (nSize >> 32);
80+
return nSize + 1;
81+
#endif
82+
#endif
83+
}
84+
85+
/**
86+
* Returns absence of zvals or hash table to garbage collect.
87+
* (e.g. for collections that are immutable or made of scalars instead of zvals)
88+
*/
89+
HashTable* collections_noop_get_gc(zend_object *obj, zval **table, int *n);
90+
/**
91+
* Returns the immutable empty array in a get_properties handler.
92+
* This is useful to keep memory low when a datastructure is guaranteed to be free of cycles (e.g. only scalars, or empty)
93+
*/
94+
HashTable* collections_noop_empty_array_get_properties_for(zend_object *obj, zend_prop_purpose purpose);
95+
96+
HashTable *collections_internaliterator_get_gc(zend_object_iterator *iter, zval **table, int *n);
97+
98+
#endif

ext/collections/config.m4

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
PHP_NEW_EXTENSION(collections, php_collections.c collections_deque.c, no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
2-
PHP_INSTALL_HEADERS([ext/collections], [php_collections.h collections_deque.h])
1+
PHP_NEW_EXTENSION(collections, php_collections.c collections_deque.c collections_util.c, no,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
2+
PHP_INSTALL_HEADERS([ext/collections], [php_collections.h collections_deque.h collections_internaliterator.h collections_util.h])
33
PHP_ADD_EXTENSION_DEP(collections, spl, true)
44
PHP_ADD_EXTENSION_DEP(collections, standard, true)
55
PHP_ADD_EXTENSION_DEP(collections, json)

ext/collections/config.w32

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// vim:ft=javascript
22

3-
EXTENSION("collections", "php_collections.c collections_deque.c", false /*never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
3+
EXTENSION("collections", "php_collections.c collections_deque.c collections_util.c", false /*never shared */, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
44
PHP_COLLECTIONS="yes";
5-
PHP_INSTALL_HEADERS("ext/collections", "php_collections.h collections_deque.h");
5+
PHP_INSTALL_HEADERS("ext/collections", "php_collections.h collections_deque.h collections_internaliterator.h collections_util.h");
Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
11
--TEST--
2-
Collections\Deque can be cloned
2+
Collections\Deque can be cloned after building properties table.
33
--FILE--
44
<?php
5-
6-
$it = new Collections\Deque([new stdClass(), new ArrayObject()]);
7-
$it2 = clone $it;
8-
unset($it);
9-
foreach ($it2 as $key => $value) {
10-
echo "Saw entry:\n";
11-
var_dump($key, $value);
12-
}
13-
5+
$x = new Collections\Deque([new stdClass()]);
6+
var_dump($x);
7+
$y = clone $x;
8+
var_dump($y);
149
?>
1510
--EXPECT--
16-
Saw entry:
17-
int(0)
18-
object(stdClass)#2 (0) {
11+
object(Collections\Deque)#1 (1) {
12+
[0]=>
13+
object(stdClass)#2 (0) {
14+
}
1915
}
20-
Saw entry:
21-
int(1)
22-
object(ArrayObject)#3 (1) {
23-
["storage":"ArrayObject":private]=>
24-
array(0) {
16+
object(Collections\Deque)#3 (1) {
17+
[0]=>
18+
object(stdClass)#2 (0) {
2519
}
26-
}
20+
}

0 commit comments

Comments
 (0)