https://gcc.gnu.org/bugzilla/show_bug.cgi?id=44462
--- Comment #5 from Richard Biener <rguenth at gcc dot gnu.org> --- So we now (GCC 8+ at least) get <bb 2> : _1 = i_am_pure (5); a_8 = _1 * 2; i_am_pure (8); return a_8; after early DCE. This is because we now do FRE before the first DCE which 4.5 didn't have (4.8 has that already). So fixed for the testcase, not exactly in general though. __attribute__ ((noinline,noclone)) int i_am_pure (int a) { if (a>10) __builtin_abort(); } int main() { i_am_pure (8); i_am_pure (8); return 0; } is still not "CSE"d. The question is whether that happens in practice? We do actually detect the "redundancy" during propagation but fail to do anything in elimination - also because we don't properly track "availability" here. As a first step one could handle the cases where the call has the same VUSE, but that only helps for pure calls, not const ones. There's also the case of aggregate returns which makes the calls receive vops: struct S { int x; }; __attribute__ ((noinline,noclone)) struct S i_am_pure (int a) { if (a>10) __builtin_abort(); } int main() { struct S x; x = i_am_pure (8); x = i_am_pure (8); return 0; } which we do not optimize at all. int main () { struct S x; <bb 2> [local count: 1073741824]: x = i_am_pure (8); x = i_am_pure (8); x ={v} {CLOBBER}; return 0; we could DSE the LHS of the calls it seems.