The MASM Forum Archive 2004 to 2012

General Forums => The Campus => Topic started by: MazeGen on January 09, 2005, 08:45:45 PM

Title: Nested procedures: are they actually supported or not?
Post by: MazeGen on January 09, 2005, 08:45:45 PM
It would be useful in my project to nest procedures. My procedures are often a few thousands of lines and from time to time I need to use smaller blocks of code (say, 50 lines) several times inside the procedure. I don't want to define such small procedure outside the parent's procedure (it would be badly readable). Such a block is also too long for a macro, which I can define anywhere, so I need a nested procedure, defined inside the procedure in area where it logically belongs to.
BTW, I know I can use

call MyProc

MyProc:
...
retn

But I would like to take advantages of standard procedure definition, like local scope of symbols etc.

I've read this (http://www.old.masmforum.com/viewtopic.php?t=1641) thread, which says it is impossible in MASM.
Some time ago I found the following words in MASM Programmer's Guide:

Quote from: Chap_07.doc, page 186, line 26You can nest procedures if they do not have parameters or USES register lists.

But when I try to compile the following:

.686
.MODEL FLAT, STDCALL

include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

.CODE
MainProc PROC USES ebx edi arg1:DWORD, arg2:DWORD
call NestedProc
ret

NestedProc PROC
  mov eax,1
  ret
NestedProc ENDP

MainProc ENDP

Start:
invoke MainProc, 1, 2
invoke ExitProcess, 0
END Start


