News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Useful technique? "internal call"

Started by NoCforMe, April 10, 2025, 02:01:21 PM

Previous topic - Next topic

NoCforMe

I'm playing with something that might come in handy in a project I'm currently working on.

Say you're inside a subroutine (PROC), and there's something you need to do repeatedly, like, say, in a loop. That thing is complex enough that you'd rather not put it inline in the loop code; but the thought of having to create a separate PROC and do all the setup to call it is a little distressing.

So it occurred to me: why not put that second PROC inside the first one, and just CALL it? So first I did a little test to see if it would even work. Well, it does.

So here's the basic idea:
OuterProc PROC
LOCAL var1:DWORD, var2:DWORD, var3:DWORD

MOV var1, 0
MOV var2, 1
MOV var3, -1

MOV EAX, 10
MOV ECX, 20
MOV EDX, 30
CALL InnerProc
. . . . .
RET

; InnerProc uses var1-var3, EAX, ECX & EDX:
InnerProc LABEL NEAR

; do a bunch of stuff here ...

;Return to caller within OuterProc():
RET

OuterProc ENDP

The RET after InnerProc returns to the CALL near the top; that code can be called any number of times.

And the beauty part is that in addition to using registers to pass values, you can use all your local variables as well--without having to push them on the stack. That ought to even save a few microseconds per invocation.

Now I'm sure that the structured-programming evangelists would be horrified by this. "Worse than GOTO!" But it looks good to me; I'll know more after I actually try to implement it.
Assembly language programming should be fun. That's why I do it.

sinsi

Pretty sure you could replace InnerProc   LABEL NEAR with InnerProc:

NoCforMe

Yes, indeedy, you can. Just wanted to make it a bit more formal.
Probly better to do it your way, since the LABEL will be visible outside of the proc, and you could get into interesting trouble trying to call it from outside.
Assembly language programming should be fun. That's why I do it.

sinsi

#3
Guess what will happen if OuterProc has arguments?
Nothing good :biggrin:

edit: wrong, it's the epilogue (see later)

NoCforMe

Quote from: sinsi on April 10, 2025, 03:47:17 PMGuess what will happen if OuterProc has arguments?
Nothing good :biggrin:
I don't think so.
This runs just fine (console app):
;============================================
; -- InternalCallTest --
;
;
; Testbed to see if we can make a call to some
; code within a PROC successfully.
;============================================

.nolist
include \masm32\include\masm32rt.inc
.list

;============================================
; Defines, structures, prototypes, etc.
;============================================

TestProc PROTO arg1:DWORD, arg2:DWORD

$CRLF EQU <13, 10>
$tab EQU 9

;============================================
; HERE BE DATA
;============================================
.data

Var1Fmt DB "var1: 0x%X", $CRLF, 0
Var2Fmt DB "var2: %d", $CRLF, 0
Var3Fmt DB "var3: %d", $CRLF, 0

InProcMsg DB $CRLF, "Now inside internal call:", $CRLF, 0

;============================================
; CODE LIVES HERE
;============================================
.code

start: CALL WinMain
INVOKE ExitProcess, EAX

;====================================================================
; Mainline proc
;====================================================================

WinMain PROC

MOV EAX, 0BADF00Dh
MOV EDX, -1
INVOKE TestProc, EAX, EDX
RET

WinMain ENDP

TestProc PROC arg1:DWORD, arg2:DWORD

LOCAL var1:DWORD, var2:DWORD, var3:DWORD
LOCAL buffer[64]:BYTE

MOV var1, EAX
MOV var2, 0
MOV var3, EDX

INVOKE wsprintf, ADDR buffer, OFFSET Var1Fmt, var1
INVOKE StdOut, ADDR buffer
INVOKE wsprintf, ADDR buffer, OFFSET Var2Fmt, var2
INVOKE StdOut, ADDR buffer
INVOKE wsprintf, ADDR buffer, OFFSET Var3Fmt, var3
INVOKE StdOut, ADDR buffer

