flat assembler
Message board for the users of flat assembler.

flat assembler > Main > Use many function parameters

Goto page 1, 2  Next
Author
Thread Post new topic Reply to topic
Mino



Joined: 14 Jan 2018
Posts: 156
Good evening.
I would like to know how to have a function with many parameters. Let's imagine this in C :
Code:
int foo(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l) {
    
}
    

Okay, it's stupid, but it's for example.
So how do you implement this in FASM? Because, of the registers, one does not have an infinity of them...
Any ideas Very Happy?

_________________
The best way to predict the future is to invent it.
Post 01 May 2018, 20:57
View user's profile Send private message Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 367
Location: Belarus
Mino wrote:
Good evening.
I would like to know how to have a function with many parameters. Let's imagine this in C :
Code:
int foo(int a, int b, int c, int d, int e, int f, int g, int h, int i, int j, int k, int l) {
    
}
    

Okay, it's stupid, but it's for example.
So how do you implement this in FASM? Because, of the registers, one does not have an infinity of them...
Any ideas Very Happy?

Code:
proc foo c\
     a, b, c, d, e, f, g, h, i, j, k, l
     ...
endp    
Post 01 May 2018, 21:01
View user's profile Send private message Visit poster's website Reply with quote
vivik



Joined: 29 Oct 2016
Posts: 425
When you don't have enough registers, you use stack. Push pop.
Post 02 May 2018, 08:05
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 15982
Location: Qo'noS
vivik wrote:
When you don't have enough registers, you use stack. Push pop.
That will depend upon the calling convention in use. For stdcall there are no registers used, it is all on the stack. The same for ccall. For MS x86-64 fastcall only the first four parameters are in registers, the rest are on the stack.
Post 02 May 2018, 09:19
View user's profile Send private message Visit poster's website Reply with quote
vivik



Joined: 29 Oct 2016
Posts: 425
Assembly coders have more freedom over what calling convention to use. C have only a small set of them. It's not like it matters that much, who's gonna notice.

Library functions have calling conventions set in stone during compilation, but you can use whatever you want in your code. As long as caller and callee agree on what calling convention to use.
Post 02 May 2018, 11:56
View user's profile Send private message Reply with quote
Mino



Joined: 14 Jan 2018
Posts: 156
Ha, okay, I get it Smile

But could you elaborate a little on the "proc", to see to what extent it is possible to deploy this method. Thanks Smile
Post 02 May 2018, 18:19
View user's profile Send private message Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 1227
Mino wrote:
Ha, okay, I get it Smile

But could you elaborate a little on the "proc", to see to what extent it is possible to deploy this method. Thanks Smile
That's a macro. The actual "assembly" answer (i.e. using actual instructions) is to simply push them on the stack.

The stack is a memory region that grows down (so when you push something, the stack pointer gets decremented and puts the value at that new location). The stack pointer is the esp register (rsp in x64).

For example:
Code:
; calling function with all parameters on the stack
; note parameters pushed in reverse order since stack grows down
push l
push k
push j
push i
push h
push g
push f
push e
push d
push c
push b
push a
call foo


; in foo, access parameters from esp if you want (each is 4 bytes here)
; the location at esp+0  (i.e. [esp]) is the return ADDRESS of the function
; [esp+4] skips that and accesses a, [esp+8] accesses b, and so on
foo:
mov eax, [esp+4*3]  ; accesses c, 3rd parameter, puts it in eax

; at end of function, you should "clean" all the parameters (restore stack pointer above them) so that the caller doesn't have to (this is stdcall)
ret 4*12  ; because you have 12 parameters, this simply returns and then ADDS 4*12 to esp (moves it up, which is "back")    


I figured this is important since you said you want to write a (toy?) compiler Razz