ML says "cannot nest procedures"  :(

Have I overlooked something?
Title: Re: Nested procedures: are they actually supported or not?
Post by: hutch-- on January 09, 2005, 09:24:47 PM
Karel,

The only way is to code it manually as MASM is designed to block nested procedures. If you have a need to do this, I would be inclined to make macros to do the entry and exit of your own nested procedure format.
Title: Re: Nested procedures: are they actually supported or not?
Post by: MazeGen on January 09, 2005, 09:31:21 PM
But what about "You can nest procedures if they do not have parameters or USES register lists" then, hutch--? Documentation bug?

[OT]It is astonishing how long you can remember my name, Steve :eek[/OT]
Title: Re: Nested procedures: are they actually supported or not?
Post by: hutch-- on January 09, 2005, 09:39:24 PM
Karel,

I have never seen that comment in MASM reference, I would be interested to see whare it came from.

I have a form of brain-lock on names once they sink in, hard to get in but even harder to get out.  :bg
Title: Re: Nested procedures: are they actually supported or not?
Post by: MazeGen on January 09, 2005, 09:58:20 PM
I've searched for it everywhere and I've just found this (http://board.win32asmcommunity.net/viewtopic.php?t=3650) thread on win32asmcommunity. Hopefully Randall will visit our thread and explain his note about nested procedures from AoA.
Anyways it seems nested procedures are not allowed in MASM  :'(
Title: Re: Nested procedures: are they actually supported or not?
Post by: MichaelW on January 09, 2005, 11:02:49 PM
From the MASM 6.0 Programmer's Guide, F.9.2 ML Errors:
Quote
A2144:  cannot nest procedures

An attempt was made to nest a procedure containing a parameter, local variable, USES clause, or a    statement that generated a new segment or group.

But this seems to apply not only to the nested procedure, but also to the procedure that contains the nested procedure.

This will work:

; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
    .486                       ; create 32 bit code
    .model flat, stdcall       ; 32 bit memory model
    option casemap :none       ; case sensitive

    include \masm32\include\windows.inc
    include \masm32\include\masm32.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\masm32.lib
    includelib \masm32\lib\kernel32.lib
    include \masm32\macros\macros.asm
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
    .data
    .code
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
start:
; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
    call MainProc
    mov   eax, input(13,10,"Press enter to exit...")
    exit

MainProc proc
    print chr$("MainProc",13,10)
    call NestedProc
    print chr$("MainProc",13,10)
    ret
   
      NestedProc proc
          print chr$(" NestedProc",13,10)
          call NestedNestedProc
          print chr$(" NestedProc",13,10)
          retn

        NestedNestedProc proc
            print chr$("  NestedNestedProc",13,10)
            retn
        NestedNestedProc endp

      NestedProc endp

MainProc endp

; «««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««««
end start


But when you eliminate parameters and local variables you effectively loose most of the benefits of using a procedure.
Title: Re: Nested procedures: are they actually supported or not?
Post by: donkey on January 10, 2005, 12:13:19 AM
Seems to be as long as you do not need a stack frame it's OK, pretty much the only reason to use the PROC statement is to set up stack frames. I guess you can do this...

SomeProc PROC param

call nestedproc
...

ret

nestedproc:



retn
SomeProc endp
Title: Re: Nested procedures: are they actually supported or not?
Post by: sluggy on January 10, 2005, 01:31:15 AM
Karel,
are you talking about having multiple entry points into the same proc, like the way it used to be done in old school ROM routines? If so, it should be like the guys said, just don't have a stack frame and carry your parameters in the various registers. I still like that old way of doing it, and have been tempted to use that method a few times....
Title: Re: Nested procedures: are they actually supported or not?
Post by: MazeGen on January 10, 2005, 09:52:43 AM
Thanks, guys.

I just like to know all features of my assembler. You're right the only advantage of the nested procedure would be that all labels inside the procedure are local to the procedure and that's what missing to me.
Title: Re: Nested procedures: are they actually supported or not?
Post by: hutch-- on January 10, 2005, 10:00:58 AM
Sluggy has a good point there, I remember libraries being written that way to provide some unusual functionality where you could use different data sizes to enter the same proc but it was always coded fully manually. It still can be done if you want to code it that way but its a reasonable amount of work and the main gain back in those days was space which does not matter that much now.
Title: Re: Nested procedures: are they actually supported or not?
Post by: MazeGen on January 10, 2005, 10:08:16 AM
No, it doesn't apply in my case. All what I needed was a simple nested procedure (with parameters given in registers), defined inside the procedure in area where it naturally belongs to, as I wrote above.
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 11, 2005, 03:04:58 AM
MazeGen,
     Your question gives me a opportunity to go into my periodic rant again.  Specifically, I contend that the directives PROC, ENDP, USES, and all the other garbage "features" that support this method of subroutine calls are attempts by MASM to be something it is not, a C compilier.  It simply obfuscates what should be a straight forward operation; one subroutine calling another.  Let me get this straight.  You want to CALL another subroutine B while you are in subroutine A, right?  Perhaps subroutine A has a stack full of parameters, surely a return address, and probably a bunch of local variables.  Now you want to load some more parameters on the stack and CALL subroutine B, which could need some more stack space for local variables.  And then subroutine B might even call subroutine C and so on.  That's nesting.  Furthermore, while in subroutine C, you might want to reference a parameter or local variable from subroutine A or B.  Is that what you want to do?  Well, theoretically you can do it with the PROC method I suppose.  But it looks like you have a hard time figuring out the syntax and getting it to work correctly.  And unless you examine a expanded listing, you are probably not too sure what the hell the assembler is doing.  And PROC sometimes calls up those inefficient time consuming stack frame instructions like LEAVE.  Furthermore, it is always PUSH'in and POP'in that EBP register every time you CALL and RET to/from a PROC.  I believe I have a better method that dispenses with all the red tape and hassle of the PROC method.  As a bonus, it does not use the EBP register for a stack frame pointer.  That means a subroutine has another register to use in the register starved environment of the INTEL X86 series.

     OK, what do you have when all the parameters, return addresses, and local variables are loaded on the stack from a series of nested calls?  That's right, you have a STRUC, referenced relative to the ESP register.  So at the beginning of each subroutine I build a STRUC of what my stack will look like after it is "loaded".  Then I simply do a STRUC reference for each local variable or parameter I need.  If I am within a nested subroutine, and need something from the stack of a previous subroutine, I include the STRUC of the previous subroutine within the STRUC of my present subroutine.  Basically I am using a static method with the assembler instead of a dynamic method with the INTEL cpu.

     If you are interested, I could present an example.  But I don't want to go through the work unless it is desired and appreciated.  Ratch
Title: Re: Nested procedures: are they actually supported or not?
Post by: hutch-- on January 11, 2005, 06:05:55 AM
I have heard these notions of assembler purism before that an assembler should only be used for mnemonic coding but we all know where that approach led where assembler was seen as hard to code and impossible to maintain or debug. What dragged assembler out of the hole was the high level capacity available in MASM so that the high level capacity of Windows could be used easily and quickly.

To go along with this stuff, we used to get told that modern compilers generate better code than hand coded assembler yet when you bother to benchmark the "gee whiz" stuff, its not that fast but it sure sounds good. The normal output of properly coded MASM using its high level emulation capacity is more than good enough to compete with modern compilers doing the same tasks.

As a rough distinction, use the easiest and clearest code possible where you are writing anything short of atom cracking algorithms and when you need something that is FAST, code it manually to get the best time but at least know the difference.

As far as the notion of a "poor mans C compiler", the overlap of languages leaves you with the other end of the problem where trying to code target assembler tasks in a compiler gives you the "poor man's assembler", its simply a matter of knowing the difference.
Title: Re: Nested procedures: are they actually supported or not?
Post by: rea on January 11, 2005, 07:50:23 AM
I only whant to correct a little :), I gues I can explain my point this are only notes :).

Quotehigh level capacity available in MASM

I think is not hig level capacity that will implicate more than get a more clear way of use structured programming in an assembler like masm, something like abstraction of registers,  types and typecheking, easy tyiping of algebraic expresions, not the crude way of define structs that in asm are more only like startOfMemory+displacement ie they define a type, in fact define a language that is diferent than the opcodes of the processor used.


