How would you do that with dynamically linked code, inspect functions you're calling at runtime before laying out your arguments?
> perhaps even encoded into the function name itself
That would mean name mangling in C and assembly.
> Coming from an Asm background, where there basically is no one "calling convention"
Right, because you can lay out memory however you want since you're at the assembly level. Higher-level code (C up) can't do that, so instead you've got standard calling conventions for inter-library call (inside a compilation unit, the compiler is free to use alternate calling conventions since it has complete control over both sides of the callsite, that's also how it can inline calls entirely).
> programmers would document which registers (almost always registers, rarely the stack --- and that can make for some great efficiency gains)
Some standard CC (though not the old CDECL) also use registers, so far as they can, depending on the arch. The SystemV AMD64 ABI uses 6 x86_64 registers for integer/pointer arguments and 8 SSE registers for FP arguments, with the rest on the stack.
(And realistically, for all but the most trivial functions, having one convention is probably a highly reasonable default. Trivial functions should probably be made available to the compiler for inlining anyway. Note also that GCC allows you to override the number of arguments passed on the stack via an annotation on 32-bit x86, if you insist.)
By using information kept with the function, or perhaps even encoded into the function name itself (as already happens when distinguishing between different calling conventions, or in the case of C++ name mangling)?
Coming from an Asm background, where there basically is no one "calling convention", and programmers would document which registers (almost always registers, rarely the stack --- and that can make for some great efficiency gains) are for what, I've always wondered why that idea didn't seem to go far.