CALL InternalCall
RET

InternalCall:

INVOKE StdOut, OFFSET InProcMsg
INVOKE wsprintf, ADDR buffer, OFFSET Var1Fmt, var1
INVOKE StdOut, ADDR buffer
INVOKE wsprintf, ADDR buffer, OFFSET Var2Fmt, var2
INVOKE StdOut, ADDR buffer
INVOKE wsprintf, ADDR buffer, OFFSET Var3Fmt, var3
INVOKE StdOut, ADDR buffer
RET

TestProc ENDP

END start
Assembly language programming should be fun. That's why I do it.

sinsi

Add this code after CALL InternalCall and before the RET
print "Do you see this?"

NoCforMe

Hmm.
Actually makes no difference whether the outer proc takes parameters or not;
the RET in the internal call returns from the outer proc, so that never gets printed.

Why? Seems like it should work:
CALL pushes the current IP on the stack and jumps to the target.
The RET pops that return address, which should be to the instruction after the call, right?
So what's going on here?

Hmm[2]: PROLOG? EPILOG? izzat the problem here?
Assembly language programming should be fun. That's why I do it.

sinsi

The second RET triggers the epilog which uses leave
;prologue
  push ebp
  mov ebp,esp
;epilogue
;leave basically does this
  mov esp,ebp
  pop ebp
;ESP now points to the original call's return address
  ret nn
That's why the print doesn't show.

NoCforMe

So you could disable prolog and epilog.
But then you'd have to use [EBP + xxxx] addressing for any locals ...
Assembly language programming should be fun. That's why I do it.

sinsi

I could see it as a possibility using it in WndProc maybe...
I've had too many years of the rule "one exit point only".

daydreamer

I had similar idea,based on oop first you write for example  class car and subclass truck is based on car ,but adds specific code handling truck

Process car
...car code here
...that also car has in common with truck
Conditional ret ,based on a truck flag is false
...truck code here
...that is unique for truck ,extra
Ret

At the same time, it satisfy asm coders want to code small,by replacing two proc's
my none asm creations
https://masm32.com/board/index.php?topic=6937.msg74303#msg74303
I am an Invoker
"An Invoker is a mage who specializes in the manipulation of raw and elemental energies."
Like SIMD coding

jj2007

The MasmBasic source uses this technique over 50 times. One important thing to remember: never use ret to end such an internal proc. Use retn instead.

NoCforMe

Aha; retn; I'll have to give that a try. Never used that instruction.

This fixes things:
InternalCall:
INVOKE StdOut, OFFSET InProcMsg
INVOKE wsprintf, ADDR buffer, OFFSET Var1Fmt, var1
INVOKE StdOut, ADDR buffer
INVOKE wsprintf, ADDR buffer, OFFSET Var2Fmt, var2
INVOKE StdOut, ADDR buffer
INVOKE wsprintf, ADDR buffer, OFFSET Var3Fmt, var3
INVOKE StdOut, ADDR buffer

OPTION EPILOGUE:NONE
RET

OPTION EPILOGUE:EPILOGUEDEF

TestProc ENDP
Just have to remember to reset OPTION EPILOGUE after all the "internal" RETs.
Assembly language programming should be fun. That's why I do it.

zedd151

That's what I had done once or twice...

OPTION EPILOGUE:NONE etc., when confronted with a similar issue in the past.  :biggrin:

And with "ret" btw, not "retn"

I don't write funny code like that anymore though. Just buggy code.  :joking:

NoCforMe

Quote from: jj2007 on April 10, 2025, 11:35:02 PMOne important thing to remember: never use ret to end such an internal proc. Use retn instead.
Wait wait wait: I take back what I said.
Why would you use RET n instead of just plain RET?
You're not popping any parameters off the stack, just returning to where you made the CALL.
Unless you're doing some kind of INVOKE there ...
My testbed that just used RET worked fine.
Assembly language programming should be fun. That's why I do it.