Quotehigh level emulation capacity
I dont like the emulation in assemblers, Im used to nasm but you know we have also a preprocessor and for emulate what do/look masm and at the end to what look C has lead to some things, like find that emulation is not suficient because the preprocessor isnt suficient (some things cant be done), also because preprocessing mean in the major of cases a foot-print (or static print) optimization cant be done and little garbage is generated but more that you use those static footprints, more repetitions that you find, I agree that is nice to have structured programming (SP) in an assembler and also OOP pheraphs others paradigms that I dont know and not implicate a high level language, but will be best if that structured programming is not inserted much "ala high level compilers" or "ala C" in emulation via preprocessor, but inside of the assembler.
Title: Re: Nested procedures: are they actually supported or not?
Post by: MazeGen on January 11, 2005, 11:35:39 AM
Ratch,
QuoteLet me get this straight.  You want to CALL another subroutine B while you are in subroutine A, right?  Perhaps subroutine A has a stack full of parameters, surely a return address, and probably a bunch of local variables.  Now you want to load some more parameters on the stack and CALL subroutine B, which could need some more stack space for local variables.  And then subroutine B might even call subroutine C and so on.  That's nesting.

We've discussed here nesting in context of definying one procedure inside another procedure (pure compile-time problem). You are speaking about different context of nesting procedures.

There were many fights whether HL support in MASM is a feature or just a garbage and I don't want to fight here again ;-)
Only a few words, based on my experience:
For some reasons, when I started using assembly language, I never used any HL syntax or macros, because I simply loved the most lowest level of coding.
Now, after some years of coding in assembly, I use a lot of macros, HL syntax and so on, simply because my code is much more READABLE, and the assembler emits no additional code, because I know how to use them. So my point of view is opposite to you: I feel my code is obfuscated when I don't use them (push/call versus invoke; long identifier names in procedures because of its global visibility versus short private names in procedures defined using PROC; many repetitive lines of code versus simple macros, which expands this code; and so on and so on.)

About your reservations:

QuoteAnd PROC sometimes calls up those inefficient time consuming stack frame instructions like LEAVE.

As far as I know, AMD does recommend to use LEAVE to release the stack frame. If you don't like LEAVE, you can code your own epilogue code.

QuoteFurthermore, it is always PUSH'in and POP'in that EBP register every time you CALL and RET to/from a PROC.

Only when you use any parameters or locals.

Anyways, I can always suppress generation of prologue and epilogue code. PROC nad ENDP directives are still worth for me as they naturally delimit the procedure and procedures defined with them have private identifiers.

QuoteOK, what do you have when all the parameters, return addresses, and local variables are loaded on the stack from a series of nested calls?  That's right, you have a STRUC, referenced relative to the ESP register.  So at the beginning of each subroutine I build a STRUC of what my stack will look like after it is "loaded".  Then I simply do a STRUC reference for each local variable or parameter I need.  If I am within a nested subroutine, and need something from the stack of a previous subroutine, I include the STRUC of the previous subroutine within the STRUC of my present subroutine.  Basically I am using a static method with the assembler instead of a dynamic method with the INTEL cpu.

I'm just working with Petroizki on a macro set, which allows you to do the same thing automatically. It is called "pmacros" and older versions can be found on the old board. New version will be much more improved.
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 11, 2005, 12:42:04 PM
MazeGen,
QuoteWe've discussed here nesting in context of definying one procedure inside another procedure (pure compile-time problem). You are speaking about different context of nesting procedures

     PROCS/subroutines have to be defined and CALLed.  My method can define a subroutine at anywhere and CALL to anywhere within a .CODE segment.

QuoteFor some reasons, when I started using assembly language, I never used any HL syntax or macros, because I simply loved the most lowest level of coding.
Now, after some years of coding in assembly, I use a lot of macros, HL syntax and so on, simply because my code is much more READABLE, and the assembler emits no additional code, because I know how to use them. So my point of view is opposite to you: I feel my code is obfuscated when I don't use them (push/call versus invoke; long identifier names in procedures because of its global visibility versus short private names in procedures defined using PROC; many repetitive lines of code versus simple macros, which expands this code; and so on and so on.)

     This is not a high level code issue.  PROCs and subroutines are not a high level item.  Spaghetti code can still flourish with PROCS or subroutines.  I am not against high level, and sometimes even use the built in features of MASM supporting high level constructs when they emit code to my liking.  I also use INVOKE, which is neither a high level construct or macro.  Neither am I against MACROs.  I've written plenty of them and use them all the time.  My gripe is the inplementation of subroutines using the PROC method.

QuoteAs far as I know, AMD does recommend to use LEAVE to release the stack frame. If you don't like LEAVE, you can code your own epilogue code.

     I concede you are correct.  IF you are going to use stack frames with EBP, then LEAVE is for you.  Now, note what else AMD says about stack frames.
QuoteThis (LEAVE) is optimal in cases where the use of a frame pointer is
desired. For highest performance code, do not use a frame
pointer at all. Function arguments and local variables should be
accessed directly through ESP, thus freeing up EBP for use as a
general purpose register and reducing register pressure.

     As AMD recommends above, I reference args and vars directly through ESP.

QuotePROC nad ENDP directives are still worth for me as they naturally delimit the procedure and procedures defined with them have private identifiers.

     Subroutines can be defined just fine with indentation and white space.  The fields within a STRUC are private too.  Just a matter of choice.  Ratch
Title: Re: Nested procedures: are they actually supported or not?
Post by: MazeGen on January 13, 2005, 05:53:07 PM
Quote from: RatchI am not against high level, and sometimes even use the built in features of MASM supporting high level constructs when they emit code to my liking.  I also use INVOKE, which is neither a high level construct or macro.  Neither am I against MACROs.

It seems I've acted too rashly - when I've read that PROC, ENDP etc are garbage features, I've thought you don't like anything else than mnemonics ;-)

Quote from: RatchSo at the beginning of each subroutine I build a STRUC of what my stack will look like after it is "loaded".  Then I simply do a STRUC reference for each local variable or parameter I need.  If I am within a nested subroutine, and need something from the stack of a previous subroutine, I include the STRUC of the previous subroutine within the STRUC of my present subroutine.  Basically I am using a static method with the assembler instead of a dynamic method with the INTEL cpu.

You take interesting way.

Quote from: RatchI reference args and vars directly through ESP

How did you tackle with the ESP changes after each PUSH/POP? Do you use some trick, or do you simply adjust some variable manually?
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 13, 2005, 08:35:12 PM
MazeGen,

QuoteIt seems I've acted too rashly - when I've read that PROC, ENDP etc are garbage features, I've thought you don't like anything else than mnemonics ;-)

     Try it, you'll like it.

QuoteYou take interesting way.

     It is a methodical way of implementing a "frameless" stack.

QuoteHow did you tackle with the ESP changes after each PUSH/POP? Do you use some trick, or do you simply adjust some variable manually?

     Below is an example.  Notice that I adjust the PUSH parameter to reflect what was pushed before.  Also since I do not need the EBP to keep track of the stack, I usually set it to zero.  Since zero is often used in comparison instructions and parameters, having this value in a register shortens the instruction length.  For instance, PUSH 0 is two bytes while PUSH EBP is only one byte.  As you can see from the example, having the stack represented by a STRUC is like looking at a map.  If I need to call still another subroutine, I encapsulate the calling STRUC within the CALLed subrouinte's STRUC and continue.  Thus you can nest subroutines to any level and access the params of any previous level.  It also helps to use EQUs to cut down on the typing.  Questons or comments are welcome.  Ratch

MAIN:

; lots and lots of skipped code

INVOKIT PopFileRead,pstrFileName,hwndEdit ; subroutine called with EBP=0

;more skipped code



;_____PopFileRead Subroutine____________________________________________________

STKPFR        STRUC            ;tailor the STRUCT according to the subroutine
EBXsave      DWORD ?          ;\
EDIsave      DWORD ?          ; >saved registers
ADR1 = $
iUniTest     DWORD ?          ; \
dwBytesRead  DWORD ?          ;  \
iFileLength  DWORD ?          ;   >local variables for subroutine
pBuffer      DWORD ?          ;  /
pText        DWORD ?          ; /
pConv        DWORD ?          ;/
ADR2 = $
return       DWORD ?          ;return address
ADR3 = $
hwndEdit     DWORD ?          ;\
pstrFileName DWORD ?          ; >pushed parameters
ADR4 = $
return1      DWORD ?          ;\
hwnd         DWORD ?          ; \
msg          DWORD ?          ;  >You don't have to add these unless you are in message
wParam       DWORD ?          ; /     processing, and need to reference these params from the
lParam       DWORD ?          ;/      CALLBACK routine. They are already PUSHed on the stack
STKPFR ENDS

S$1 EQU ESP.STKPFR             ;use this to save some typing

PopFileRead:                   ;PopFileRead--hwndEdit,pstrFileName--enter this subroutine with EBP=0
SUB ESP,ADR2-ADR1             ;make local variables space
RPUSHIT EBX,EDI               ;save those registers

                               ;Open the file
;                              ;***NOTICE HOW WE HAVE TO ADJUST FOR 6 PUSHES AHEAD OF THIS PARAMETER
;                               ^
;                              / \
;                             /   \
;                            /     \
;                           /       \
;                          /         \
;                         /           \
;                        /             \
;                       /               \
;                      /                 \
;                     /                   \
;                    /                     \
;                   /                       \      NOTICE BELOW THAT A PUSH EBP IS A ONE BYTE
;                  /                         \     INSTRUCTION AND PUSH 0 IS TWO BYTES!
                  ;_____________^_____________\
INVOKE CreateFile,[S$1.pstrFileName+6*DWORD],GENERIC_READ,FILE_SHARE_READ\
                  ,EBP,OPEN_EXISTING,EBP,EBP
.IF EAX==INVALID_HANDLE_VALUE
   XOR EAX,EAX                 ;return false
   JMP PFR$RET1
.ENDIF

; skipped code



INVOKE LocalAlloc,LMEM_FIXED,EAX
MOV [S$1.pBuffer],EAX
                               ;Read file and put terminating zeros at end.
LEA EAX,[S$1.dwBytesRead]
INVOKE ReadFile,EBX,[S1$.pBuffer+3*DWORD],[S1$.iFileLength+2*DWORD],EAX,EBP
                               ;***NOTICE THE STACK COMPENSATION IN THE INVOKE ABOVE

; skipped code

MOV ECX,[S$1.iFileLength]     ;typical example of a local variable reference

;skipped code

                               ;***NOTICE THE STACK COMPENSATION IN THE INVOKE BELOW
INVOKE IsTextUnicode,[S$1.pBuffer+2*DWORD],[S$1.iFileLength+1*DWORD],EAX ;EAX=&iUniTest

TEST EAX,EAX
.IF !ZERO?                    ;is the file Unicode?
   MOV ECX,[S$1.pBuffer]
   SUB [S$1.iFileLength],2
   INC ECX
   INC ECX
   LEA EDX,[EBP+4]             ;EBP=0,EDX=4
   MOV [S$1.pText],ECX   

   .IF D[S$1.iUniTest] & IS_TEXT_UNICODE_REVERSE_SIGNATURE ;
     MOV ECX,[S$1.iFileLength]

     ;skipped code

   .ENDIF
.ENDIF

;skipped code

INVOKE LocalFree,[S$1.pConv]
MOV EAX,ESP                   ;RETURN ESP=TRUE = NONZERO

PFR$RET1:
POPIT EBX,EDI                 ;RESTORE THOSE REGGIES
ADD ESP,ADR2-ADR1             ;RECOVER LOCAL VARIABLE SPACE
RET ADR4-ADR3                 ;see STRUC STKPFR ABOVE

END MAIN

Title: Re: Nested procedures: are they actually supported or not?
Post by: Randall Hyde on January 14, 2005, 11:37:45 PM
Although versions of MASM have allowed you to "nest" procedures (without prolog generating code), such nesting rarely had the intended effect (say, as we've come to expect in a high level language). For example,


xyz proc
     <some code>
abc proc
    <some code>
abc endp
    <some more code>
xyz endp

The xyz proc falls directly into abc. It does not jump around abc as you would expect in a block-structured HLL. This is why you can't have stack frames in nested procedures -- it really messes things up.

If you want block-structured assembly code, use HLA :-)
cheers,
Randy Hyde
Title: Re: Nested procedures: are they actually supported or not?
Post by: Randall Hyde on January 14, 2005, 11:42:30 PM
Quote from: Ratch on January 11, 2005, 03:04:58 AM
MazeGen,
     Your question gives me a opportunity to go into my periodic rant again.  ...<snipped>
     If you are interested, I could present an example.  But I don't want to go through the work unless it is desired and appreciated.  Ratch
You are confusing static or lexical nesting (of, say PROC directives) with dynamic nesting.

Static nesting is useful for information hiding purposes and encapsulation. If those terms don't mean anything (good) to you, then I can see why you don't like nested procedures.  The cool thing about statically nested procedures, though, is that you can easily access local variables in the outer procedure. That's not something you can easily do without a block-structured language.  And in some cases, the ability to do this is quite useful (I use this feature all the time, for example, in the HLA v2.0 source code).  Sure, you could make all your variables global, or pass pointers to your locals to other procedures, but that's inefficient and clumsy. 

Given that you're supposed to be able to do everything in assembly that can be done in a HLL, I find it amusing when people argue how you *shouldn't* do advanced things in assembly. :-)
Cheers,
Randy Hyde
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 15, 2005, 02:38:30 AM
Randall Hyde,

QuoteYou are confusing static or lexical nesting (of, say PROC directives) with dynamic nesting.

     Au contraire, I do know the difference between static and dynamic nesting.  Procedures/subroutines need to be defined (static), and called (dynamic).  Static nesting doesn't mean too much in a assembler unless it tries to be a compiler with lots of tables keeping track of what variable is local or global.  And as you pointed out, MASM doesn't even prevent fall through execution.

QuoteStatic nesting is useful for information hiding purposes and encapsulation. If those terms don't mean anything (good) to you, then I can see why you don't like nested procedures.

     Information concealment and encapsulaton aren't necessary to keep me from writing bad code.

QuoteThe cool thing about statically nested procedures, though, is that you can easily access local variables in the outer procedure.

     I can reference anything pushed on the stack (local variables) using my method as explained in my previous example.

QuoteSure, you could make all your variables global, or pass pointers to your locals to other procedures, but that's inefficient and clumsy

     I use globalization sparingly and carefully.  I usually pass params to subroutines using the stack and registers when convenient and prudent.

QuoteGiven that you're supposed to be able to do everything in assembly that can be done in a HLL, I find it amusing when people argue how you *shouldn't* do advanced things in assembly. :-)

     I don't shirk from violating "structured programming principles" to speed up or shrink code, provided it is clear and documented.  Ratch

Title: Re: Nested procedures: are they actually supported or not?
Post by: hutch-- on January 15, 2005, 11:44:06 AM
I have tended to see procedures in the light of how convenient they are and constructing a stack frame ALA MASM proc does not worry me one iota as in most instances it does not effect the speed of the code.

When a procedure is short enough to be sensitive to stack entry and exit overhead, it is also small enough to inline which is clearly faster that procedures with no stack frame. I do bother from time to time to write procedures without a stack frame but it is to get EBP as a general purpose register, the speed gain is negligible but the extra register allows you to write code that is often a lot faster as you can avoid a range of memory load/store operations by doing so.
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 15, 2005, 03:03:41 PM
hutch--,
     To each his/her own choice.  Stack frames just aren't my way.  Ratch
Title: Re: Nested procedures: are they actually supported or not?
Post by: MazeGen on January 19, 2005, 05:18:18 PM
Ratch,
it is interesting indeed, I've never seen similar method before.

But I still don't see how you handle the ESP changes after PUSH/POP somewhere inside the procedure:


PUSH EBX
...
MOV EAX,[S$1.pBuffer] ; address of S$1.pBuffer will be incorrect here
...
POP EBX


There is also at least one duplicity: Registers saved in the prologue code using RPUSHIT macro have to match appropriate items in the stack structure and tehrefore it may lead to bugs.

I, personally, like automatized method more than doing it all by hand with structures and macros. I hope new verison of pmacros will be finished soon.
Title: Re: Nested procedures: are they actually supported or not?
Post by: rea on January 19, 2005, 09:55:16 PM
One way is that you already know how is the structure of the stack, if you see, there normally always be arguments, locals, more locals pushes that in the mean time will become arguments to other function pheraphs.


In that way, you can define deepness of a local, for example [nArguments][retaddr][nLocals][nPushes] with each push you update a extra variable and this one "know" the feep  (how many) pushes have you done, then you can reference a local function some like.....

for example, the deep of the arguments is like this:
nPushes*4+nLocals*4+4+argumentToAccess
or
deepOfPushes+deepOfLocals+4+argumentToAccess

And the deep of the locals is:

nPushes*n4+localToAccess
or
deepOfPushes+localToAccess

In that way you only need update one variable for each push and pop, also in that way you can have nested locals, you only do the clean of the nesteds locals...



That way have worked the majority of the times nice, dont remember exactly when I have problems ;).....
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 20, 2005, 02:31:00 AM
MazeGen,

QuoteBut I still don't see how you handle the ESP changes after PUSH/POP somewhere inside the procedure:

     You COULD do it this way as shown in my example above when using INVOKE.


PUSH EBX
MOV EAX,[S$1.pBuffer+DWORD]
POP EBX


     But another way would be to define something like EBXSAV DWORD ? within the STRUC. Then code the following.


MOV [$$1.EBXSAV],EBX
MOV EAX,[S$1.pBuffer]        ;compensation not needed here this time
MOV EBX,[$$1.EBXSAV]


QuoteThere is also at least one duplicity: Registers saved in the prologue code using RPUSHIT macro have to match appropriate items in the stack structure and tehrefore it may lead to bugs.


RPUSHIT EBX,EBP,ESI,EDI
..........
POPIT EBX,EBP,ESI,EDI


    The RPUSHIT MACRO generates code to PUSH the parameters onto the stack in reverse order.  POPIT takes the params off the stack in normal order.  You can copy the params of RPUSHIT directly to POPIT and be assured the params will store and retrieve in the correct order.  Ratch
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 20, 2005, 03:03:52 AM
rea,

     You don't have to do any compensation for changing the stack UNLESS you reference a parameter on the stack after changing the stack.  If you call a subroutine within another subroutine, define a new STRUC as follows.


SS$2 STRUC             ;structure for inner subroutine
LOCVAR1 DWORD ?
LOCVAR2 DWORD?
EBXSAV DWORD ?
RETURN DWORD ?
SS$1{}                ;previous defined structure from calling subroutine
SS$2 ENDS


     Then a parameter from the previous subroutine can be reference like MOV EDX,[ESP.SS$2.SS$1.EBXSAVE] .  EQUs can be used to shorten the code phrase.  Ratch
Title: Re: Nested procedures: are they actually supported or not?
Post by: MazeGen on January 20, 2005, 09:10:49 AM
Ratch,

Quote from: Ratch
You COULD do it this way as shown in my example above when using INVOKE.

As far as you use it only in INVOKE, I agree it is usable.

Quote from: Ratch
PUSH EBX
MOV EAX,[S$1.pBuffer+DWORD]
POP EBX

That's why we tend to use only symbolic names - such hardcoding is awkward. Any time you add or remove some of PUSH-POP sequencies, you have to rewrite all references to S$1:


PUSH EBX
PUSH ECX
MOV EAX,[S$1.pBuffer+2*DWORD]
POP ECX
POP EBX


Quote from: Ratch
     But another way would be to define something like EBXSAV DWORD ? within the STRUC. Then code the following.


MOV [$$1.EBXSAV],EBX
MOV EAX,[S$1.pBuffer]        ;compensation not needed here this time
MOV EBX,[$$1.EBXSAV]

Similar. Any time you add or remove some of these substitutive sequencies, you have to rewrite the structure.

That is a big limitation of your method :tdown
I wonder how do you handle extensive procedures - it has to be very complicated.
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 20, 2005, 04:43:36 PM
MazeGen,
QuoteSimilar. Any time you add or remove some of these substitutive sequencies, you have to rewrite the structure.That is a big limitation of your method

     Some might consider it a bother, but it is not a limitation.  You only have to edit the pertinent parts of the structure.  Not a big deal as far as I can see.  Likewise if you choose to compensate references to the stack directly after pushing and popping.   It just doesn't seem very onerous to me.  Not if one is always cognizant of what s/he and the stack are doing, which is what a programmer should be aware of anyway.

QuoteI wonder how do you handle extensive procedures - it has to be very complicated

     Not at all. It is very methodical.  Perhaps I don't understand what you mean by extensive. 

     My method is quite flexible.  For instance, I can SUB ESP,4*DWORD within Sub1 just before calling Sub2.  Sub2 can then fill in those 4 items of data according to its STRUC map and return to Sub1.  So then Sub1 has 4 items of data/params ready to go.  Can PROC-ENDP do that easily?  I don't know.  Ratch

Title: Re: Nested procedures: are they actually supported or not?
Post by: rea on January 20, 2005, 05:27:59 PM
QuoteLikewise if you choose to compensate references to the stack directly after pushing and popping.   It just doesn't seem very onerous to me.

Yes, the programmer should be aware of what is in the stack and the locals, but the assembler is not able to know this :), you have one way, I like the way where I define the locals thinking that there can be not only locals+arguments+retaddr but +fastlocals(push/pop) that need modify a little push/pop or have a way for handle the variable that hold the "compensation" of the fastlocals.

:D.
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 20, 2005, 08:09:35 PM
rea,

     Yes, you could update a stack compensation variable for every PUSH/POP or other stack modification you do.  And one can easily write a MACRO to do it.  No doubt MASM does that for their PROC-ENDP scheme.  But why bother.  It's so easy to count forward and backward for numbers less than 10.  And because I never encountered accumulated PUSHes/POPs greater than ten, I never had to take off my shoes.  I just don't see what keeping a variable does for you except make extra work.  Perhaps an example will enlighten me.  Ratch
     
Title: Re: Nested procedures: are they actually supported or not?
Post by: Randall Hyde on January 20, 2005, 09:05:26 PM
Quote from: Ratch on January 20, 2005, 04:43:36 PM
MazeGen,
QuoteSimilar. Any time you add or remove some of these substitutive sequencies, you have to rewrite the structure.That is a big limitation of your method

     Some might consider it a bother, but it is not a limitation.  You only have to edit the pertinent parts of the structure.  Not a big deal as far as I can see.  Likewise if you choose to compensate references to the stack directly after pushing and popping.   It just doesn't seem very onerous to me.  Not if one is always cognizant of what s/he and the stack are doing, which is what a programmer should be aware of anyway.
Unless, of course, two paths in the code merge at some point and the ESP value is different along both paths.
Doesn't happen very often, but it *does* happen. In such cases, indexing off ESP cannot reference local variables in the common code sequence because ESP's value isn't always the same.

Quote
QuoteI wonder how do you handle extensive procedures - it has to be very complicated

     Not at all. It is very methodical.  Perhaps I don't understand what you mean by extensive. 

     My method is quite flexible.  For instance, I can SUB ESP,4*DWORD within Sub1 just before calling Sub2.  Sub2 can then fill in those 4 items of data according to its STRUC map and return to Sub1.  So then Sub1 has 4 items of data/params ready to go.  Can PROC-ENDP do that easily?  I don't know.  Ratch


