Alex Coplan <alex.cop...@arm.com> writes: > Hi, > > As the PR shows, pair-fusion was tricking memory_modified_in_insn_p into > returning false when a common base register (in this case, x1) was > modified between the mem and the store insn. This lead to wrong code as > the accesses really did alias. > > To avoid this sort of problem, this patch avoids invoking RTL alias > analysis altogether (and assume an alias conflict) if the two insns to > be compared share a common address register R, and the insns see different > definitions of R (i.e. it was modified in between). > > Bootstrapped/regtested on aarch64-linux-gnu (all languages, both regular > bootstrap and LTO+PGO bootstrap). OK for trunk?
Sorry for the slow review. The patch looks good to me, but... > @@ -2544,11 +2624,37 @@ pair_fusion_bb_info::try_fuse_pair (bool load_p, > unsigned access_size, > && bitmap_bit_p (&m_tombstone_bitmap, insn->uid ()); > }; > > + // Maximum number of distinct regnos we expect to appear in a single > + // MEM (and thus in a candidate insn). > + static constexpr int max_mem_regs = 2; > + auto_vec<access_info *, max_mem_regs> addr_use_vec[2]; > + use_array addr_uses[2]; > + > + // Collect the lists of register uses that occur in the candidate MEMs. > + for (int i = 0; i < 2; i++) > + { > + // N.B. it's safe for us to ignore uses that only occur in notes > + // here (e.g. in a REG_EQUIV expression) since we only pass the > + // MEM down to the alias machinery, so it can't see any insn-level > + // notes. > + for (auto use : insns[i]->uses ()) > + if (use->is_reg () > + && use->includes_address_uses () > + && !use->only_occurs_in_notes ()) > + { > + gcc_checking_assert (addr_use_vec[i].length () < max_mem_regs); > + addr_use_vec[i].quick_push (use); ...if possible, I think it would be better to just use safe_push here, without the assert. There'd then be no need to split max_mem_regs out; it could just be hard-coded in the addr_use_vec declaration. Or does that not work for some reason? I'm getting a sense of deja vu... If it doesn't work, an alternative would be to use access_array_builder. OK for trunk and backports if using safe_push works. Thanks, Richard > + } > + addr_uses[i] = use_array (addr_use_vec[i]); > + } > + > store_walker<false, decltype(tombstone_p)> > - forward_store_walker (mem_defs[0], cand_mems[0], insns[1], tombstone_p); > + forward_store_walker (mem_defs[0], cand_mems[0], addr_uses[0], insns[1], > + tombstone_p); > > store_walker<true, decltype(tombstone_p)> > - backward_store_walker (mem_defs[1], cand_mems[1], insns[0], tombstone_p); > + backward_store_walker (mem_defs[1], cand_mems[1], addr_uses[1], insns[0], > + tombstone_p); > > alias_walker *walkers[4] = {}; > if (mem_defs[0]) > @@ -2562,8 +2668,10 @@ pair_fusion_bb_info::try_fuse_pair (bool load_p, > unsigned access_size, > { > // We want to find any loads hanging off the first store. > mem_defs[0] = memory_access (insns[0]->defs ()); > - load_walker<false> forward_load_walker (mem_defs[0], insns[0], > insns[1]); > - load_walker<true> backward_load_walker (mem_defs[1], insns[1], > insns[0]); > + load_walker<false> forward_load_walker (mem_defs[0], insns[0], > + addr_uses[0], insns[1]); > + load_walker<true> backward_load_walker (mem_defs[1], insns[1], > + addr_uses[1], insns[0]); > walkers[2] = &forward_load_walker; > walkers[3] = &backward_load_walker; > m_pass->do_alias_analysis (alias_hazards, walkers, load_p); > diff --git a/gcc/testsuite/g++.dg/torture/pr116783.C > b/gcc/testsuite/g++.dg/torture/pr116783.C > new file mode 100644 > index 00000000000..6d59159459d > --- /dev/null > +++ b/gcc/testsuite/g++.dg/torture/pr116783.C > @@ -0,0 +1,98 @@ > +// { dg-do run } > +// { dg-additional-options "-fstack-protector-strong > -fno-late-combine-instructions" } > +// { dg-require-effective-target fstack_protector } > +// { dg-require-effective-target c++11 } > + > +struct Private { > + char data[24]{}; > + long moved_from : 4; > + Private() : moved_from (0) {} > +}; > + > +struct QVariant { > + __attribute__((noipa)) > + ~QVariant() { > + if (!d.moved_from && d.data[0] != 42) > + __builtin_abort (); > + } > + __attribute__((noipa)) > + QVariant() { > + d.data[0] = 42; > + } > + __attribute__((noipa)) > + QVariant(QVariant &other) : d(other.d) {} > + QVariant(QVariant &&other) : d(other.d) { > + other.d = Private(); > + other.d.moved_from = true; > + } > + QVariant &operator=(QVariant); > + Private d; > +}; > + > +QVariant id (QVariant v) { return v; } > +QVariant &QVariant::operator=(QVariant other) > +{ > + id(other); > + return *this; > +} > + > +template <typename T> struct QList { > + T d; > + struct const_iterator { > + T *ptr; > + T &operator*() { return *ptr; } > + __attribute__((noipa)) > + bool operator!=(const_iterator other) { return ptr != other.ptr; } > + void operator++() { ptr++; } > + }; > + __attribute__((noipa)) > + T at() { return d; } > + const_iterator begin() { return const_iterator { &d }; } > + const_iterator end() { return const_iterator { &d }; } > +}; > +struct QArrayDataPointer { > + int d; > + int *ptr; > + long size; > +}; > + > +QArrayDataPointer null_qadp; > + > +struct QString { > + __attribute__((noipa)) > + QList<QString> split() const { > + return QList<QString> {null_qadp}; > + } > + __attribute__((noipa)) > + friend bool operator==(QString, QString) { return true; } > + > + __attribute__((noipa)) > + QString(QArrayDataPointer dp) : d(dp) {} > + QArrayDataPointer d; > +}; > + > +__attribute__((noipa)) > +QString as_qstr (QVariant *v) > +{ > + return QString (null_qadp); > +} > + > +int *getNode(const QString &tagContent) { > + auto expr = tagContent.split(); > + auto blockName = expr.at(); > + auto loadedBlocksVariant = QVariant (); > + QList<QVariant> blockVariantList; > + for (auto &item : blockVariantList) { > + auto blockNodeName = as_qstr (&item); > + blockNodeName == blockName; > + QString q(null_qadp); > + } > + loadedBlocksVariant = QVariant(); > + return nullptr; > +} > + > +int main(void) > +{ > + QString foo(null_qadp); > + getNode(foo); > +}