You want to keep it simple for the application developer.
The best IMHO is "intrinsics". Usually I don't really want to use assembler, what I want is to use a feature of the processor that isn't available from a high level language. Intrinsics are used very similar to ordinary functions. The compiler knows what these functions do and how to translate them to machine code, just as it knows how to translate + - etc. into machine code. If it generates code for different machines, it knows how to do this, so I can use the same intrinsic on PowerPC, x86, or ARM without any changes to my own code.
For example processors tend to have an instruction that counts the number of bits that are set in a 64 bit integer. Many compilers have an intrinsic "popcount" which returns the number of bits that are set. On any machine. Integrated into the optimisation process. And the compiler can translate popcount (100) -> 3 because 100 = 0x40 + 0x20 + 0x04. It can remove the instructions from loops, can move it around to the most efficient place and so on. And of course it's easy to use, and my code stays readable.
And it's very easy to learn. Every time I use a different method, I'll have to dig out some manual or some sample code. Obviously you are restricted to things that the compiler's authors foresaw. The Rust example for multiplying by six using shift + add cannot be done with in intrinsic. (But I have seen intrinsics for rotate instead of shift instructions which are not part of any language).
Now if your code needs to run on a brand new architecture, and an intrinsic was supported on architecture A, B, and C, but not on the brand new X, then if you are lucky the intrinsic function just doesn't exist. Which means that for your brand new architecture you can write an ordinary function that does what the intrinsic does, just likely a lot slower. Take popcount() as an example: It's not hard to implement by hand, it's just not very efficient. Probably 20-30 instructions for 64 bit. And using it may be sub-optimal, but at least it works. Or there may be an instruction "approximate square root of 1/x" which is a nice building block for a very fast square root. Obviously you can just write a function that calls 1 / sqrt(x). You throw away the advantages, but at least it works. YMMV.