After b <- a, every later use of b (until b is redefined) can read a directly. On its own it changes nothing; its job is to expose the copy b as dead so dead-code elimination can delete it, and to line up operands so value-numbering sees more equalities. The small pass that makes the big ones fire.
Given a chain of copies and one real use, rewrite each use to its ultimate source and mark the now-unused copies dead. Watch c = b + 1 become c = a + 1 and b strand: live demo
“Copy propagation removes the copy.” — not by itself. It only rewrites the uses; the copy statement is removed later by dead-code elimination, once nothing reads it. Two passes, one enabling the other. cited
On SSA it is almost free: a copy b = a means every use of b is literally the use of a’s single definition — substitute and the copy is unreferenced. Allen & Cocke, 1971
That the rewrite preserves the value — c via b equals c via a — holds on the canonical compiler: