2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include <gtest/gtest.h>
18 #include <boost/range/join.hpp>
21 #include "hphp/runtime/test/test-context.h"
23 #include "hphp/runtime/vm/jit/alias-class.h"
24 #include "hphp/runtime/vm/jit/ir-unit.h"
25 #include "hphp/runtime/vm/jit/memory-effects.h"
26 #include "hphp/runtime/vm/jit/ssa-tmp.h"
32 //////////////////////////////////////////////////////////////////////
34 std::vector
<AliasClass
> generic_classes() {
51 std::vector
<AliasClass
> specialized_classes(IRUnit
& unit
) {
52 auto const bcctx
= BCContext
{ BCMarker::Dummy(), 0 };
54 // Specialized test cases need some SSATmp*'s and similar things, so let's
55 // make some instructions.
56 auto const mainFP
= unit
.gen(DefFP
, bcctx
, DefFPData
{ std::nullopt
})->dst();
64 // Some stack locations.
65 AStack::at(IRSPRelOffset
{ -1 }),
66 AStack::range(IRSPRelOffset
{ -4 }, IRSPRelOffset
{ -1 }),
67 AStack::below(IRSPRelOffset
{ -11 }),
68 AStack::range(IRSPRelOffset
{ -15 }, IRSPRelOffset
{ -11 }),
69 AStack::range(IRSPRelOffset
{ -13 }, IRSPRelOffset
{ -10 }),
70 AStack::range(IRSPRelOffset
{ -61 }, IRSPRelOffset
{ -51 }),
82 //////////////////////////////////////////////////////////////////////
84 TEST(AliasClass
, AliasIdSet
) {
85 constexpr auto Max
= AliasIdSet::Max
;
86 constexpr auto BitsetMax
= AliasIdSet::BitsetMax
;
88 EXPECT_TRUE(BitsetMax
< 64);
90 AliasIdSet big
= BitsetMax
+ 100;
91 EXPECT_EQ(big
.size(), 1);
92 EXPECT_TRUE(big
.isBigInteger());
93 EXPECT_FALSE(big
.empty());
96 EXPECT_EQ(big
.size(), 1);
97 EXPECT_TRUE(big
.isBigInteger());
98 EXPECT_FALSE(big
.empty());
100 big
.set(BitsetMax
+ 100);
101 EXPECT_EQ(big
.size(), 1);
102 EXPECT_TRUE(big
.isBigInteger());
103 EXPECT_FALSE(big
.empty());
104 EXPECT_TRUE(big
.hasSingleValue());
106 big
.unset(BitsetMax
+ 100);
107 EXPECT_EQ(big
.size(), 0);
108 EXPECT_TRUE(big
.isBitset());
109 EXPECT_TRUE(big
.empty());
110 EXPECT_FALSE(big
.hasSingleValue());
112 AliasIdSet ids
{ 0u, 3u, IdRange
{ 6, 9 }, IdRange
{ 15, 12 }, BitsetMax
};
114 EXPECT_EQ(ids
.size(), 6);
115 EXPECT_TRUE(ids
.isBitset());
116 EXPECT_FALSE(ids
.empty());
117 EXPECT_FALSE(ids
.isBigInteger());
118 EXPECT_FALSE(ids
.hasUpperRange());
120 EXPECT_TRUE(ids
.test(0));
121 EXPECT_TRUE(ids
.test(3));
122 EXPECT_FALSE(ids
.test(5));
123 EXPECT_TRUE(ids
.test(6));
124 EXPECT_TRUE(ids
.test(8));
125 EXPECT_FALSE(ids
.test(9));
126 EXPECT_FALSE(ids
.test(12));
127 EXPECT_FALSE(ids
.test(14));
128 EXPECT_FALSE(ids
.test(15));
129 EXPECT_TRUE(ids
.test(BitsetMax
));
130 EXPECT_FALSE(ids
.test(BitsetMax
+ 1));
131 EXPECT_FALSE(ids
.test(63));
133 EXPECT_TRUE(ids
== (ids
| AliasIdSet
{}));
134 EXPECT_TRUE(ids
== (ids
| 6));
135 EXPECT_TRUE(ids
== (ids
| BitsetMax
));
136 EXPECT_TRUE((1000 | ids
).test(1000));
138 AliasIdSet unbounded
= IdRange
{ 4 };
140 EXPECT_EQ(unbounded
.size(), Max
);
141 EXPECT_TRUE(unbounded
.isBitset());
142 EXPECT_FALSE(unbounded
.empty());
143 EXPECT_FALSE(unbounded
.isBigInteger());
144 EXPECT_TRUE(unbounded
.hasUpperRange());
146 EXPECT_TRUE(unbounded
.test(4));
147 EXPECT_TRUE(unbounded
.test(12));
148 EXPECT_TRUE(unbounded
.test(61));
149 EXPECT_TRUE(unbounded
.test(62));
150 EXPECT_TRUE(unbounded
.test(BitsetMax
));
151 EXPECT_TRUE(unbounded
.test(BitsetMax
+ 1));
152 EXPECT_TRUE(unbounded
.test(64));
153 EXPECT_TRUE(unbounded
.test(100));
155 EXPECT_TRUE(ids
.maybe(IdRange
{ 5, 7 }));
156 EXPECT_TRUE(ids
.maybe(unbounded
));
158 EXPECT_TRUE((ids
| unbounded
).hasUpperRange());
161 //////////////////////////////////////////////////////////////////////
163 TEST(AliasClass
, Basic
) {
164 IRUnit unit
{test_context
};
165 auto const specialized
= specialized_classes(unit
);
166 auto const generic
= generic_classes();
167 auto const joined
= boost::join(generic
, specialized
);
169 for (auto cls
: joined
) {
170 // Everything is a subclass of AUnknown and intersects AUnknown.
171 EXPECT_TRUE(cls
.maybe(AUnknown
));
172 EXPECT_TRUE(cls
<= AUnknown
);
174 // Everything contains AEmpty, but AEmpty can't be anything.
175 EXPECT_TRUE(AEmpty
<= cls
);
176 EXPECT_FALSE(AEmpty
.maybe(cls
));
179 // maybe() is a symmetric relation
180 for (auto c1
: joined
) {
181 EXPECT_EQ(c1
.maybe(AUnknown
), AUnknown
.maybe(c1
));
182 EXPECT_EQ(c1
.maybe(AEmpty
), AEmpty
.maybe(c1
));
183 for (auto c2
: joined
) {
184 EXPECT_EQ(c1
.maybe(c2
), c2
.maybe(c1
));
188 // If one class is a subclass of another, and they aren't equal, the other
189 // isn't a subclass of it.
190 for (auto c1
: joined
) {
191 for (auto c2
: joined
) {
194 EXPECT_FALSE(c2
<= c1
);
200 // == implies <=, and <= implies maybe
201 for (auto c1
: joined
) {
202 for (auto c2
: joined
) {
203 EXPECT_TRUE(!(c1
== c2
) || c1
<= c2
);
204 EXPECT_TRUE(!(c1
<= c2
) || c1
.maybe(c2
));
208 // Arguments to operator| are subclasses of the result, and operator| is
210 for (auto c1
: joined
) {
211 for (auto c2
: joined
) {
212 auto const res
= c1
| c2
;
213 EXPECT_EQ(res
, c2
| c1
);
214 EXPECT_TRUE(c1
<= res
);
215 EXPECT_TRUE(c2
<= res
);
220 TEST(AliasClass
, StackBasics
) {
221 IRUnit unit
{test_context
};
223 // Some basic canonicalization and maybe.
225 AliasClass
const stk
= AStack::at(IRSPRelOffset
{ 0 });
227 EXPECT_TRUE(stk
<= AStackAny
);
228 EXPECT_TRUE(stk
!= AStackAny
);
231 EXPECT_TRUE(stk
.maybe(stk
));
232 EXPECT_TRUE(stk
<= stk
);
235 // Stack ranges, with subtype and maybe.
237 AliasClass
const stk1
= AStack::at(IRSPRelOffset
{ -10 });
238 AliasClass
const stk2
= AStack::range(IRSPRelOffset
{ -11 },
239 IRSPRelOffset
{ -9 });
240 EXPECT_TRUE(stk1
<= stk2
);
241 EXPECT_TRUE(stk1
.maybe(stk2
));
243 AliasClass
const stk3
= AStack::range(IRSPRelOffset
{ -14 },
244 IRSPRelOffset
{ -9 });
245 EXPECT_TRUE(stk1
<= stk3
);
246 EXPECT_TRUE(stk1
.maybe(stk3
));
247 AliasClass
const stk4
= AStack::at(IRSPRelOffset
{ -15 });
248 AliasClass
const stk5
= AStack::at(IRSPRelOffset
{ -14 });
249 // stk4's slot is immediately below stk3's range, but stk5 is the last slot
251 EXPECT_FALSE(stk3
.maybe(stk4
));
252 EXPECT_FALSE(stk3
<= stk4
);
253 EXPECT_TRUE(stk5
.maybe(stk3
));
254 EXPECT_TRUE(stk5
<= stk3
);
255 EXPECT_FALSE(stk5
.maybe(stk4
));
256 EXPECT_FALSE(stk5
<= stk4
);
260 TEST(AliasClass
, SpecializedUnions
) {
261 IRUnit unit
{test_context
};
263 AliasClass
const stk
= AStack::range(IRSPRelOffset
{ -12 },
264 IRSPRelOffset
{ -9 });
265 AliasClass
const unrelated_stk
= AStack::at(IRSPRelOffset
{ -14 });
266 AliasClass
const related_stk
= AStack::range(IRSPRelOffset
{ -12 },
267 IRSPRelOffset
{ -10 });
269 auto const stk_and_frame
= stk
| ALocalAny
;
270 EXPECT_TRUE(!stk_and_frame
.is_stack());
271 EXPECT_TRUE(ALocalAny
<= stk_and_frame
);
272 EXPECT_TRUE(stk
<= stk_and_frame
);
273 EXPECT_TRUE(AStackAny
.maybe(stk_and_frame
));
274 EXPECT_TRUE(ALocalAny
.maybe(stk_and_frame
));
275 EXPECT_FALSE(unrelated_stk
<= stk_and_frame
);
276 EXPECT_FALSE(stk_and_frame
.maybe(unrelated_stk
));
278 auto const stk_and_prop
= stk
| APropAny
;
279 EXPECT_TRUE(stk_and_prop
.maybe(stk_and_frame
));
280 EXPECT_TRUE(stk_and_frame
.maybe(stk_and_prop
));
281 EXPECT_FALSE(stk_and_prop
<= stk_and_frame
);
282 EXPECT_FALSE(stk_and_frame
<= stk_and_prop
);
283 EXPECT_TRUE(APropAny
.maybe(stk_and_prop
));
284 EXPECT_TRUE(AStackAny
.maybe(stk_and_prop
));
286 auto const unrelated_stk_and_prop
= unrelated_stk
| APropAny
;
287 EXPECT_FALSE(stk_and_frame
.maybe(unrelated_stk_and_prop
));
288 EXPECT_FALSE(unrelated_stk_and_prop
.maybe(stk_and_frame
));
289 EXPECT_TRUE(unrelated_stk_and_prop
.maybe(stk_and_prop
)); // because of prop
290 EXPECT_FALSE(unrelated_stk_and_prop
<= stk_and_prop
);
291 EXPECT_FALSE(stk_and_prop
<= unrelated_stk_and_prop
);
292 EXPECT_FALSE(unrelated_stk_and_prop
<= stk_and_frame
);
293 EXPECT_FALSE(stk_and_frame
<= unrelated_stk_and_prop
);
295 EXPECT_FALSE(stk_and_prop
<= AHeapAny
);
296 EXPECT_TRUE(stk_and_prop
.maybe(AHeapAny
));
297 EXPECT_FALSE(stk_and_frame
<= AHeapAny
);
298 EXPECT_FALSE(stk_and_frame
.maybe(AHeapAny
));
300 auto const rel_stk_and_frame
= related_stk
| ALocalAny
;
301 EXPECT_TRUE(stk_and_frame
.maybe(rel_stk_and_frame
));
302 EXPECT_TRUE(rel_stk_and_frame
.maybe(stk_and_frame
));
303 EXPECT_TRUE(related_stk
<= stk
);
304 EXPECT_TRUE(rel_stk_and_frame
<= stk_and_frame
);
305 EXPECT_FALSE(stk_and_frame
<= rel_stk_and_frame
);
306 EXPECT_TRUE(rel_stk_and_frame
.maybe(stk_and_prop
));
307 EXPECT_TRUE(stk_and_prop
.maybe(rel_stk_and_frame
));
308 EXPECT_FALSE(rel_stk_and_frame
<= stk_and_prop
);
310 auto const some_mis
= AMIStateTempBase
;
312 auto const some_heap
= AElemIAny
;
313 auto const u1
= some_heap
| some_mis
;
314 auto const u2
= ALocalAny
| u1
;
315 EXPECT_TRUE((AHeapAny
| some_heap
) == AHeapAny
);
316 EXPECT_TRUE(AHeapAny
<= (AHeapAny
| u1
));
317 EXPECT_TRUE(AHeapAny
<= (AHeapAny
| u2
));
320 auto const mis_stk
= some_mis
| stk
;
321 auto const mis_stk_any
= AStackAny
| mis_stk
;
323 EXPECT_EQ(some_mis
, AliasClass
{*mis_stk_any
.mis()});
324 EXPECT_NE(mis_stk_any
, AStackAny
| AMIStateAny
);
326 auto const other_mis
= AMIStateBase
;
328 EXPECT_LE(some_mis
, some_mis
| other_mis
);
329 EXPECT_LE(other_mis
, some_mis
| other_mis
);
331 EXPECT_NE(some_mis
, some_mis
| other_mis
);
332 EXPECT_NE(other_mis
, some_mis
| other_mis
);
335 TEST(AliasClass
, StackUnions
) {
336 IRUnit unit
{test_context
};
339 AliasClass
const stk1
= AStack::at(IRSPRelOffset
{ -3 });
340 AliasClass
const stk2
= AStack::at(IRSPRelOffset
{ -4 });
341 AliasClass
const stk3
= AStack::at(IRSPRelOffset
{ -5 });
342 AliasClass
const stk12
= AStack::range(IRSPRelOffset
{ -4 },
343 IRSPRelOffset
{ -2 });
344 AliasClass
const stk23
= AStack::range(IRSPRelOffset
{ -5 },
345 IRSPRelOffset
{ -3 });
346 AliasClass
const stk13
= AStack::range(IRSPRelOffset
{ -5 },
347 IRSPRelOffset
{ -2 });
348 EXPECT_EQ(stk1
| stk2
, stk12
);
349 EXPECT_EQ(stk2
| stk3
, stk23
);
350 EXPECT_EQ(stk1
| stk3
, stk13
);
353 // Same as above but with some other bits.
355 AliasClass
const stk1
= AHeapAny
| AStack::at(IRSPRelOffset
{ -3 });
356 AliasClass
const stk2
= AHeapAny
| AStack::at(IRSPRelOffset
{ -4 });
357 AliasClass
const stk3
= AHeapAny
| AStack::at(IRSPRelOffset
{ -5 });
358 AliasClass
const stk12
= AHeapAny
| AStack::range(IRSPRelOffset
{ -4 },
359 IRSPRelOffset
{ -2 });
360 AliasClass
const stk23
= AHeapAny
| AStack::range(IRSPRelOffset
{ -5 },
361 IRSPRelOffset
{ -3 });
362 AliasClass
const stk13
= AHeapAny
| AStack::range(IRSPRelOffset
{ -5 },
363 IRSPRelOffset
{ -2 });
364 EXPECT_EQ(stk1
| stk2
, stk12
);
365 EXPECT_EQ(stk2
| stk3
, stk23
);
366 EXPECT_EQ(stk1
| stk3
, stk13
);
370 AliasClass
const stk1
= AStack::at(IRSPRelOffset
{ 0 });
371 AliasClass
const stk2
= AStack::at(IRSPRelOffset
{ -2 });
372 AliasClass
const true_union
= AStack::range(IRSPRelOffset
{ -2 },
373 IRSPRelOffset
{ 1 });
374 EXPECT_NE(stk1
| stk2
, AStackAny
);
375 EXPECT_EQ(stk1
| stk2
, true_union
);
379 AliasClass
const deep_stk1
= AStack::below(IRSPRelOffset
{ -9 });
380 AliasClass
const deep_stk2
= AStack::below(IRSPRelOffset
{ -13 });
381 EXPECT_EQ(deep_stk1
| deep_stk2
, deep_stk1
);
385 TEST(AliasClass
, IterUnion
) {
386 IRUnit unit
{test_context
};
387 auto const bcctx
= BCContext
{ BCMarker::Dummy(), 0 };
388 auto const FP
= unit
.gen(DefFP
, bcctx
, DefFPData
{ std::nullopt
})->dst();
391 AliasClass
const iterP0
= aiter_pos(FP
, 0);
392 AliasClass
const iterP1
= aiter_pos(FP
, 1);
393 auto const u1
= iterP0
| iterP1
;
394 EXPECT_TRUE(u1
<= AIterAny
);
395 EXPECT_TRUE(iterP0
<= u1
);
396 EXPECT_TRUE(iterP1
<= u1
);
400 AliasClass
const iterP0
= aiter_pos(FP
, 0);
401 AliasClass
const iterE0
= aiter_end(FP
, 0);
402 AliasClass
const iterP1
= aiter_pos(FP
, 1);
404 // All the alias classes should be distinct to start.
405 auto const classes
= std::vector
{iterP0
, iterE0
, iterP1
};
406 for (auto const cls1
: classes
) {
407 for (auto const cls2
: classes
) {
408 if (cls1
== cls2
) continue;
409 EXPECT_FALSE(cls1
<= cls2
);
413 auto const u1
= iterP0
| iterE0
;
414 EXPECT_TRUE(iterP0
<= u1
);
415 EXPECT_TRUE(iterE0
<= u1
);
416 EXPECT_FALSE(iterP1
<= u1
);
417 EXPECT_TRUE(u1
<= AIterAny
);
419 EXPECT_TRUE(u1
.iter());
420 EXPECT_TRUE(u1
.is_iter());
422 auto const u2
= iterE0
| iterP1
;
423 EXPECT_FALSE(iterP0
<= u2
);
424 EXPECT_TRUE(iterE0
<= u2
);
425 EXPECT_TRUE(iterP1
<= u2
);
426 EXPECT_TRUE(u2
<= AIterAny
);
428 EXPECT_TRUE(u1
.iter());
429 EXPECT_TRUE(u1
.is_iter());
433 AliasClass
const local
= ALocal
{ FP
, 0 };
434 AliasClass
const iter
= aiter_pos(FP
, 0);
435 auto const u1
= local
| iter
;
436 EXPECT_TRUE(local
<= u1
);
437 EXPECT_TRUE(iter
<= u1
);
438 EXPECT_FALSE(!!u1
.is_iter());
439 EXPECT_FALSE(!!u1
.is_local());
440 EXPECT_TRUE(!!u1
.local()); // locals are preferred in unions to iters
441 EXPECT_FALSE(!!u1
.iter());
445 AliasClass
const iterP0
= aiter_pos(FP
, 0);
446 AliasClass
const iterE0
= aiter_end(FP
, 0);
447 AliasClass
const iterP1
= aiter_pos(FP
, 1);
448 AliasClass
const iterE1
= aiter_end(FP
, 1);
450 EXPECT_FALSE(iterP0
.maybe(iterP1
));
451 EXPECT_FALSE(iterE0
.maybe(iterE1
));
453 auto const u1
= iterP0
| iterE0
;
454 auto const u2
= iterP1
| iterE1
;
455 EXPECT_FALSE(u1
== u2
);
456 EXPECT_FALSE(u1
.maybe(u2
));
457 EXPECT_FALSE(u1
<= u2
);
458 EXPECT_FALSE(u2
<= u1
);
460 EXPECT_TRUE(iterE1
<= u2
);
461 EXPECT_TRUE(iterP1
<= u2
);
462 EXPECT_FALSE(iterP0
<= u2
);
463 EXPECT_FALSE(iterE0
<= u2
);
465 auto const u3
= u1
| iterP1
;
466 EXPECT_TRUE(u3
.iter());
467 EXPECT_TRUE(iterP1
<= u3
);
468 EXPECT_TRUE(iterP0
<= u3
);
469 EXPECT_TRUE(iterE0
<= u3
);
470 EXPECT_TRUE(u1
<= u3
);
471 EXPECT_TRUE(u2
.maybe(u3
));
473 // u2 <= u3 isn't 'really' true, but operator| is conservative and makes u3
474 // too big for that right now.
475 EXPECT_TRUE(!u1
.precise_union(iterP1
));
479 TEST(AliasClass
, FrameUnion
) {
480 IRUnit unit
{test_context
};
481 auto const bcctx
= BCContext
{ BCMarker::Dummy(), 0 };
482 auto const dsData
= DefStackData
{ SBInvOffset
{ 0 }, SBInvOffset
{ 0 } };
483 auto const biData
= BeginInliningData
{
484 IRSPRelOffset
{ 0 }, nullptr, 1, SrcKey
{}, IRSPRelOffset
{ 0 },
487 auto const SP
= unit
.gen(DefRegSP
, bcctx
, dsData
)->dst();
488 auto const FP1
= unit
.gen(DefFP
, bcctx
, DefFPData
{ std::nullopt
})->dst();
489 auto const FP2
= unit
.gen(BeginInlining
, bcctx
, biData
, SP
)->dst();
491 AliasClass
const local1
= ALocal
{ FP1
, 0 };
492 AliasClass
const localR
= ALocal
{ FP1
, AliasIdSet::IdRange(0, 2) };
493 AliasClass
const fctx1
= AFContext
{ FP1
};
494 AliasClass
const fctx2
= AFContext
{ FP2
};
496 EXPECT_TRUE(local1
.maybe(localR
));
497 EXPECT_TRUE(local1
<= localR
);
498 EXPECT_FALSE(local1
.maybe(fctx1
));
499 EXPECT_FALSE(localR
.maybe(fctx1
));
500 EXPECT_TRUE(fctx1
.maybe(AFContextAny
));
501 EXPECT_TRUE(fctx1
.maybe(AActRecAny
));
502 EXPECT_TRUE(fctx1
.maybe(AActRec
{FP1
}));
504 auto const u1
= local1
| fctx1
;
505 auto const u2
= localR
| fctx1
;
506 auto const u3
= local1
| fctx2
;
507 auto const u4
= u3
| fctx1
;
508 auto const u5
= u4
| AMIStateAny
;
509 EXPECT_TRUE(u1
<= u2
);
510 EXPECT_TRUE(u1
.maybe(u2
));
511 EXPECT_FALSE(u1
<= localR
);
512 EXPECT_FALSE(u1
.maybe(fctx2
));
513 EXPECT_FALSE(u2
.maybe(fctx2
));
514 EXPECT_TRUE(u3
.local().has_value());
515 EXPECT_FALSE(u3
.fcontext().has_value());
517 EXPECT_TRUE(u5
.local().has_value());
518 EXPECT_EQ(u5
.local()->frameIdx
, local1
.local()->frameIdx
);
519 EXPECT_EQ(u5
.local()->ids
, local1
.local()->ids
);
522 TEST(AliasClass
, Pointees
) {
523 IRUnit unit
{test_context
};
524 auto const bcctx
= BCContext
{ BCMarker::Dummy(), 0 };
525 auto ptr
= unit
.gen(LdMBase
, bcctx
, TLval
, AliasClassData
{ ALocalAny
| APropAny
})->dst();
526 auto const acls
= pointee(ptr
);
527 EXPECT_EQ(ALocalAny
| APropAny
, acls
);
530 //////////////////////////////////////////////////////////////////////