Sure proc/endp can do that. Another thing to consider is this -- if you call several routines that use the C calling convention (specifically, the caller is responsible for popping the parameters), it is possible to leave parameters on the stack across several calls and clean them up only upon exiting the procedure making the calls. This saves one instruction per call. Not a lot, but it is a common optimization compilers make and one that assembly programmers should be aware of. While you can adjust your ESP offset values based on what you leave on the stack, this scheme quickly leads to the problem I mention above.

Using ESP as a base pointer register *can* be done in some special cases. But in general, it's very difficult (arguable impossible) to do all indexing off ESP.

Another issue alluded to in this thread: accessing local variables in a (statically nested) caller's activation record. The examples I've seen presented in this thread fall completely apart when you have recursion. People need to keep in mind the difference between static nesting and lexical nesting. Unless you have static links or a display, it's very difficult to access objects that are not local to the currently running procedure, but are local to other procedures (that have directly or indirectly called the current procedure).
Cheers,
Randy Hyde
Title: Re: Nested procedures: are they actually supported or not?
Post by: Ratch on January 20, 2005, 10:15:09 PM
Randall Hyde,
     Very interesting points you make.  I think I can come up with some solutions.  Rather than try to hypothesize on how I will solve those problems, I will just say that I will TRY to overcome them when they occur for me.  If someone can post a small example that demonstrates the problem, I will be more than happy to take a crack at solving it.  Ratch
Title: Re: Nested procedures: are they actually supported or not?
Post by: rea on January 21, 2005, 06:41:25 AM
mmm, yes, I was refering to some like C can do, also I where thinking that "I can " call fastLocals the ones that you do for preserve a register, they are still local to the proc, but in some way there are not a part of the proc, but a part of preserve the value... anyway.


For example:

x:
sub esp, 24

; some names for locals where defined like x esp+8, v1 esp,... by the way
%define x esp+8
; from this part I can access locals and arguments
add dword[x], 5
push eax
push ebx
invoke calculationT ; it destroy those registers
add [x], eax ; *
invoke calculationZ, dword[x]
·
·
·
pop ebx
pop eax
·
·
·


*this isnt right, even that calculationT dosent have C calling convention, I will not pop the values for access my local ;), altought I can do some like add [x+8], eax and instead of do that, is pheraphs more easy take a extra variable that handle that in the fly, also it need modificate push and pop for provide a little more control....



QuoteUnless, of course, two paths in the code merge at some point and the ESP value is different along both paths.
Doesn't happen very often, but it *does* happen.

You are refering for example to blocks?, I think it will more easy, with the same technique.... but I dont know if what Im thinking is the same that you have in mind...... Im a little aware that sometimes a local is not necesary for a function only if certain path is followed, I dont know if when this path is reached that the space for the nested locals is reached or is only a "semantic" behaviour, for example:

(it is a raw example, in fact isnt implementing nothing, but show the point :))

xthing:
sub esp, 12
sub [esp], eax
jnz .end
add [esp+4], ecx ; **
sub [esp+8], ecx ; also **
; make more operatios with this ones
mov ecx, [esp+4]
add [esp], ecx
.end:
·
·
·

** are say local variables they will be necesary in remote case that such path can be reached....

But supose that this function is recursive and that you have a buffer (only used when the stop condition is reached), then you will allocate for each call supose 512 bytes, then if in some case that your functions being called 10 times you will have allocated in the stack 5120 bytes, and the thing is that the buffer is only used one time.... :).


QuoteIn such cases, indexing off ESP cannot reference local variables in the common code sequence because ESP's value isn't always the same.

You are refering some thing like the above, but in the case that is more than one path and diferents paths make a substraction of the esp for his own locals????? for example, one path substract 12, other 20, 36, 48, then if you dosent aling before get out of this path, sure it will be very dificult to know what is the align or extra compensation for this "fastlocals"....

It is not posible to calculate in the paths that is posible and balance in the cases that is not automaticaly posible???

For example is easy when you have paths that are consecutive.... a=12, b= 20, c= 36, d= 48, you only add them...
but the dificult is when you have more than one path to follow: b follow a, there is a choice for path a or c, and d is after any of them. ( (a+b) Xor (c) ) => d, after the execution how many are (before execute the path d)??? 32 or 36? :), the only way that I see for solve this is realign the stack again for have at the end a zero path :) and in that way will not matter if the first or the second path (some one will like to say me how many impact is there for align the stack in those cases)....


I think I have in mind some things (is the only solution that I have in mind from some time a go ...), but best is to know if you have a example to take over ;) or otehr solution ;) (if I have understood correctly or the anterior is uncorrect?), in my case I have only finded other problem and is: when you allocate space for locals and use the locals in that way and then you align esp!!!, in fact you are not sure where the return address is and keep the track of where the ret address is important in that case I supose you are forced also to use ebp, but that will be more a fault of the programmer (I gues?)


The majority of the index I think can be done with esp, you only need to take care more of what is happening :D, because esp is the one changing (and should be taked in count when is changed), not like ebp (this one is really more easy) ;).