There simply isn't another way to access registers from one 'thread' on another thread without using an intrinsic. You need that to calculate finite differences. For a long time, the only option was ddx()/ddy(). Now we also wave intrinsics, which you couldn't implement yourself either.
You need to access the neighboring pixels (fragments) in a quad to compute d_dx and d_dy, but quads are an implementation detail not exposed to the programmer.