loop
is exactly like dec ecx
/ jnz
(except for not modifying the status flags, weirdness with the address-size prefix, and being much slower on most CPUs).
Its branch displacement range is a standard rel8
[-128 .. +127], so there's no requirement that it be used for a backward branch.
Thus you can use it as a conditional without even jumping through any hoops, just use it to jump forward over the code for the recursion base-case (possibly just a ret
):
; first arg in ECX = recursion count
my_func:
loop @keep_descending
;; fall-through path: ecx was 1 before loop, and is now 0
... do something here ...
ret
@keep_descending:
; ecx has been decremented by 1
push ebx ; save a call-preserved reg; use it to save state across the recursive call
... main body of function here ...
call my_func ; clobbers ecx; save it if needed
... more stuff
; If your function way was tail-recursive, you should have just made a loop
lea eax, [edx+ecx] ; return in EAX
pop ebx
ret
It's easy to abuse loop
to use it as a generic if (n != 0)
without modifying n
, if your recursion termination condition is something other than a down-counter.
inc ecx ; 1 byte in 32-bit mode
loop ecx_was_non_zero
;; fall-through path: ecx was zero before inc, and is now zero again
This is 1 byte shorter than test ecx, ecx / jnz
(in 32-bit mode), and thus useful for a code-golf GCD loop, where the only thing that matters is code-size, not performance. (An equivalent trick with CX works in 16-bit mode, but 64-bit mode doesn't have 1-byte inc
.)
When using procedures, you need to ensure that registers and flags are preserved. You need to use PUSHFD/POPFD and PUSHAD/POPAD.
That's a crappy calling convention that imposes a lot of work on every function you call. It's totally normal to allow functions to clobber flags, and eax/ecx/edx, so they can use some registers without saving/restoring.
POPAD is really inconvenient to use, because it overwrites all the registers, including EAX where you wanted to put a return value. So you either have to save/restore EAX around the POPAD, or you have to store your result into the right spot on the stack for POPAD to load it into EAX.
Also, it's possible to leave registers and FLAGS unmodified without using pushad
/popad
, so that statement is false. Unless you take it as a separate requirement, also inconveniencing you the way the loop
requirement does.
MASM is it possible to create a recursive function without conditional statement?
That's a different question (because loop
is a conditional branch), but the answer still is yes. Your options include self-modifying code. (See The Story of Mel for an example.) In your case, you might have the start of the function overwrite the call
instruction to a nop
or something.
You can do that branchlessly with a cmov
to get a pointer to either that instruction or some harmless location into a register. Or maybe you'd call cmov
a conditional instruction. It's not a branch, but it has conditional in the name. Anyway, self-modifying-code is horrible for performance on modern CPUs, so you shouldn't use that either.
But if you're just coming up with silly computer tricks like abusing loop
, then you should also consider self-modifying code, or a lookup table of pointers, or other ways of affecting flow control without a standard conditional instruction like jcc
.