You can put some parameters in registers and use your own custom calling convention if you want, but make sure the function and caller agree with each other (e.g. if you put first parameter into eax, then you'd better assume eax is first parameter inside the function also).
Post 02 May 2018, 20:01
View user's profile Send private message Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 367
Location: Belarus
Furs wrote:
Code:
; in foo, access parameters from esp if you want (each is 4 bytes here)
; the location at esp+0  (i.e. [esp]) is the return ADDRESS of the function
; [esp+4] skips that and accesses a, [esp+8] accesses b, and so on
foo:
mov eax, [esp+4*3]  ; accesses c, 3rd parameter, puts it in eax

; at end of function, you should "clean" all the parameters (restore stack pointer above them) so that the caller doesn't have to (this is stdcall)
ret 4*12  ; because you have 12 parameters, this simply returns and then ADDS 4*12 to esp (moves it up, which is "back")    

A little correction here. It is generally not practical to use ESP-based addressing within a stack frame when writing the code manually or by means of a simple code generator within a compiler. Any push/pop changes the value of ESP, so in order to access the right place one will have to recalculate the constant offset for every place such access is taken. Which makes it nearly impossible to combine distinct branches of a conditional statement (in HLL terms) having common parts in them into a single piece of code.

In most cases EBP-based addressing is the way to go. Especially if you’re eager to use SEH or just have easily traceable stack.
Post 02 May 2018, 22:08
View user's profile Send private message Visit poster's website Reply with quote
bitRAKE



Joined: 21 Jul 2003
Posts: 2653
Location: dank orb
Another option that almost no one uses is to pass a single pointer to an array of arguments. I like the ENTER instruction because it also can copy part of the previous stack frame - almost no one uses that anymore either.

_________________
utf8everywhere.org


Last edited by bitRAKE on 03 May 2018, 09:20; edited 1 time in total
Post 03 May 2018, 08:13
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar
Assembly Artist


Joined: 16 Jun 2003
Posts: 6928
Location: Kraków, Poland
bitRAKE wrote:
Another option that almost no one uses is to pass a single pointer to an array of arguments. I like the ENTER instruction because it also can copy part of the previous stack frame - almost no one uses that anymore either.
I think this may be a side-effect of everyone switching to a faster sequences like PUSH EBP / MOV EBP,ESP / SUB ESP,X in place of ENTER X,0. This led to a perception of ENTER as bloated and deprecated so we stopped thinking about it and forgot that it does in fact have a second parameter.
Post 03 May 2018, 08:32
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 15982
Location: Qo'noS
Tomasz Grysztar wrote:
This led to a perception of ENTER as bloated ...
The thought of ENTER being bloated is weird. It is shorter than the PUSH/MOV/SUB sequence.
Post 03 May 2018, 09:04
View user's profile Send private message Visit poster's website Reply with quote
vivik



Joined: 29 Oct 2016
Posts: 425
What exactly ENTER X,1 does?
Post 03 May 2018, 12:21
View user's profile Send private message Reply with quote
bitRAKE



Joined: 21 Jul 2003
Posts: 2653
Location: dank orb
Code:
MACRO ENTER num,k
  LOCAL level,temp
  level = k MOD 32
  push rbp
  mov [temp],rsp    ; temp is internal to the CPU Wink
  REPEAT level - 1  ; skip for K = 0,1
    lea rbp,[rbp-8] ; don't effect flags
    push qword [rbp]
  END REPEAT
  IF level > 0
    push [temp]
  END IF
  mov rbp,[temp]
  sub rsp,num
END MACRO    
RBP doesn't need to be on the stack - could be any memory buffer. The first parameter can be zero.

_________________
utf8everywhere.org
Post 03 May 2018, 15:01
View user's profile Send private message Visit poster's website Reply with quote
Mino



Joined: 14 Jan 2018
Posts: 156
Thank you very much for your answers Smile I think I can find solutions adapted to my needs with all this Wink

Quote:

I figured this is important since you said you want to write a (toy?) compiler :p

Yes, indeed, it already gives me a wider range of possibilities than I expected!
PS: The compiler will not be "toy", at least I hope so Very Happy The language I have created is quite complex to compile in fact... But one thing at a time :p

_________________
The best way to predict the future is to invent it.
Post 03 May 2018, 15:26
View user's profile Send private message Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 1227
bitRAKE wrote:
Code:
MACRO ENTER num,k
  LOCAL level,temp
  level = k MOD 32
  push rbp
  mov [temp],rsp    ; temp is internal to the CPU Wink
  REPEAT level - 1  ; skip for K = 0,1
    lea rbp,[rbp-8] ; don't effect flags
    push qword [rbp]
  END REPEAT
  IF level > 0
    push [temp]
  END IF
  mov rbp,[temp]
  sub rsp,num
END MACRO    
RBP doesn't need to be on the stack - could be any memory buffer. The first parameter can be zero.
So basically it copies an array of frame pointers and pushes it to the current frame, then appends the "current" frame to that array?

Seems kinda wasteful just to be able to access the frame of another function -- but it's not like compilers are any better, even with the "nested functions" extension, GCC does similar wasteful shit, since the compiler developers are too lazy to put some nested function optimizations for 10 years or more. "Compilers can optimize better than humans" is preached on stackoverflow, yeah right, maybe in theory with actual non-lazy compiler devs. Rolling Eyes

I use ENTER x,0 when optimizing for size though (if it's better). Wink
Post 03 May 2018, 20:03
View user's profile Send private message Reply with quote
bitRAKE



Joined: 21 Jul 2003
Posts: 2653
Location: dank orb
People are afraid to use RSP in a general manner, but on modern OS's there is no such requirement within one's own code. Of course, external code has requirements.

For example, on Win64 we might:
Code:
; stack aligned MOD 16
    lea rbp,[_APIs] ; no alignment required on this buffer

    enter 0,4*3
    pop rbp
    pop rcx rdx r8 r9
    retn

    dq 0 ; hWnd
    dq lpText
    dq lpCaption
    dq 0 ; uType
    dq _jmp__MessageBoxW
    dq _continue
    rq 5
_APIs:

lpText du "(K)not in a tree.",0
lpCaption du "What?",0

_jmp__MessageBoxW: jmp [user32.MessageBoxW]

_continue:
    leave ; not needed we know where RBP/RSP is    
...a completely data driven API call. It's a little cryptic to follow the code, but if one wants to size optimize. Wink

It wouldn't be surprising if you were to wonder how this is smaller. Two factors make it smaller: same code for multiple API calls, and the separation of code and data makes it compress better.

32-bit Windows, that data is on the stack - just POP EBP and RETN.

API thunk is just used for illustrative purposes.

Changes made to the API buffer prior to the ENTER instruction are persistent across calls. Whereas those afterward are discarded on stack cleanup (LEAVE).
Post 04 May 2018, 03:41
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar
Assembly Artist


Joined: 16 Jun 2003
Posts: 6928
Location: Kraków, Poland
Nice trick, I never really thought of possibility to use ENTER just to move an arbitrarily sized block of data onto the stack. And now I realized it could actually be a nice method of creating initialized local variables. With fasm[g]'s "proc" macros if you define a local variable as having initial value, an instruction like MOV [EBP-X],Y is generated to ensure that it works as one might expect. The alternative could be to put the initial values into data segment and then use ENTER to copy the values to the stack. Interesting. Smile
Post 04 May 2018, 10:10
View user's profile Send private message Visit poster's website Reply with quote
rugxulo



Joined: 09 Aug 2005
Posts: 2302
Location: Usono (aka, USA)
Tomasz Grysztar wrote:
bitRAKE wrote:
I like the ENTER instruction because it also can copy part of the previous stack frame - almost no one uses that anymore either.
I think this may be a side-effect of everyone switching to a faster sequences like PUSH EBP / MOV EBP,ESP / SUB ESP,X in place of ENTER X,0. This led to a perception of ENTER as bloated and deprecated so we stopped thinking about it and forgot that it does in fact have a second parameter.


It's really only used for languages with nested functions (e.g. Pascal, Modula-2, Oberon). C-based languages usually don't support that (or only as non-standard extension). So it's less useful for them.

And yes, it is slower, quite noticeably so, but only if you loop over it a million times. But maybe newest cpus aren't as bad about it? (It actually used to be faster, allegedly!)

revolution wrote:

The thought of ENTER being bloated is weird. It is shorter than the PUSH/MOV/SUB sequence.


Only barely.

The 186 was still limited to 20-bit addresses (1 MB of RAM, not counting hardware EMS or IBM PC UMBs beyond 640 kb), and most people didn't have the full megabyte since it cost literally thousands of dollars. (Actually most old machines didn't even support the full meg, usually capping out at 256 kb or 512 kb, but the cpu itself did support the full 1048576 bytes.)

My point is that it saves a few bytes in very limited environments, but obviously that advantage disappeared with larger (386+, 32-bit) code and bloatier software libraries. So nobody cared as much beyond the 486 or such. What good is saving 1 kb on a machine with 8 MB total? Heck, just for extra code+data alignment used to pacify the quirky 486, you're wasting tons more space.

So it's long been moot.


Last edited by rugxulo on 06 May 2018, 02:33; edited 1 time in total
Post 05 May 2018, 00:54
View user's profile Send private message Visit poster's website Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 1227
rugxulo wrote:
Only barely.
Not if the stack frame is larger than 128 bytes:
Code:
push ebp
mov ebp, esp
sub esp, 1024    
9 bytes versus 4 for enter 1024,0. On x64 code is even more so, 11 bytes vs 4, almost 3 times less space.

7 bytes is barely "barely", it's a huge amount of code.

rugxulo wrote:
My point is that it saves a few bytes in very limited environments
It always saves those few bytes (unless alignment wastes it, ofc assuming you use it on many functions not just 1). Doesn't matter the total amount of memory, it's a measurable thing, which is always saved. Razz
Post 05 May 2018, 12:04
View user's profile Send private message Reply with quote
bitRAKE



Joined: 21 Jul 2003
Posts: 2653
Location: dank orb
It is true we went from KB (I worked testing RAM chips for a small system integrator in my teens) to MB to GB of memory on our local machines, but today the user base and update frequency play a larger role in data size - single characters on the highest volume web pages have enormous costs in time and money.
Post 05 May 2018, 14:48
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  
Goto page 1, 2  Next

< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2018, Tomasz Grysztar.

Powered by rwasa.