The CPU doesn't care, do whatever is convenient and maintainable. The only judge of "correctness" is you, if you're not trying to link to any code generated by a C compiler.
But yes, register args are usually a good idea, with call-clobbered AX, CX, DX. Letting ES be call-clobbered might be convenient to avoid having functions save/restore it, if you're willing to set it before every rep
-string function.
Passing args in registers that line up with where BIOS int
calls want them can maybe save some instructions in wrapper code.
You can even use a custom calling convention on a per-function basis, but that's harder to remember / document. Useful for local helper functions that are only called from one function (but multiple places in that function), or from a couple similar functions in one file. In comments, document which registers for input, output, and clobbered (used as scratch space without save/restore).
Having a couple different calling conventions for different kinds of functions is the middle ground between 1 fixed convention vs. a different one for every function.
Returning boolean conditions in FLAGS is convenient for asm, especially if you expect your caller to branch on it. Or for a function like memcmp
, ending with cmp al, dl
or whatever lets your caller branch on equality, or on greater / less, whichever FLAGS it wants to read. All of this without the cost of actually generating a + / 0 / - return value like the C function.
An answer on CodeGolf.SE Tips for golfing in x86/x64 machine code goes into more details about what you might do if you're going all out for small code without caring at all about maintainability or consistency between functions.
If you want to fit more code into a 512-byte first-stage bootloader, or into fewer extra sectors, you can often save some bytes without hurting readability.
Fewer instructions is generally easier to read. (That's not always the same thing as smaller machine-code size, though.)