-
Notifications
You must be signed in to change notification settings - Fork 3k
/
type-array.h
669 lines (559 loc) · 20.1 KB
/
type-array.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_ARRAY_H_
#define incl_HPHP_ARRAY_H_
#include "hphp/runtime/base/array-data.h"
#include "hphp/runtime/base/datatype.h"
#include "hphp/runtime/base/member-val.h"
#include "hphp/runtime/base/req-ptr.h"
#include "hphp/runtime/base/tv-variant.h"
#include "hphp/runtime/base/types.h"
#include <algorithm>
#include <vector>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
struct ArrayIter;
struct VariableUnserializer;
////////////////////////////////////////////////////////////////////////////////
enum class AccessFlags {
None = 0,
Error = 1,
Key = 2,
ErrorKey = Error | Key,
};
inline AccessFlags operator&(AccessFlags a, AccessFlags b) {
return static_cast<AccessFlags>(static_cast<int>(a) & static_cast<int>(b));
}
constexpr bool any(AccessFlags a) {
return a != AccessFlags::None;
}
////////////////////////////////////////////////////////////////////////////////
/*
* Array type wrapping ArrayData to implement reference counting, copy-on-write
* and ArrayData escalation.
*
* "Escalation" happens when an underlying ArrayData cannot handle an operation
* and instead it needs to "upgrade" itself to be a more general (but slower)
* type of ArrayData to accomplish the task.
*/
struct Array {
private:
using Ptr = req::ptr<ArrayData>;
using NoIncRef = Ptr::NoIncRef;
using NonNull = Ptr::NonNull;
using Flags = AccessFlags;
public:
/*
* Create an empty array or an array with one element.
*
* Note these are different than the copy (or copy-like) constructors that
* also take one value.
*/
static Array Create() {
return Array(ArrayData::Create(), NoIncRef{});
}
static Array CreateVec() {
return Array(ArrayData::CreateVec(), NoIncRef{});
}
static Array CreateDict() {
return Array(ArrayData::CreateDict(), NoIncRef{});
}
static Array CreateKeyset() {
return Array(ArrayData::CreateKeyset(), NoIncRef{});
}
static Array CreateVArray() {
return Array(ArrayData::CreateVArray(), NoIncRef{});
}
static Array CreateDArray() {
return Array(ArrayData::CreateDArray(), NoIncRef{});
}
static Array Create(const Variant& value) {
return Array(ArrayData::Create(value), NoIncRef{});
}
static Array Create(const Variant& key, const Variant& value);
/////////////////////////////////////////////////////////////////////////////
Array() {}
~Array();
/*
* Take ownership of `ad'.
*/
static ALWAYS_INLINE Array attach(ArrayData* ad) {
return Array(ad, NoIncRef{});
}
/*
* Transfer ownership of our underlying ArrayData.
*/
ArrayData* detach() { return m_arr.detach(); }
/*
* Get or null out the underlying ArrayData pointer.
*/
ArrayData* get() const { return m_arr.get(); }
void reset(ArrayData* arr = nullptr) { m_arr.reset(arr); }
/*
* Delegate to the underlying ArrayData.
*
* Deliberately doesn't throw_null_pointer_exception as a perf optimization.
*/
ArrayData* operator->() const { return m_arr.get(); }
/*
* "Copy" constructors.
*/
explicit Array(ArrayData* data) : m_arr(data) { }
Array(const Array& arr) : m_arr(arr.m_arr) { }
/*
* Special constructor for use from ArrayInit that creates an Array without a
* null check and without an inc-ref.
*/
enum class ArrayInitCtor { Tag };
explicit Array(ArrayData* ad, ArrayInitCtor)
: m_arr(ad, NoIncRef{})
{}
/*
* Move constructor.
*/
Array(Array&& src) noexcept : m_arr(std::move(src.m_arr)) { }
/*
* Assignment.
*/
Array& operator=(ArrayData* data) {
m_arr = data;
return *this;
}
Array& operator=(const Array& arr) {
m_arr = arr.m_arr;
return *this;
}
Array& operator=(const Variant& v);
/*
* Move assignment.
*/
Array& operator=(Array&& src) {
m_arr = std::move(src.m_arr);
return *this;
}
Array& operator=(Variant&& v);
/*
* Escalate the underlying ArrayData.
*/
void escalate();
#define COPY_BODY(meth, def) \
if (!m_arr) return def; \
auto new_arr = m_arr->meth; \
return new_arr != m_arr ? Array{new_arr, NoIncRef{}} : Array{*this};
/*
* Make a copy of this array.
*
* Like the underlying ArrayData::copy operation, the returned Array may
* point to the same underlying array as the original, or a new one.
*/
Array copy() const { COPY_BODY(copy(), Array{}) }
Array toVec() const { COPY_BODY(toVec(true), CreateVec()) }
Array toDict() const { COPY_BODY(toDict(true), CreateDict()) }
Array toKeyset() const { COPY_BODY(toKeyset(true), CreateKeyset()) }
Array toPHPArray() const { COPY_BODY(toPHPArray(true), Array{}) }
Array toVArray() const { COPY_BODY(toVArray(true), CreateVArray()) }
Array toDArray() const { COPY_BODY(toDArray(true), CreateDArray()) }
#undef COPY_BODY
/////////////////////////////////////////////////////////////////////////////
/*
* Nullability.
*/
bool isNull() const { return !m_arr; }
/*
* Size.
*/
bool empty() const { return !m_arr || m_arr->empty(); }
ssize_t size() const { return m_arr ? m_arr->size() : 0; }
ssize_t length() const { return m_arr ? m_arr->size() : 0; }
/*
* Array kind.
*/
bool isVecArray() const { return m_arr && m_arr->isVecArray(); }
bool isDict() const { return m_arr && m_arr->isDict(); }
bool isKeyset() const { return m_arr && m_arr->isKeyset(); }
bool isHackArray() const { return m_arr && m_arr->isHackArray(); }
bool isPHPArray() const { return !m_arr || m_arr->isPHPArray(); }
bool isVArray() const { return m_arr && m_arr->isVArray(); }
bool isDArray() const { return m_arr && m_arr->isDArray(); }
bool isVecOrVArray() const { return m_arr && m_arr->isVecOrVArray(); }
bool isDictOrDArray() const { return m_arr && m_arr->isDictOrDArray(); }
/////////////////////////////////////////////////////////////////////////////
/*
* Start iterator.
*
* See array-iterator.h for end() and next().
*/
ArrayIter begin(const String& context = null_string) const;
/*
* Converts `k' to a valid key for this array kind.
*/
Cell convertKey(Cell k) const;
Cell convertKey(const Variant& k) const;
/*
* Should int-like string keys be implicitly converted to integers before they
* are inserted?
*/
bool useWeakKeys() const {
// If array isn't set we may implicitly create a mixed array, which uses
// weak keys. We never implicitly create a Hack array.
return !m_arr || m_arr->useWeakKeys();
}
/*
* Convert the underlying ArrayData to a static copy of itself.
*/
void setEvalScalar() const;
/////////////////////////////////////////////////////////////////////////////
// PHP operations.
/*
* Get a packed array of this Array's values.
*/
Array values() const;
/*
* PHP array union operator.
*/
Array operator+(ArrayData* data) const;
Array operator+(const Array& v) const;
Array operator+(const Variant& v) const = delete;
Array& operator+=(ArrayData* data);
Array& operator+=(const Array& v);
Array& operator+=(const Variant& v);
/*
* Implementation of array_merge().
*
* This is different from operator+(), where existing keys' values are NOT
* modified. This function will actually override with new values.
*
* When merging a packed array with another packed array, new elements are
* always appended, and this is also different from operator+() where
* existing numeric indices are not modified.
*/
Array& merge(const Array& arr);
/*
* Comparison function for array operations.
*/
using PFUNC_CMP = int (*)(const Variant& v1, const Variant& v2,
const void* data);
/*
* Return the entries that have keys and/or values that are (intersect), or
* are not (diff) present in `array'.
*
* Keys and values can be compared by user supplied functions and `key_data'
* or `value_data' will be passed into the corresponding `cmp_function' as
* the `data' parameter. Otherwise, equal() will be called for comparisons.
* If both `by_key' and `by_value' are true, both keys and values have to
* match to be included (intersect) or excluded (diff).
*/
Array diff(const Variant& array, bool by_key, bool by_value,
PFUNC_CMP key_cmp_function = nullptr,
const void* key_data = nullptr,
PFUNC_CMP value_cmp_function = nullptr,
const void* value_data = nullptr) const;
Array intersect(const Variant& array, bool by_key, bool by_value,
PFUNC_CMP key_cmp_function = nullptr,
const void* key_data = nullptr,
PFUNC_CMP value_cmp_function = nullptr,
const void* value_data = nullptr) const;
/*
* Sorting.
*/
static int SortRegularAscending(const Variant& v1, const Variant& v2,
const void* data);
static int SortNumericAscending(const Variant& v1, const Variant& v2,
const void* data);
static int SortStringAscending(const Variant& v1, const Variant& v2,
const void* data);
static int SortStringAscendingCase(const Variant& v1, const Variant& v2,
const void* data);
static int SortLocaleStringAscending(const Variant& v1, const Variant& v2,
const void* data);
static int SortRegularDescending(const Variant& v1, const Variant& v2,
const void* data);
static int SortNumericDescending(const Variant& v1, const Variant& v2,
const void* data);
static int SortStringDescending(const Variant& v1, const Variant& v2,
const void* data);
static int SortStringDescendingCase(const Variant& v1, const Variant& v2,
const void* data);
static int SortLocaleStringDescending(const Variant& v1, const Variant& v2,
const void* data);
static int SortNaturalAscending(const Variant& v1, const Variant& v2,
const void* data);
static int SortNaturalDescending(const Variant& v1, const Variant& v2,
const void* data);
static int SortNaturalCaseAscending(const Variant& v1, const Variant& v2,
const void* data);
static int SortNaturalCaseDescending(const Variant& v1, const Variant& v2,
const void* data);
void sort(PFUNC_CMP cmp_func, bool by_key, bool renumber,
const void* data = nullptr);
/*
* Sort multiple arrays at once similar to how ORDER BY clause works in SQL.
*/
struct SortData {
Variant* original;
const Array* array;
bool by_key;
PFUNC_CMP cmp_func;
const void* data;
std::vector<ssize_t> positions;
};
static bool MultiSort(std::vector<SortData>& data, bool renumber);
static void SortImpl(std::vector<int>& indices, const Array& source,
Array::SortData& opaque,
Array::PFUNC_CMP cmp_func,
bool by_key, const void* data = nullptr);
/*
* Type conversions.
*/
bool toBoolean() const { return m_arr && !m_arr->empty(); }
int8_t toByte() const { return toBoolean() ? 1 : 0; }
int16_t toInt16() const { return toBoolean() ? 1 : 0; }
int32_t toInt32() const { return toBoolean() ? 1 : 0; }
int64_t toInt64() const { return toBoolean() ? 1 : 0; }
double toDouble() const { return toBoolean() ? 1.0 : 0.0; }
String toString() const;
/*
* Comparisons.
*/
bool same(const Array& v2) const;
bool same(const Object& v2) const;
bool equal(const Array& v2) const;
bool equal(const Object& v2) const;
bool less(const Array& v2, bool flip = false) const;
bool less(const Object& v2) const;
bool less(const Variant& v2) const;
bool more(const Array& v2, bool flip = true) const;
bool more(const Object& v2) const;
bool more(const Variant& v2) const;
int compare(const Array& v2, bool flip = false) const;
/////////////////////////////////////////////////////////////////////////////
// Element rval/lval.
#define FOR_EACH_KEY_TYPE(...) \
C(Cell, __VA_ARGS__) \
I(int, __VA_ARGS__) \
I(int64_t, __VA_ARGS__) \
V(const String&, __VA_ARGS__) \
V(const Variant&, __VA_ARGS__) \
D(double, __VA_ARGS__)
/*
* Get a refcounted copy of the element at `key'.
*/
Variant operator[](Cell key) const;
Variant operator[](int key) const;
Variant operator[](int64_t key) const;
Variant operator[](const String& key) const;
Variant operator[](const Variant& key) const;
Variant operator[](double key) const = delete;
Variant operator[](const char*) const = delete;
#define C(key_t, name, ret_t, cns) \
ret_t name(key_t, Flags = Flags::None) cns;
#define V C
#define I V
#define D(key_t, name, ret_t, cns) \
ret_t name(key_t, Flags = Flags::None) cns = delete;
/*
* Get an rval to the element at `key'.
*/
FOR_EACH_KEY_TYPE(rvalAt, member_rval, const)
/*
* Get an lval to the element at `key'.
*
* These are ArrayData::lval() and ArrayData::lvalRef(), with CoW and
* escalation. As with those functions, the Ref versions should be used if
* the lval will be boxed, and the non-Ref versions should be used otherwise.
*/
FOR_EACH_KEY_TYPE(lvalAt, member_lval, )
FOR_EACH_KEY_TYPE(lvalAtRef, member_lval, )
#undef D
#undef I
#undef V
#undef C
/*
* Get an lval to a newly created element.
*/
member_lval lvalAt();
member_lval lvalAtRef();
/////////////////////////////////////////////////////////////////////////////
// Element access and mutation.
#define C(key_t, ret_t, name, cns) ret_t name(key_t, bool isKey = false) cns;
#define V C
#define I(key_t, ret_t, name, cns) ret_t name(key_t) cns;
#define D(key_t, ret_t, name, cns) ret_t name(key_t) cns = delete;
/*
* Membership.
*/
FOR_EACH_KEY_TYPE(bool, exists, const)
/*
* Remove an element.
*/
FOR_EACH_KEY_TYPE(void, remove, )
#undef D
#undef I
#undef V
#undef C
#define C(key_t, name, value_t) \
void name(key_t k, value_t v, bool isKey = false);
#define V C
#define I(key_t, name, value_t) void name(key_t k, value_t v);
#define D(key_t, name, value_t) void name(key_t k, value_t v) = delete;
/*
* Set an element to `v', unboxing `v' if it's boxed.
*/
FOR_EACH_KEY_TYPE(set, TypedValue)
/*
* Set an element to `v', preserving refs unless they are singly-referenced.
*/
FOR_EACH_KEY_TYPE(setWithRef, TypedValue)
/*
* Set an element to a reference to `v', boxing it if it's unboxed.
*/
FOR_EACH_KEY_TYPE(setRef, Variant&)
/*
* Add an element.
*
* Like set(), but with the precondition that the key does not already exist
* in the array.
*/
FOR_EACH_KEY_TYPE(add, TypedValue)
#undef D
#undef I
#undef V
#undef C
#define C(key_t, name, value_t)
#define V(key_t, name, value_t) \
void name(key_t k, value_t v, bool isKey = false);
#define I(key_t, name, value_t) void name(key_t k, value_t v);
#define D(key_t, name, value_t) void name(key_t k, value_t v) = delete;
/*
* Variant overloads.
*/
FOR_EACH_KEY_TYPE(set, const Variant&)
FOR_EACH_KEY_TYPE(setWithRef, const Variant&)
FOR_EACH_KEY_TYPE(add, const Variant&)
#undef D
#undef I
#undef V
#undef C
/*
* Append or prepend an element, with semantics like set{,WithRef}().
*/
void append(TypedValue v);
void append(const Variant& v);
void appendWithRef(TypedValue v);
void appendWithRef(const Variant& v);
void appendRef(Variant& v);
void prepend(TypedValue v);
void prepend(const Variant& v);
/*
* Remove all elements.
*/
void clear() { operator=(Create()); }
/*
* Stack/queue-like functions.
*/
Variant pop();
Variant dequeue();
#undef FOR_EACH_KEY_TYPE
/////////////////////////////////////////////////////////////////////////////
private:
Array(ArrayData* ad, NoIncRef) : m_arr(ad, NoIncRef{}) {}
Array& plusImpl(ArrayData* data);
Array& mergeImpl(ArrayData* data);
Array diffImpl(const Array& array, bool by_key, bool by_value, bool match,
PFUNC_CMP key_cmp_function, const void* key_data,
PFUNC_CMP value_cmp_function, const void* value_data) const;
template<typename T> member_rval rvalAtImpl(const T& key, Flags) const;
template<typename T> member_lval lvalAtImpl(const T& key, Flags);
template<typename T> member_lval lvalAtRefImpl(const T& key, Flags);
template<typename T> bool existsImpl(const T& key) const;
template<typename T> void removeImpl(const T& key);
template<typename T> void setImpl(const T& key, TypedValue v);
template<typename T> void setWithRefImpl(const T& key, TypedValue v);
template<typename T> void setRefImpl(const T& key, Variant& v);
template<typename T> void addImpl(const T& key, TypedValue v);
static void compileTimeAssertions();
/////////////////////////////////////////////////////////////////////////////
private:
Ptr m_arr;
};
///////////////////////////////////////////////////////////////////////////////
struct ArrNR {
explicit ArrNR(ArrayData* data = nullptr) { m_px = data; }
ArrNR(const ArrNR& a) { m_px = a.m_px; }
~ArrNR() {
if (debug) {
m_px = reinterpret_cast<ArrayData*>(0xdeadbeeffaceb004);
}
}
ArrayData* get() const { return m_px; }
operator const Array&() const { return asArray(); }
Array& asArray() {
return *reinterpret_cast<Array*>(this); // XXX
}
const Array& asArray() const {
return const_cast<ArrNR*>(this)->asArray();
}
private:
static void compileTimeAssertions();
private:
ArrayData* m_px;
};
///////////////////////////////////////////////////////////////////////////////
/*
* An Array wrapper that doesn't run a destructor unless you
* explicitly ask it to.
*
* This is used for the dynPropTable in ExecutionContext, so that at
* sweep time we don't run these destructors.
*/
struct ArrayNoDtor {
/* implicit */ ArrayNoDtor(ArrayData* table) { new (&m_arr) Array(table); }
ArrayNoDtor() { new (&m_arr) Array(); }
ArrayNoDtor(ArrayNoDtor&& o) noexcept {
new (&m_arr) Array(std::move(o.m_arr));
}
~ArrayNoDtor() {}
Array& arr() { return m_arr; }
const Array& arr() const { return m_arr; }
void destroy() { m_arr.~Array(); }
private:
union { Array m_arr; };
};
///////////////////////////////////////////////////////////////////////////////
ALWAYS_INLINE Array empty_array() {
return Array::attach(staticEmptyArray());
}
ALWAYS_INLINE Array empty_vec_array() {
return Array::attach(staticEmptyVecArray());
}
///////////////////////////////////////////////////////////////////////////////
/*
* Type-pun a referenced ArrayData* as an Array&.
*
* This lets us take advantage of Array's CoW and escalation machinery to
* possibly mutate `ad', without the overhead or behavioral change of
* refcounting.
*
* asArrRef() unconditionally removes Persistent bits from the referenced type.
*/
ALWAYS_INLINE Array& asArrRef(member_lval lval) {
assertx(isArrayLikeType(lval.type()));
lval.type() = lval.val().parr->toDataType();
return *reinterpret_cast<Array*>(&lval.val().parr);
}
///////////////////////////////////////////////////////////////////////////////
}
#endif