longjump is special: it can return twice and the effect is observable even for non-escaping variables: so values that must be preserved across function calls need to be forced to memory by declaring them volatile.
GCC specifically marks longjmp as returns_twice, which as far as I can tell prevents the surrounding function from being inlinable and additionally treats all local variables as volatile at the point of the longjmp call (forcing them to memory even if not escaping), but that's a GCC extension.
I can see how failure to prove that a variable hasn't escaped a certain scope must prevent compiler reordering when a function of unknown implementation is called -- but not how that failure should require emitting memory barrier instructions.
However I realize that setjmp()/longjmp() isn't about threading either. What those functions do is quite weird.
It's true that the C specification could have said that the setjmp() function is "special". Then the only way to implement it is to spill most local variables to the stack before the call. I suppose the C authors didn't want to introduce this special case (is there any other function that the C compiler is required to treat specially?)
However setjmp()/longjmp() are different beasts entirely, and the problem here isn't related to multi-threading and thus not related to hardware memory ordering.
> However setjmp()/longjmp() are different beasts entirely
Yes, exactly, they are not "equivalent" to pthread_mutex_lock() at all, which is what you suggested in the beginning. A call to pthread_mutex_lock() is a regular function call as far as the compiler is concerned.
I'll be explicit: on POSIX systems that implement the POSIX threads extension there is a header file called pthread.h that declares a regular function called pthread_mutex_lock() and that function can be called as a regular function by an ISO C compliant compiler.
(POSIX also allows defining macros that can achieve the same effect, possibly more efficiently, but pthread_mutex_lock() et al. have to exist also as regular function definitions.)
So the point remains: pthread_mutex_lock() works not because the C compiler treats it specially. That makes sense since the C standard doesn't even mention it. Unlike setjmp() it's not part of the C standard, and it doesn't need to be, because none of its behavior requires compiler support, beyond what is already required by the platform ABI.
Here's a better analogy. The equivalent just works if we use C++ exception handling instead of setjmp. You can change variables after a try, and those values will be reliably observed in the catch.
setjmp and longjmp are a module that you can write in a small amount of assembly language, without changing anything in the compiler to support them. (Provided it has a bona fide volatile.)
Exception handling is a fairly complex feature requiring supporting in the compiler, with various strategies that have different performance trade-offs.
That's a very nice comparison.