https://gcc.gnu.org/bugzilla/show_bug.cgi?id=120980
Richard Biener <rguenth at gcc dot gnu.org> changed: What |Removed |Added ---------------------------------------------------------------------------- Keywords| |wrong-code Ever confirmed|0 |1 Last reconfirmed| |2025-07-15 Status|UNCONFIRMED |NEW --- Comment #13 from Richard Biener <rguenth at gcc dot gnu.org> --- (In reply to Krister Walfridsson from comment #12) > > For now, I think a workable solution is to set a limit on how far > > out-of-bounds an access may be, and to allow fully out-of-bounds loads > > within that range (as long as they are on the same page as the last byte of > > the object). And maybe only when loading using a vector type? [...] > > This did not work. > > I started by enabling this only for vector types, but that failed because > the store-merging pass transforms element-wise copying of bytes like: > > _101 = BIT_FIELD_REF <MEM <vector(8) char> [(char *)in_12], 8, 56>; > MEM[(char *)out_14 + 7B] = _101; > _98 = BIT_FIELD_REF <MEM <vector(8) char> [(char *)in_12], 8, 48>; > MEM[(char *)out_14 + 6B] = _98; > ... > _61 = BIT_FIELD_REF <MEM <vector(8) char> [(char *)in_12], 8, 0>; > *out_14 = _61; > > into a single copy using unsigned long: > > _60 = MEM <unsigned long> [(char *)in_12]; so it's the read side that is now no longer a vector access. I suppose store-merging could have preserved the vector(8) char access and instead add a VIEW_CONVERT_EXPR to unsigned long for the store. But this would be a code-gen change "for the verifier" (or rather for a murky defined GIMPLE IL semantic). > MEM <unsigned long> [(char *)out_14] = _60; > > So we must handle out-of-bounds accesses for all types, not just vectors. > But doing that gives me many new failures due to aliasing issues. > > A simple example of the problem can be seen by compiling the following > function for x86_64 with -O3 -fno-strict-aliasing: > > char c; > int foo(int *p) > { > c = 0; > int retval = *p; > c = 1; > return retval; > } > > The problem is that the dead store elimination pass removes c=0, but the > store is not dead if out-of-bounds loads are allowed, because the function > could be called as foo(&c). DSE (or rather the alias oracles) reasoning is that p cannot possibly point to 'c' because that doesn't fit an int. So yeah ... This would in theory point to a possible miscompilation scenario. I believe the alias oracle would happily derive the same conclusion when the 'int' would instead be vector(4) char (one more reason to not define IL correctness differently for vector vs. scalar types). The other case where we use alignment for making non-trapping out-of-bound accesses apart from early break vectorization is vectorization of loads with gaps. But IIRC we make sure to never have fully out-of-bound vector accesses from that (more for optimality than correctness though). But then this would not save us here I think. The following shows this considering q == &x short x[7] __attribute__((aligned(16))); unsigned foo (unsigned short * __restrict p, unsigned short *q) { unsigned res = 0; q = __builtin_assume_aligned (q, 16); x[0] = 1; #pragma GCC unroll 0 for (int i = 0; i < 2; ++i) { p[4*i] = ((unsigned short *)q)[4*i]; p[4*i+1] = ((unsigned short *)q)[4*i + 1]; p[4*i+2] = ((unsigned short *)q)[4*i + 2]; p[4*i+3] = ((unsigned short *)q)[4*i + 2]; } x[0] = 0; return res; }