Nested procedures: are they actually supported or not?

Started by MazeGen, January 09, 2005, 08:45:45 PM

Previous topic - Next topic

MazeGen

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 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?

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MazeGen

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]

hutch--

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
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MazeGen

I've searched for it everywhere and I've just found this 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  :'(

MichaelW

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.
eschew obfuscation

donkey

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
"Ahhh, what an awful dream. Ones and zeroes everywhere...[shudder] and I thought I saw a two." -- Bender
"It was just a dream, Bender. There's no such thing as two". -- Fry
-- Futurama

Donkey's Stable

sluggy

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....

MazeGen

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.

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

MazeGen

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.

Ratch

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

hutch--

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.
Download site for MASM32      New MASM Forum
https://masm32.com          https://masm32.com/board/index.php

rea

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.

MazeGen

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.