0

For an assignment in my assembly programming class, we were given the problem below. I tried to solve it a couple different ways, but I could only think of solutions that use conditional statements. I have not found an answer online or from others in the class. Coming from c++/javascript style languages I understand that there must be a base statement (otherwise the function could call itself forever). Would that not require a conditional statement?

Heres the problem:

**Direct recursion is a term used when a procedure calls itself. You do not want want to let a procedure keep calling itself forever, because the runtime stack would fill up. You need to limit the recursion in some way.

Write a MASM program that calls a recursive procedure. Label the procedure recProc. Inside this procedure, add 1 to a counter so you can verify the number of times it executes. Run your program with a debugger, and at the end of the program, check the counter’s value. Put a number in ECX that specifies the number of times you want to allow the recursion to continue.

Use only the LOOP instruction and no other other conditional statements.

Find a way for the recursive procedure to call itself a fixed number of times.

When using procedures, you need to ensure that registers and flags are preserved. You need to use PUSHFD/POPFD and PUSHAD/POPAD.**

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • cmov? could you useit? – Severin Pappadeux Apr 12 '18 at 04:13
  • @SeverinPappadeux The professor didnt explicitly say we cannot, but i dont see it in our book, so I would say no. – Brayan Castro Apr 12 '18 at 04:15
  • 2
    We seem to have some contradictory requirements here. Is ECX supposed to hold the number of times recProc is *supposed* to run? Or the number it *did* run? I suppose theoretically it could be used for both, but that seems silly. Also, `loop` **is** a conditional statement. Picture `loop notdone`. – David Wohlferd Apr 12 '18 at 05:55
  • Hint: recall that `LOOP` decrements ECX. If ECX becomes equal to zero, controls falls through to the next statement, otherwise it goes to the label argument. – 500 - Internal Server Error Apr 12 '18 at 07:04

3 Answers3

2

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • `inc ecx` \ `loop` is 3 bytes in 32-bit mode, `inc cx` \ `loop` is 3 bytes in 16-bit mode. 64-bit mode is lacking in single-byte increment instructions so `inc ecx` \ `loop` (2 + 2) is A. as large as `test` \ `jnz` (2 + 2) and B. you need `inc rcx` (3) or `test rcx, rcx` (also 3) because `loop` defaults to `a64`. Or `inc ecx` \ `a32 loop` (2 + 3). Ie in 64-bit mode the `loop` alternative never saves any bytes (just different status flags). – ecm Sep 28 '21 at 18:16
  • @ecm: Oh, derp, didn't look at enough of the context of that statement when looking at your edit, just had in my head the fact that `test ecx,ecx` is the same size in both 32 and 64-bit mode, and that `loop` saves bytes vs. `dec ecx/jnz` in all modes, neither of which were the point. Thanks, fixed. – Peter Cordes Sep 28 '21 at 18:22
  • I forgot, does `a32 loop` in a 64-bit mode clear the upper half of `rcx`? That would make a further difference between `inc ecx` \ `a32 loop` or using `o32 test ecx, ecx`. – ecm Sep 28 '21 at 18:29
  • 1
    @ecm: Yes, every write of a 32-bit register zero-extends into the full 64-bit register. (I didn't double check that fact, but I'm pretty sure it's true here.) – Peter Cordes Sep 28 '21 at 18:41
  • Oops the `o32 inc` already zero extends into `rcx` regardless. So my question doesn't matter here. – ecm Sep 28 '21 at 18:43
0

The loop itself is conditional branch thing, and the whole task description is somewhat... meh... for my taste. hmmm...

Considering the task description and the minimal effort, I would probably produce this (didn't debug it, as I don't have MASM or any OS capable of running MASM, so fix syntax problems yourself, but the principle should be clear from the comments)

; in data section
counter DWORD 0

; in code section

; recursive function to call itself recursively ECX-1 many times
; ECX should be above zero (for zero it will end with 4 billions
; of recursive depth exhausting stack memory quickly)
recProc PROC
    ; preserve flags and all register values
    pushfd
    push   ecx      ; only ECX is modified in procedure body
    ; (no need for weapons of mass destruction like PUSHAD)

    ; don't call recursively with the same ECX, decrement it first
    jmp    recProc_test_if_call_is_needed

    ; main loop calling recProc ECX-1 many times
recProc_loop:
    call   recProc
recProc_test_if_call_is_needed:
    ; count every iteration, even ones not leading to recursion
    inc    DWORD PTR [counter]
    loop   recProc_loop

    ; restore all registers and flags
    pop    ecx
    popfd
    ret
ENDP

This should produce for ECX=3 argument value in [counter] == 6 if I did debug it in my head correctly. (And overall [counter] == ∑i, i=1,..,original ecx)


EDIT:

And about the general question in topic "is it possible to create a recursive function without conditional statement" ...

Well, the recursive function has to call itself, otherwise it would be not a recursive function, so 1) somewhere in the body of the function is a self-call.

