AliasClass AliasClass::operator|(AliasClass o) const { if (auto const c = precise_union(o)) return *c; auto const unioned = static_cast<rep>(m_bits | o.m_bits); // If they have the same stag, try to merge them with unionData. auto stag1 = m_stag; auto stag2 = o.m_stag; if (stag1 == stag2) return unionData(unioned, *this, o); // If one of the alias classes have a non-None stag, we can only keep it if // the other doesn't have the corresponding bit set. if (stag1 != STag::None && (o.m_bits & stagBit(stag1))) stag1 = STag::None; if (stag2 != STag::None && (m_bits & stagBit(stag2))) stag2 = STag::None; auto ret = AliasClass{unioned}; if (stag1 == stag2) return ret; // both None. // Note: union operations are guaranteed to be commutative, so if there are // two non-None stags, we have to consistently choose between them. For now // we keep the one with a smaller `rep' value, instead of discarding both. const AliasClass* chosen = &o; auto stag = stag2; if (stag1 != STag::None) { if (stag2 == STag::None || stagBit(stag1) < stagBit(stag2)) { chosen = this; stag = stag1; } } switch (stag) { case STag::None: break; case STag::Frame: new (&ret.m_frame) AFrame(chosen->m_frame); break; case STag::Prop: new (&ret.m_prop) AProp(chosen->m_prop); break; case STag::ElemI: new (&ret.m_elemI) AElemI(chosen->m_elemI); break; case STag::ElemS: new (&ret.m_elemS) AElemS(chosen->m_elemS); break; case STag::Stack: new (&ret.m_stack) AStack(chosen->m_stack); break; case STag::MIState: new (&ret.m_mis) AMIState(chosen->m_mis); break; case STag::Ref: new (&ret.m_ref) ARef(chosen->m_ref); break; } ret.m_stag = stag; return ret; }
TEST(AliasClass, IterUnion) { IRUnit unit{test_context}; auto const marker = BCMarker::Dummy(); auto const FP = unit.gen(DefFP, marker)->dst(); { AliasClass const iterP0 = AIterPos { FP, 0 }; AliasClass const iterP1 = AIterPos { FP, 1 }; auto const u1 = iterP0 | iterP1; EXPECT_EQ(u1, AIterPosAny); EXPECT_TRUE(iterP0 <= AIterPosAny); EXPECT_FALSE(iterP0 <= AIterBaseAny); } { AliasClass const iterP0 = AIterPos { FP, 0 }; AliasClass const iterB0 = AIterBase { FP, 0 }; AliasClass const iterP1 = AIterPos { FP, 1 }; auto const u1 = iterP0 | iterB0; EXPECT_TRUE(iterP0 <= u1); EXPECT_TRUE(iterB0 <= u1); EXPECT_FALSE(u1 <= AIterPosAny); EXPECT_FALSE(u1 <= AIterBaseAny); EXPECT_TRUE(u1 <= (AIterPosAny | AIterBaseAny)); EXPECT_FALSE(iterP1 <= u1); EXPECT_FALSE(iterP1 <= iterP0); EXPECT_FALSE(iterP1 <= iterB0); EXPECT_TRUE(!!u1.iterPos()); EXPECT_TRUE(!!u1.iterBase()); EXPECT_TRUE(!u1.is_iterPos()); EXPECT_TRUE(!u1.is_iterBase()); } { AliasClass const local = AFrame { FP, 0 }; AliasClass const iter = AIterPos { FP, 0 }; auto const u1 = local | iter; EXPECT_TRUE(local <= u1); EXPECT_TRUE(iter <= u1); EXPECT_FALSE(!!u1.is_iterPos()); EXPECT_FALSE(!!u1.is_frame()); EXPECT_TRUE(!!u1.frame()); // locals are preferred in unions to iters EXPECT_FALSE(!!u1.iterPos()); } { AliasClass const iterP0 = AIterPos { FP, 0 }; AliasClass const iterB0 = AIterBase { FP, 0 }; AliasClass const iterP1 = AIterPos { FP, 1 }; AliasClass const iterB1 = AIterBase { FP, 1 }; EXPECT_FALSE(iterP0.maybe(iterP1)); EXPECT_FALSE(iterB0.maybe(iterB1)); auto const u1 = iterP0 | iterB0; auto const u2 = iterP1 | iterB1; EXPECT_FALSE(u1 == u2); EXPECT_FALSE(u1.maybe(u2)); EXPECT_FALSE(u1 <= u2); EXPECT_FALSE(u2 <= u1); EXPECT_TRUE(iterB1 <= u2); EXPECT_TRUE(iterP1 <= u2); EXPECT_FALSE(iterP0 <= u2); EXPECT_FALSE(iterB0 <= u2); auto const u3 = u1 | iterP1; EXPECT_FALSE(!!u3.iterPos()); EXPECT_FALSE(!!u3.iterBase()); EXPECT_TRUE(iterP1 <= u3); EXPECT_TRUE(iterP0 <= u3); EXPECT_TRUE(iterB0 <= u3); EXPECT_TRUE(u1 <= u3); EXPECT_TRUE(u2.maybe(u3)); // u2 <= u3 isn't 'really' true, but operator| is conservative and makes u3 // too big for that right now. EXPECT_TRUE(!u1.precise_union(iterP1)); } }