The last two operands to a gc.relocate represent indices into the associated gc.statepoint's gc bundle list. (Effectively, gc.relocates are projections from the gc.statepoints multiple return values.)
We can use this to recognize when two gc.relocates are equivalent (and can be CSEd), even when the indices are non-equal. This is particular useful when considering a chain of multiple statepoints as it lets us eliminate all duplicate gc.relocates in a single pass.
The meat of the change isn't actually in GVN. For some reason, we had been marking gc.relocates as reading memory. This change undoes that. I believe this to be a legacy of very early implementation conservatism, but a skeptical view is appreciated. (The EarlyCSE change is simply moving the special casing from readonly to readnone handling.)