Search code examples
assemblyx86masmirvine32state-diagram

How and where to end this finite state machine program working?


I have written this program from a diagram given to me. I just can't figure out where and how to stop it from running. It keeps asking for inputs forever. Where to add exit command in my program? Any idea?

Thank you.

INCLUDE Irvine32.inc
.data
A DWord ?
B dword ?
prompta  byte "what is your digit a?",0
promptb  byte "what is your digit b?",0
message0 byte "you are in s0 with output ",0
message1 byte "you are in s1 with output ",0
message2 byte "you are in s2 with output ",0
message3 byte "you are in s3 with output ",0
.code

 main PROC


  call s0
   call waitmsg

Initial State is S0

 myexit proc
   mov eax, white+16*black
   call settextcolor
   call waitmsg
   ret
 myexit endp

This exit procedure here isn't working

 readdigits proc
 mov edx, offset prompta
 call writestring
 call readint   ; dword into eax
 mov a,eax
 mov edx, offset promptb
 call writestring
 call readint
 mov b,eax

 ret 
 readdigits endp

Procedures for S0,S1,S2,S3 Start here

 s0 proc
 mov edx,offset message0
 call writestring
 mov eax,0               ;Output is 0 in State 0

 call writedec
 call crlf

 call readdigits

.if(a==0)&&(b==0) 
 call s0
.endif
.if(a==1)&&(b==1)
 call s1
.endif
call s2
ret
s0 endp

s1 proc
mov edx,offset message1
call writestring
mov eax,0               ;Output is 1 in State 0
call writedec
call crlf

 call readdigits
.if(a==0)&&(b==0) 
 call s2
.endif
.if(a==1)&&(b==1)
 call s3
.endif
 call s1
 ret
 s1 endp

 s2 proc
 mov edx,offset message2
 call writestring
 mov eax,1                  ;Output is 1 in State 2
 call writedec
 call crlf

 call readdigits
.if(a==0)&&(b==0) 
 call s0
.endif
.if(a==1)&&(b==1)
 call s1
.endif
call s2
ret
s2 endp


s3 proc
mov edx,offset message3
call writestring
mov eax,1                 ;Output is 1 in State 2
call writedec
call crlf

call readdigits
.if(a==0)&&(b==0) 
 call s2
.endif
.if(a==1)&&(b==1)
call s0
.endif
call s1
ret
s3 endp


main endp
end main

Solution

  • Normally for a state-machine, you'd write it as a single function, using jmp instead of call to go to the next state.

    Your state functions never return, they always jump to a new state (or to re-run the current state, like the call s1 at the bottom of s1.) The ret at the end is never reached, so you're just pushing an ever-growing amount of return addresses that prevent you from actually returning to main.

    Use labels like s1: instead of s1 proc. Or you can still use that MASM syntax to pretend each one is a separate functions, but use jmp s2 to tail-call the next state.

    Then when you detect a termination condition as the next state, you can ret, and it will return to main.


    This has the big advantage that you can use conditional branches like jne s1 instead of jumping over a call/jmp. MASM's .if syntax might not be powerful enough to do that for you, but you're getting huge missed optimizations from using that anyway. e.g. a==0 && b==0 can be checked with mov eax, a / or eax, b / jz both_were_zero. Plus, with only 2 "variables", keep them in call-preserved registers like ebx and esi or something, instead of keeping them in memory at all. That's what registers are for! You're writing in asm

    Also, you can optimize by laying them out so s3 can fall-through into s1 instead of ending with jmp s1. Or keep it simple and keep the jmp. (If I was doing this optimization, I'd leave the jmp s1 as a comment as documentation that the fall-through to s1 as the next state is intentional.)


    You have the rest of your code nested inside your main proc, which is weird but maybe not causing an actual problem. But the first code block with the start of main shows that it falls through from call waitmsg into myexit proc? That's super weird, but should actually work if myexit is what you want to happen.

    Also, you don't have a main endp line until the end of your file, so you're telling the assembler that the other proc declarations are inside main.