2) if there is no conditional statement affecting code flow, the function will always run in the same way (control-flow wise), i.e. doing the self-call.

1+2 => infinite loop.

You can argue what qualifies as conditional statement, for example you can create inside function jump which calculates target address as math expression, jumping sometimes into code path containing the self-call, and sometimes to code path not containing the call, making the recursion terminal under certain conditions, but without using explicit Jcc or loop conditional branching, but for me even that qualifies as "conditional" statement.

So the simple answer is: NO, it is not possible.

But in your task it is possible because you were given loop instruction. BTW do NOT use loop instruction in "production" code: Why is the loop instruction slow? ... same goes for pushad/popad (that's also horrible idea - especially in recursive functions, because that means the depth of recursion will be seriously limited by the wasted stack space used to preserve registers which don't change, but even in non-recursive calls it's in 99% of cases better (performance wise) to save particular registers only, not using pushad/popad), and preserving flags between calls is ridiculous too. That's why I find your task description "meh", as it's literally enforcing you to write stupid code. :/

Ped7g
  • 16,236
  • 3
  • 26
  • 63
  • My first thought for ending recursion without any kind of conditional branch was self-modifying code :P See [The Story of Mel](http://catb.org/jargon/html/story-of-mel.html) for a classic example. – Peter Cordes Apr 12 '18 at 10:04
  • @PeterCordes Yeah, but that's not enough pure in math sense to me. But you have some point, as it's not obvious how to call it "conditional". I would rather mark it invalid as it is calling different function then (not self-call), so it's not recursion. :) ;) – Ped7g Apr 12 '18 at 10:11
  • Well sure, to use it for this you'd overwrite one of the instructions in your function to have it return instead of calling itself. Instead of calling a different function. And BTW, `loop` can branch forward; you're overcomplicating this. The 8-bit displacement is a standard `rel8`, not +0..+255. – Peter Cordes Apr 12 '18 at 10:13
  • @PeterCordes I don't get that about overcomplicating.. your variant has two `ret` paths on contrary? I believe both are of very similar complexity, and whichever is simpler may be purely personal preference, for me having extra entry point into "complete normally ordered do{}while" loop is a bit easier to read, than the intermediate return, as in my code I often had some stack/register restoring and having multi return path was a bit more tedious for me. Unless I'm missing something obvious, I think this is about personal preference, and it's nice you provided the other option too. :) – Ped7g Apr 13 '18 at 08:15
  • I thought yours was overcomplicated before I'd taken in all the stupid requirements in the OP's assignment to use `pushad` / `pushf`, and before I'd actually written my answer, I think. As far as it being personal preference: no, it's a choice between tail duplication costing extra code size vs. having an extra `jmp` on the execution path through the function. I prefer to optimize for fewer instructions executed, but there are objective reasons why you might choose one vs. the other. – Peter Cordes Apr 16 '18 at 22:37
0

And here is my code for this challenge

include Irvine32.inc
.386
.model flat, stdcall
.stack 4096
ExitProcess Proto, dwExitCode:dword
.data
    sum dword ?
.code
main proc
    mov ecx, 5                         ; counter loop in recursive
    mov eax, 0                          ; sum =0
    call recursive
    mov sum , eax
    mov eax, sum
    call WriteDec                       ; if you want display to console window: use Lib or write code
invoke ExitProcess, 0
main endp
; Recusive
; receive : ECX : counter
; eax : accumulator
recursive proc 
    loop l1                     ;loop first , so it alway used, to dec ECX, 
    ret                         ; when ecx=0, no loop, ret is used... back to main
    l1:
        add eax, 1          ;add 1 to accumulator
        call recursive
        ret
recursive endp
end main

Thanks for watched

nam4lpha
  • 21
  • 1
  • 4
  • Please translate *all* non-English comments and identifiers (in code). This site only assumes that readers speak English. – HelpingHand Sep 25 '21 at 17:34