I think it's important to state that ABIs are just conventions, standards that have been decided upon by mostly high-level-language designers, and may not reflect any inherent limitations of the machine itself. In other words, you'll need to know them if you interface with other code written in a HLL, but if you're using Asm, there's no need to stick to this rigidity within the code you write.
For example, the stack alignment restriction only came about because some SSE instructions initially required that their operands be aligned in memory. This is a rather odd restriction considering that x86 was always intelligent about unaligned accesses (they used to be slower, but the penalty has almost disappeared with the latest models), and later revisions of SSE added instructions for unaligned access. But this doesn't affect any other instructions - in particular, call/return don't care, so in your own code you don't need to align the stack every time - it's only when you call into some other code that requires/expects such a condition.
Ditto for calling conventions; they are defined just so you can interface with other code, and not due to any inherent aspect of the instruction set. In fact although there are explicit call/return instructions, nothing requires that you use them, nor does the concept of a "function" even exist at this level. This means you can write horribly convoluted "sphagetti code", but also do what can't be done easily in a higher-level language with "unconventional" control flow, like coroutines (https://news.ycombinator.com/item?id=7676691 ).
Also, I believe that you should learn Asm not just so you can write the same code a compiler could generate, but so you can see and exploit the full potential of the machine. I feel this point is lost in a lot of material on this subject, maybe because their authors also never realised this, and so contribute to the belief that using Asm is extremely difficult and tedious with very little gain. IMHO it's only when you "break out" of the restricted way of thinking that HLLs impose, and see the machine for what it really is, that you can truly see the cost-benefit tradeoff and what it means for code to be efficient. Look at the demoscene 4k/64k productions for some inspiration. :-)
An opportunity to mention that Go uses the Plan9 ABI which is different to GCC C style ABI. Plan9 did away with previous ABI conventions to make it easier to port code. Portability was built into Plan9 from the outset, rather than "ok, it works for x86, how shall we #ifdef other CPUs into our code"
Go doesn't use the Plan 9 calling convention, and there's not a single Plan 9 calling convention anyway. Go arguments are passed on the stack, and results are on the stack too. The Plan 9 C compiler used by the Go toolchain passes arguments on the stack, but the return value is in a register. On Plan 9, the first argument is usually (but not always) in a register. All these are different.
Go inherited and extended the Plan 9 toolchain, but the calling convention was changed. The main reason Go returns on the stack is to support multiple return values.
I've done a bit of everything, from web development to embedded systems and electronics; currently mostly the latter. Some reverse-engineering included.
That sounds familiar, though I'm more of the former. Not sure I'd enjoy the lower level stuff day to day, but I do enjoy knowing the full stack (all the way down to the transistors, digital system design, and physics) because it allows you to converse with a wide range of people with a diverse range of backgrounds.
Yeah when I had finally peeled back the layers all the way to asm I had this moment of enlightenment. Personally I think the time it took me to peel back those layers was wasted and I wish I had started with asm rather than a HLL. I think it would be much better for students to move on to HLLs once they see the need for automating certain things. Knuth was right to use a synthetic assembly language rather than a HLL for the The Art of Computer Programming.
Knuth's "Structured Programming with Go To Statements" is also a good read and explicitly points out how high-level languages make certain ways of expressing algorithms hard or impossible.
For example, the stack alignment restriction only came about because some SSE instructions initially required that their operands be aligned in memory. This is a rather odd restriction considering that x86 was always intelligent about unaligned accesses (they used to be slower, but the penalty has almost disappeared with the latest models), and later revisions of SSE added instructions for unaligned access. But this doesn't affect any other instructions - in particular, call/return don't care, so in your own code you don't need to align the stack every time - it's only when you call into some other code that requires/expects such a condition.
Ditto for calling conventions; they are defined just so you can interface with other code, and not due to any inherent aspect of the instruction set. In fact although there are explicit call/return instructions, nothing requires that you use them, nor does the concept of a "function" even exist at this level. This means you can write horribly convoluted "sphagetti code", but also do what can't be done easily in a higher-level language with "unconventional" control flow, like coroutines (https://news.ycombinator.com/item?id=7676691 ).
Also, I believe that you should learn Asm not just so you can write the same code a compiler could generate, but so you can see and exploit the full potential of the machine. I feel this point is lost in a lot of material on this subject, maybe because their authors also never realised this, and so contribute to the belief that using Asm is extremely difficult and tedious with very little gain. IMHO it's only when you "break out" of the restricted way of thinking that HLLs impose, and see the machine for what it really is, that you can truly see the cost-benefit tradeoff and what it means for code to be efficient. Look at the demoscene 4k/64k productions for some inspiration. :-)