crypt() doesn't just return a hash of the input + salt, it returns a string that has a bunch of information packed into it, including, at a minimum, the algorithm type, salt and hashed value. The exact format of the string and the information it contains varies from algorithm to algorithm. For blowfish, $hashed in my example would contain four things: an identifier for the blowfish algorithm, the number of rounds run to generate the hash, the salt and finally the actual hash itself.
On a similar note, the $salt parameter of crypt() isn't actually just the salt. It needs to be a specially formatted string that contains, at a minimum, the algorithm type and salt. Again, the exact format depends on the algorithm. The fact that the $salt parameter and return value of crypt are compatible formats is the reason my first example works.
Another example:
echo crypt('abc123', '$2a$04$saltsaltsaltsaltsaltxx');
The salt in this example is the base64 value of the string "saltsaltsaltsaltsaltxx$$"
This gives you the output:
$2a$04$saltsaltsaltsaltsaltxuK2.MS4sJd6ZjnuS0fp2eenjndo.g4hS
You can see how the salt is embedded directly in the output, and how the salt parameter that I passed to crypt() is in the same format as the value that crypt() returned back to me. In both the $salt parameter and the return value of crypt():
$2a$ tells crypt that this is blowfish
04$ tells crypt how many rounds to use
saltsaltsaltsaltsaltxx / saltsaltsaltsaltsaltxu is the salt value (more on why this differs in a moment)
uK2.MS4sJd6ZjnuS0fp2eenjndo.g4hS is the hashed password
One last point of potential confusion is the fact that the final "x" from the original salt appears to be missing and I've listed the "u" that replaced it as belonging to both the salt and the hash in the returned string. This is because crypt() + blowfish uses a 16 byte (128 bit) hash, but saltsaltsaltsaltsaltxx is 132 bits. The final four bits of the last 'x' are truncated, not returned by crypt() and thus not present in the returned version of the hash. For this reason, using "saltsaltsaltsaltsaltxy" as your salt will give you exactly the same output as using "saltsaltsaltsaltsaltxx", but using "saltsaltsaltsaltsaltxA" will give you a different value.
It is possible to extract the first 16 bytes of the original salt from the value returned by crypt(), but this is not something you'll probably ever need to do unless you happen to be writing your own implementation of crypt.