x86 assembler language isn't so bad. It's when you get to the machine code that it starts to get really ugly. Instruction encodings, addressing modes, etc are much more complicated than the ones for most RISC CPUs. And there's extra fun built in for backward compatibility purposes -- stuff that only kicks in when the processor is in a certain state.
In 16-bit modes, for example, addressing can seem downright bizarre; there's an addressing mode for [BX+SI]
, but not one for [AX+BX]
. Things like that tend to complicate register usage, since you need to ensure your value's in a register that you can use as you need to.
(Fortunately, 32-bit mode is much saner (though still a bit weird itself at times -- segmentation for example), and 16-bit x86 code is largely irrelevant anymore outside of boot loaders and some embedded environments.)
There's also the leftovers from the olden days, when Intel was trying to make x86 the ultimate processor. Instructions a couple of bytes long that performed tasks that no one actually does any more, cause they were frankly too freaking slow or complicated. The ENTER and LOOP instructions, for two examples -- note the C stack frame code is like "push ebp; mov ebp, esp" and not "enter" for most compilers.