flat assembler
Message board for the users of flat assembler.

Index > Main > Calling Conventions

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



Joined: 18 Feb 2018
Posts: 59
moveax41h 18 Feb 2018, 03:45
Are calling conventions required in actual assembly programs or are they just created for the compiler's sake for higher level languages?

I find that when I program assembly programs, while they are also very simple, there is no need to "set up" and "tear down" the stack, and I just use labels to define given routines... Is this just because my programs have been so trivial?

Do you have any other suggestions for getting away from "compiler-written assembly" and delving into human-written assembly more?

_________________
-moveax41h
Post 18 Feb 2018, 03:45
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20430
Location: In your JS exploiting you and your system
revolution 18 Feb 2018, 04:39
Calling conventions are just that "conventions". You can do whatever you want in assembly.

But certain things will make your code easier to read and understand when you come back six months later to work on it again. Using some form of standard calling convention will help a lot, and be far less confusing. You don't have to use it always, but it is nice not to have to think through each and every function call and wonder what it needs to run correctly.

If you have 1000 functions and 1000 different ways that each is called your mind will explode with the extra cognitive load. Razz
Post 18 Feb 2018, 04:39
View user's profile Send private message Visit poster's website Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 1228
Location: Belarus
DimonSoft 18 Feb 2018, 08:33
moveax41h wrote:
Are calling conventions required in actual assembly programs or are they just created for the compiler's sake for higher level languages?

Just to add a little bit to the discussion.

Following calling conventions (like most conventions) is required only in cases when your code interacts with code written by someone else, i.e. you can do anything you wish until you’re at the border.

Nevertheless, like revolution said, it is usually a good idea to pick a convention and follow it throughout your code to avoid confusing yourself. The second reason to stick yourself with some convention is that certain requirements sometimes have quite strong impact on your code. For example, in 64-bit Windows programming you’re required to ensure stack alignment when calling into a Windows function, as well as alignment of most data being passed to it. You’re not really required to align literally everything in your own program, but enforcing such alignment for all your data makes it easier to pass it anywhere and to avoid spontaneous runtime errors (some functions do fail on misaligned data, some do not).
Post 18 Feb 2018, 08:33
View user's profile Send private message Visit poster's website Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 2545
Furs 18 Feb 2018, 13:58
revolution wrote:
If you have 1000 functions and 1000 different ways that each is called your mind will explode with the extra cognitive load. Razz
I disagree.

You already have to know what parameters a function takes, what types, and what they do, etc. So need to look up its documentation anyway, which is a good thing. A simple additional information stating which register or stack slot the parameter goes into, is not going to explode your brain.

But of course when you call someone else's code (e.g. API or library) that uses a certain convention, you have to follow it when calling that function.
Post 18 Feb 2018, 13:58
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20430
Location: In your JS exploiting you and your system
revolution 18 Feb 2018, 14:14
Furs wrote:
So need to look up its documentation anyway ...
What is this strange thing you talk about called "documentation"? Smile
Post 18 Feb 2018, 14:14
View user's profile Send private message Visit poster's website Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 2545
Furs 18 Feb 2018, 16:29
I mean, even comments above the function etc Wink That describes the basics of what the parameters etc do and what their types represent and so on.
Post 18 Feb 2018, 16:29
View user's profile Send private message Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 1228
Location: Belarus
DimonSoft 20 Feb 2018, 05:29
Furs wrote:
I mean, even comments above the function etc Wink That describes the basics of what the parameters etc do and what their types represent and so on.

Nevertheless, if you have each of your procedures follow different conventions, you’re more likely to make write-only code, i.e. to have problems supporting it in the future. Using the same convention for most procedures comes with certain habits and makes one’s brain see calling convention-related bugs immediately at a lower cognitive price.

Having different conventions for every procedure generally has more cons than pros. First of all, come on, if your code needs comments describing implementation details, then it can be improved. Only comments explaining the mapping between business logic and its implementation are worth leaving in the code.

The second point is that using custom calling conventions (which usually means passing parameters in registers, at least partially) strikes a programmer back as soon as the procedure becomes called from more than two places. Having procedures with different register usage requirements means there will always be a place where their usage becomes too inefficient due to the need to perform redundant exchanges. Also, fastcall is not really considered fast for modern hardware ’cause the cost of keeping certain values in certain registers often makes the code inefficient enough to be just as good/bad as using stack.
Post 20 Feb 2018, 05:29
View user's profile Send private message Visit poster's website Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 2545
Furs 20 Feb 2018, 13:06
What do you mean with implementation detail? You only describe the inputs to the function (or outputs). Say this generic code (doesn't really explain but you get it):
Code:
; Does some stuff with the object. Parameters:
;  obj:   pointer to the object
;  scale: some fixed point scale
SomeFunction:    
versus:
Code:
; Does some stuff with the object. Parameters:
;  [rax] obj:   pointer to the object
;  [edx] scale: some fixed point scale
SomeFunction:    
Post 20 Feb 2018, 13:06
View user's profile Send private message Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20430
Location: In your JS exploiting you and your system
revolution 20 Feb 2018, 13:53
I think the "first parameter = ...", "second parameter = ..." paradigm is more flexible. You are not committing yourself to any particular register or stack position. You can use common macros to change the desired convention without having to change all the comments describing the input/output values. You can even try different conventions by simply changing the macros and see which one works best with the code.
Post 20 Feb 2018, 13:53
View user's profile Send private message Visit poster's website Reply with quote
rugxulo



Joined: 09 Aug 2005
Posts: 2341
Location: Usono (aka, USA)
rugxulo 20 Feb 2018, 21:03
moveax41h wrote:
Are calling conventions required in actual assembly programs or are they just created for the compiler's sake for higher level languages?


Static compilers are quite limited while dynamic humans are much smarter! Razz So compilers have to live within their own rules. Humans are less limited by design and can do any obscure trick that works (including improving compilers!).

As resident expert revolution says, compilers are "brain dead"! But they perform a useful function to most people. The old adage "compilers know better" isn't absolutely true (somebody has to write the compiler!). If the compiler was always worse, nobody would write or use it in the first place. (Obligatory link to Niklaus Wirth's Compiler Construction e-book online.)

Honestly, I'd suggest not worrying about the minor details unless you're sure you need it. Or maybe you're just curious, dunno. My point is that raw computing speed is less important than you think. It's cliche, but truly, algorithms are also very important, maybe moreso than low-level implementation details.

Quote:

I find that when I program assembly programs, while they are also very simple, there is no need to "set up" and "tear down" the stack, and I just use labels to define given routines... Is this just because my programs have been so trivial?


Recursive functions, nested functions, and temporary local (stack) data are the main reasons for stack frames. No, you don't absolutely need it if everything is global, thus you don't need to preserve local data across function calls.

With enough registers, or when using register calling convention, sometimes you can avoid stack frames for simple cases. Or maybe your compiler is smart enough to do it anyways when not truly needed.

FPC Programmer's Guide, 11.1.12, Stack frame omission wrote:

Under specific conditions, the stack frame (entry and exit code for the routine, see section section 6.3, page 396) will be omitted, and the variable will directly be accessed via the stack pointer.

Conditions for omission of the stack frame:

* The target CPU is x86 or ARM.
* The -O2 or -OoSTACKFRAME command line switch must be specified.
* No inline assembler is used.
* No exceptions are used.
* No routines are called with outgoing parameters on the stack.
* The function has no parameters.


FYI, Delphi and FPC (since 2.0?) both use "register" for most targets. i8086-msdos still uses "pascal", though. So yes, register is usually faster, AFAIK.

Quote:

Do you have any other suggestions for getting away from "compiler-written assembly" and delving into human-written assembly more?


Honestly, I don't recommend it, but you can always study the compiler's assembly output, if you think it'll help you learn (or if you can improve it). Don't forget that you can usually cooperate with the compiler (inline asm) if you need manual optimizations.

EDIT: Or just study some pure asm projects, e.g. MenuetOS/32, FASM, OctaOS, etc. But that's fairly obvious advice, if you really want to create stand-alone apps from scratch.
Post 20 Feb 2018, 21:03
View user's profile Send private message Visit poster's website Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 1228
Location: Belarus
DimonSoft 20 Feb 2018, 21:15
Furs wrote:
What do you mean with implementation detail? You only describe the inputs to the function (or outputs). Say this generic code (doesn't really explain but you get it):
Code:
; Does some stuff with the object. Parameters:
;  obj:   pointer to the object
;  scale: some fixed point scale
SomeFunction:    
versus:
Code:
; Does some stuff with the object. Parameters:
;  [rax] obj:   pointer to the object
;  [edx] scale: some fixed point scale
SomeFunction:    

It’s not a problem if you use registers/stack to pass parameters in a consistent way throughout your code. But then you’re actually using a particular calling convention.

If you assign different registers/stack locations in different situations, THAT is a problem. When you write the procedure, you choose RAX and EDX, presumably because it makes the code smaller/faster/easier to read/write. Maybe it is even convenient to call such a procedure from the place you call it now. But then you have another place where you should call the procedure, and suddenly it turns out that some of the registers hold useful values and the need to call the procedure makes you add redundant exchanges/push-pop pairs, stuff like that.

What you gain? Maybe a few ticks in cases when the caller doesn’t have to do anything special to play the rules of the procedure. What do you lose? (1) Tens of ticks in cases when the rules of the procedure are not comfortable for the caller. (2) Ability to write recursive procedures (you’ll need stack anyway, so no reason to play tricks with arbitrarily chosen registers). (3) Code maintainability: you now have to double-check that your comments tell you the truth about your code, you now have different rules throughout your program. Even more: you’re now in big trouble if significant change to the procedure internals (implementation) makes your initial choice of registers bad. Now you have to walk through all the code, find all the calls, change the calling code, test (!) it again… Too much for a few ticks saved that you might not even notice due to all the cool stuff modern processors do to make plain compiler-generated code with stack-based calling conventions run as fast as super-optimized custom code with bells and whistles.
Post 20 Feb 2018, 21:15
View user's profile Send private message Visit poster's website Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 2545
Furs 21 Feb 2018, 17:59
Uh, you only really need to care about the return value registers, because registers you "save" in the function (non-clobbered regs) can't be parameters anyway (I mean not much point in saving them then).

The only thing that does matter is situations like one of the following:

  • if you chain functions, and usually one of the parameters is result of another function, use same register as the output of that function (the "standard" calling conventions do not do this and thus are piss-poor designed, e.g. they return in eax/rax, but don't take eax as parameter -> register shuffling, stupid design)
  • if you use special-purpose registers in specific instructions, so it depends on the instruction/function and micro-optimizations here


The standard calling conventions are horrible in respect to the kind of "code quality" you speak of, yet they are consistent, so clearly your reasoning doesn't really hold. Consistency doesn't mean good code. Except for the GCC-specific 32-bit register parameter passing convention (parameters in eax, ecx, edx), all the others do not make use of eax/rax when it is the return value which means you will end up with a lot of register shuffling around.

Furthermore they don't take parameters of non-clobbered registers so all registers that could have had other values are trashed anyway and you'll have to "reload" them between functions. If you're worried that the code uses specific registers that don't correspond to those of the function, well, then decide which functions to call first and lookup their registers before writing the (local) piece of code Wink


EDIT: Let me put it another way. If you have a function A, which takes parameter 'edx' and doesn't touch 'ebx' and 'ebp' (just example, those are non-call clobbered, i.e. it saves them if it uses them), and a function B which has the same calling convention, you won't be able to make much optimizations between them in respect to register usage.

ebx and ebp are not touched but they can't be parameters, since both use the same calling convention for "consistency". 'edx' is a parameter but at the end of function A, it is trashed, thus you will have to reload it anyway (i.e. register moves). So in this case consistency enables literally zero ability to reuse a register across functions without reloading the parameter for the next call, because they both use the same calling convention.

If you had a variable computed before function A that you wanted to pass to function B, you'd have to place it in ebx or ebp -- then reload it into edx before calling B. A useless "mov edx, ebp" or whatever, and all this because of consistency.

To fix this, function B would need to take ebx or ebp as parameters, so they could be loaded only once before function A (if that's needed). Then no reload is needed. Basically it needs to take registers that A does not touch.

Which obviously means a different calling convention: parameter registers are usually touched (or always?) so thus its parameter registers must be different from A's to get rid of the reload.
Post 21 Feb 2018, 17:59
View user's profile Send private message Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 1228
Location: Belarus
DimonSoft 21 Feb 2018, 19:13
Furs wrote:
  • if you chain functions, and usually one of the parameters is result of another function, use same register as the output of that function (the "standard" calling conventions do not do this and thus are piss-poor designed, e.g. they return in eax/rax, but don't take eax as parameter -> register shuffling, stupid design)

So, your procedure should know where do its parameters come from? Have you ever heard of SRP and stuff like that? I guess, we’re talking about hand-written (not compiler-generated) code that is part of a large project and is subject to further support, right?

Consider the following example. I have four callers of a procedure. Two of them have parameter value in EAX, the other two—in EDX. Which register should I use to hold the procedure parameter?

Furs wrote:
The standard calling conventions are horrible in respect to the kind of "code quality" you speak of, yet they are consistent, so clearly your reasoning doesn't really hold.

I like ISO/IEC 25010. It defines Quality as something that has characteristics, subcharacteristics and measures, and it is an international standard. (BTW, note that you are the first one to use the word “quality”, not me.)

You seem to put equality sign between performance and code quality. Which I strongly disagree, because performance is the property of an executable program, not code. Not even to mention that your microoptimizations might even decrease performance on modern CPUs.

Furs wrote:
Consistency doesn't mean good code.

Maintainability does. And consistently written code is generally more maintainable. That’s why we have coding guidelines in the first place: those describing even such useless (from performance point of view) stuff as “How much spaces should I write here, or should I use a Tab?”

Quote:
Except for the GCC-specific 32-bit register parameter passing convention (parameters in eax, ecx, edx), all the others do not make use of eax/rax when it is the return value which means you will end up with a lot of register shuffling around.

C/C++, open-source, Linux and stuff like that is not the whole world. And not even its best part. Even constantly-blamed Delphi has been using EAX, ECX and EDX for parameter passing for ages. Not to mention thousands of other compilers.

You will end up shuffling registers around anyway if you ever add something to your program after its initial release. This shuffling is not a property of a calling convention itself, it is a property of whether the procedure requirements and the caller’s register choice suit each other. That your callers are happy with your current implementation doesn’t mean they will be after you make changes to your procedure.

Quote:
If you're worried that the code uses specific registers that don't correspond to those of the function, well, then decide which functions to call first and lookup their registers before writing the (local) piece of code Wink

And re-decide whenever you need to make changes to your code. And rewrite a whole lot of it if a change makes you review your register usage policy for a procedure with lots of callers. Too much work for having a few ticks (questionnably) saved.

Quote:
ebx and ebp are not touched but they can't be parameters, since both use the same calling convention for "consistency".

You’ve never really written such code, right?

Quote:
'edx' is a parameter but at the end of function A, it is trashed, thus you will have to reload it anyway (i.e. register moves).

I will not use EDX in the caller to store data needed later in the first place. Just like you wouldn’t do with whatever register YOU choose. It’s just that in my code you don’t need to dive deep to find out the details.

You can save EAX, ECX and EDX and be proud of it. Or you can save EBX, ESI and EDI and stay closer to a commonly-used calling convention. But since you will almost always need EAX, ECX and EDX for calculations in your procedure anyway (do you use arrays? loops?), so the caller has to store the calculated values somewhere before doing even more calculations anyway.

It turns out, EBX, ESI and EDI are more convenient for long-time storage: ESI and EDI are cool when dealing with arrays, and all the three are preserved if you call WinAPI functions. EBX is often spare for loop counter in such cases, instead of ECX.

Quote:
If you had a variable computed before function A that you wanted to pass to function B, you'd have to place it in ebx or ebp -- then reload it into edx before calling B. A useless "mov edx, ebp" or whatever, and all this because of consistency.

In real world you might decide not to call function B if function A fails, so no real need to calculate values before you need them. And, like I said, whether you choose this or that set of registers doesn’t make difference. Creators of x86 calling conventions just made the choice keeping in mind the specifics of x86 instructions.

Quote:
Which obviously means a different calling convention: parameter registers are usually touched (or always?) so thus its parameter registers must be different from A's to get rid of the reload.

How does custom register choice prevent this? If you need EDX inside the procedure (say, for a mul), you will use it and… save the old value to the stack?

If the caller needs to use values prepared for this function later, it will save them. In most cases the caller just calculates them to pass to the function and proceed with its results, not its parameters.
Post 21 Feb 2018, 19:13
View user's profile Send private message Visit poster's website Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 2545
Furs 22 Feb 2018, 13:34
DimonSoft wrote:
So, your procedure should know where do its parameters come from? Have you ever heard of SRP and stuff like that? I guess, we’re talking about hand-written (not compiler-generated) code that is part of a large project and is subject to further support, right?

Consider the following example. I have four callers of a procedure. Two of them have parameter value in EAX, the other two—in EDX. Which register should I use to hold the procedure parameter?
Depends which of the calls is more important for performance. You don't have to go so far all the time, of course. If they're equal in all ways, just toss a coin?

DimonSoft wrote:
I like ISO/IEC 25010. It defines Quality as something that has characteristics, subcharacteristics and measures, and it is an international standard. (BTW, note that you are the first one to use the word “quality”, not me.)

You seem to put equality sign between performance and code quality. Which I strongly disagree, because performance is the property of an executable program, not code. Not even to mention that your microoptimizations might even decrease performance on modern CPUs.
That's why I put it in quotes (so it's not literal meaning).

Also, I have no idea what you are talking about with performance or whatever. You are the one who complained about the multiple calling conventions resulting in code moving around registers (for parameters etc) and so on, so I was addressing that.

So obviously, an extra mov is bad in this case, because you complained about it and that's what the argument was. It has nothing to do with whether it's faster or slower or more quality (literally) or not, but with arguing about the issue you brought up.

DimonSoft wrote:
Maintainability does. And consistently written code is generally more maintainable. That’s why we have coding guidelines in the first place: those describing even such useless (from performance point of view) stuff as “How much spaces should I write here, or should I use a Tab?”
That's ok, cause those don't affect the output. I don't find looking up registers for function parameters a burden simply because you already have to know the order of the parameters of the function anyway. Even with a "standard" calling convention, how do you know that what's in eax should be x and not y? Maybe the function has parameter y first, etc. You still have to look it up.

Also, if maintainability is so important why evne use assembly language?

In my opinion, if you aren't going to go all the way in asm, then it's pointless to use it. Compilers can be designed with custom calling conventions (automatic or not, using only simple heuristics now), I've done it myself in a GCC plugin. In which case you don't care about the calling convention in the code whatsoever.

So the compiler can generate superior code than you in this situation (function calls, not a tight call-less loop), and you don't even have to use asm, which is a "bonus" for maintainability; "maintainable" asm has no perks versus a HLL.

DimonSoft wrote:
And re-decide whenever you need to make changes to your code. And rewrite a whole lot of it if a change makes you review your register usage policy for a procedure with lots of callers. Too much work for having a few ticks (questionnably) saved.
Well why code in asm then?

If you don't care of a few ticks, or code size, just use a HLL? I mean, even if you think you're going to change the code a lot, use a HLL. And when you think it's "frozen" for its current version (i.e. no revamp of it anymore), maybe code it in asm once if you are really perfectionist, etc.

Of course, you can mix and match asm and HLL code. The point is that we're talking about bits needing asm, so let's say, every tick (or code size) is worth it in this situation. Wink

DimonSoft wrote:
You’ve never really written such code, right?
What do you mean? That's the standard x86 (and x64) calling convention: ebp (rbp) and ebx (rbx) are saved in the callee if used. So they can't be parameters without changing the calling convention totally (not just what passes in what regs, but also which are clobbered and which aren't).

DimonSoft wrote:
It turns out, EBX, ESI and EDI are more convenient for long-time storage: ESI and EDI are cool when dealing with arrays, and all the three are preserved if you call WinAPI functions. EBX is often spare for loop counter in such cases, instead of ECX.
No, they're only on the artificial tests they did on software that existed when they designed the calling convention. You seem to not understand what I'm saying.

Sure, the x86 calling convention is probably better, on average, than other calling conventions. But that's assuming you use only one calling convention. My point isn't to change it to a different one and keep that for the whole program and expect it to be "better" than the default. It probably won't be.

The point is to not be confined to one calling convention. No matter how good "on average" a calling convention is (just look at the mailing lists how x64 Linux ABI was designed -- clearly they cared only for specific average cases), average is not the only situation that will arise.

I have no interest in designing or using a calling convention in all the code in my arguments here, but to use multiple ones. I will never claim that one calling convention can be superior in ALL cases.

(that said, it's not hard to make a better ABI than the standard one, especially the disgusting Microsoft x64 ABI)

For example, the x64 MS ABI is so disgusting and has that stupid "Home Space" or "Shadow Space" because they, literally, wanted to use one calling convention everywhere. Instead of making an exception for the extremely rare variadic functions and "non prototyped" functions in old C (lol who uses those anymore?), they decided to make a subpar ABI just to be able to claim "dude we use only one ABI with no special cases hah!!!", which degrades the "quality" of ALL the rest of the code, the majority 99.999999% of functions which don't give a shit about variadic or "non prototyped" functions. Aiming for the average no matter the cost is just stupid especially since compilers can easily deal with multiple calling conventions (so maintainability is not a concern).


In short, I'm not advocating about making a calling convention to apply to all functions and expect it to work better than the default one; but to use multiple calling conventions as needed in the code. After all, if I code in asm, it means it's worth it.

I will never want to end up with a stupid convention like the x64 MS ABI that "works everywhere, equally" and has that thing that gets on my nerves called the Shadow Space. (for example in the x64 Linux ABI, it's damn difficult to implement variadic functions and not even faster than passing them directly on the stack since they'll have to be spilled anyway -- they could have just used a different calling convention to pass all parameters on the stack instead, but no, "purity" bullshit to keep the same calling convention everywhere leads to trash code like this).
Post 22 Feb 2018, 13:34
View user's profile Send private message Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 1228
Location: Belarus
DimonSoft 22 Feb 2018, 17:48
Furs wrote:
DimonSoft wrote:
Consider the following example. I have four callers of a procedure. Two of them have parameter value in EAX, the other two—in EDX. Which register should I use to hold the procedure parameter?
Depends which of the calls is more important for performance. You don't have to go so far all the time, of course. If they're equal in all ways, just toss a coin?

Now you work on the next version and there appear 5 places where ECX is the best choice. Should I go change the procedure and the 4 callers, or should I leave it? Remember, in the next version you’re going to make changes to some other procedures and those changes would make EDX a better choice in 3 places out of these 5.

So, you can spend your time changing the register choice to suit the needs of… I don’t know… the most often used caller, most of the callers, somebody else’s needs. Or you can measure what you’ve gained and just go release a new version. No, seriously, you have to measure it on real projects (synthetic tests are synthetic). What you’re talking about is just using multiple variations of fastcall which itself doesn’t make any significant diference for an out-of-order pipelined CPU running OS with pre-emptive multitasking, since any 5 ticks you posibly gain will get lost on the very first context switch.

Furs wrote:
Also, I have no idea what you are talking about with performance or whatever. You are the one who complained about the multiple calling conventions resulting in code moving around registers (for parameters etc) and so on, so I was addressing that.

I talk about performance since that seems to be the only pragmatic reason to use custom calling conventions for each procedure. Maybe size-coding is relevant here, although this would not gain much even here. Name your goals.

As for register shuffling, I don’t have this problem using, say, stdcall. All the parameters are passed on the stack and I don’t need to xchg eax, edx just because the procedure is written for effective calling from another place and requires its parameter in edx while I have it in eax. Yes, I may (!) lose a few ticks due to memory accesses but (1) top of the stack is already in cache anyway (2) these few ticks are nothing compared to context switches and (3) I save hours of work whenever I need to make significant changes to my program: I just use the same rules and make all the code obey them, so no need to double check every place that calls every procedure being changed.

Furs wrote:
I don't find looking up registers for function parameters a burden simply because you already have to know the order of the parameters of the function anyway.

Order of parameters doesn’t make me change the way I prepare (calculate) them: if in this place it is more convenient to calculate it in ECX, I will, if EDX is more convenient in another place, I will use it there.

This becomes more an argument of “stack- vs register-based calling conventions” here though.

Furs wrote:
Even with a "standard" calling convention, how do you know that what's in eax should be x and not y? Maybe the function has parameter y first, etc. You still have to look it up.

The answer is simple: fastcall gains nearly nothing for x86 at a high maintenance cost, most other conventions use stack. Knowing just parameter order is much easier than remembering which parameter goes where. One of the reasons MS-DOS/BIOS calling conventions didn’t survive, I guess.

Furs wrote:
Also, if maintainability is so important why evne use assembly language?

I knew you’ll say this. If you’re writing some toy project that is never going to be maintained anyway, our whole argument costs nothing: there’s no difference, you can even let yourself write terrible buggy code and if it works, that’s OK. But should it then be advised to everyone?

Furs wrote:
In my opinion, if you aren't going to go all the way in asm, then it's pointless to use it.

I prefer going plain asm. That’s why I value maintainability more than… not performance, ugh?.. Because even projects that would be considered small by 99% of programmers out there cannot be written in assembly at once. You write libraries and reuse your code and you spend days/weeks/months before you get something close to your original idea. And that is what makes maintainability a must.

Furs wrote:
Compilers can be designed with custom calling conventions (automatic or not, using only simple heuristics now), I've done it myself in a GCC plugin. In which case you don't care about the calling convention in the code whatsoever.

So the compiler can generate superior code than you in this situation (function calls, not a tight call-less loop), and you don't even have to use asm, which is a "bonus" for maintainability; "maintainable" asm has no perks versus a HLL.

I want more control over my code. I don’t want to deal with MSVCRT hell and similar problems of HLLs. I want some size-coding for style points. There’s plenty of reasons not to use HLL, none of which renders maintainability useless.

Furs wrote:
Of course, you can mix and match asm and HLL code. The point is that we're talking about bits needing asm, so let's say, every tick (or code size) is worth it in this situation. Wink

The only real difference I see between asm and HLL is that you have to be a bit more verbose with asm. Ticks are not worth if nobody even notices them. How often do you measure your code performance?

A few places with strict performance requirements are definitely worth being written with every tick in mind (mostly long calculations). Interacting the user, accessing disk or network, retrieving parameters from command line—most of the stuff is not worth over-optimizing at the cost of maintainability, since nobody will notice and you will just create more work for yourself which is useful for training your brain but most tasks in IT serve this purpose even better.

Furs wrote:
DimonSoft wrote:
You’ve never really written such code, right?
What do you mean? That's the standard x86 (and x64) calling convention: ebp (rbp) and ebx (rbx) are saved in the callee if used. So they can't be parameters without changing the calling convention totally (not just what passes in what regs, but also which are clobbered and which aren't).

They can’t be parameters in these calling conventions because other means of passing parameters are specified as the convention. If you wish, you CAN extend these conventions to pass parameters through EBX/RBX. The same for EBP/RBP although for these ones it wouldn’t be practical.

The fact values in these registers should stay intact doesn’t mean you can’t pass parameters in them. In fact, it is useful since you then know you can leave some useful value there and be sure it will stay there after the call. Do you suggest trashing every register out there and making callers save their data themselves?

Furs wrote:
DimonSoft wrote:
It turns out, EBX, ESI and EDI are more convenient for long-time storage: ESI and EDI are cool when dealing with arrays, and all the three are preserved if you call WinAPI functions. EBX is often spare for loop counter in such cases, instead of ECX.
No, they're only on the artificial tests they did on software that existed when they designed the calling convention.

They’re suitable because x86 ISA is made so.
Code:
; WARNING: Written in browser tab, error and corner case handling skipped. For demo purposes only.
        mov     ebx, [nDoughPieces]
        mov     esi, [pDoughPiecesArray]
        mov     edi, [pCakeArray]
.BakeLoop:
        lodsd
        stdcall BakeCake, eax, KITCHEN_DEFAULT, CAKETYPE_TASTY
        stosd
        stdcall Cake.PutCream, eax
        dec     ebx
        jnz     .BakeLoop    

ECX, EDX and partially EAX are still free to use in calculations. And they aree needed there: ECX for small loops and custom shifts, EAX and EDX for mul/div stuff. ESP definitely points to the stack. EBP holds a pointer to the base of the stack frame which makes it easy to access nearly unlimited amount of parameters and local variables without the need to spend a register: most of them are really needed once or twice, spending 1 register out of 7 (without ESP) for each of them would be a bad idea.

GRPs are not tied to particular usage, but if you don’t try to swim against the river and use them in a way they are good at you gain a lot.

Furs wrote:
My point isn't to change it to a different one and keep that for the whole program and expect it to be "better" than the default. It probably won't be. The point is to not be confined to one calling convention.

Using “standard” calling conventions means saying, “OK, it is at most this bad in all cases but is generally better in some of them”. When I use it, I know the worst case.

With custom calling conventions you go from the other side: “It is this good in the case I’ve optimized it for, in all other cases it may be worse”. You then know the best case.

But nobody cares if it took a program 5 or 6 ms to do the job. Nobody writes in TORs and other specifications that “the program should do this amount of work in at least 5 ms”. Whenever performance is important it is always “It should take at most 1000 ms for the program to do this amount of work in this environment”.

Furs wrote:
I will never claim that one calling convention can be superior in ALL cases.

Nobody here does. I claim that sticking with one calling convention (with rare exceptions of performance-critical code paths) makes maintaining projects easier and is thus worth it.

Furs wrote:
For example, the x64 MS ABI is so disgusting and has that stupid "Home Space" or "Shadow Space" because they, literally, wanted to use one calling convention everywhere. Instead of making an exception for the extremely rare variadic functions and "non prototyped" functions in old C (lol who uses those anymore?), they decided to make a subpar ABI just to be able to claim "dude we use only one ABI with no special cases hah!!!", which degrades the "quality" of ALL the rest of the code, the majority 99.999999% of functions which don't give a shit about variadic or "non prototyped" functions. Aiming for the average no matter the cost is just stupid especially since compilers can easily deal with multiple calling conventions (so maintainability is not a concern).

God bless you for these words! I felt I was the only one to hate x64 MS calling convention (which even made it way into x64 UEFI standard). But then again, it wasn’t designed for hand-written assembly. Which is why I’m not eager to switch to x64 ASAP.

Quote:
In short, I'm not advocating about making a calling convention to apply to all functions and expect it to work better than the default one; but to use multiple calling conventions as needed in the code. After all, if I code in asm, it means it's worth it.

Got your idea but still insist that using something consistent is a better advice generally. No need to forbid yourself switching to something else in certain cases, but no need to invent a new convention for every procedure in a program.
Post 22 Feb 2018, 17:48
View user's profile Send private message Visit poster's website Reply with quote
rugxulo



Joined: 09 Aug 2005
Posts: 2341
Location: Usono (aka, USA)
rugxulo 22 Feb 2018, 19:33
Furs wrote:

That's the standard x86 (and x64) calling convention: ebp (rbp) and ebx (rbx) are saved in the callee if used. So they can't be parameters without changing the calling convention totally (not just what passes in what regs, but also which are clobbered and which aren't).


Does GCC always know if it's used? I don't think so. There's optional switches like (uncommon) "-momit-leaf-frame-pointer" and (common) "-fomit-frame-pointer". Supposedly it makes debugging harder (on some targets), and the benefit is usually little to nothing anyways. I guess it's enabled by default on some but not on i386 (AFAIK).
Post 22 Feb 2018, 19:33
View user's profile Send private message Visit poster's website Reply with quote
rugxulo



Joined: 09 Aug 2005
Posts: 2341
Location: Usono (aka, USA)
rugxulo 22 Feb 2018, 20:48
Furs wrote:

For example, the x64 MS ABI is so disgusting and has that stupid "Home Space" or "Shadow Space" because they, literally, wanted to use one calling convention everywhere. Instead of making an exception for the extremely rare variadic functions and "non prototyped" functions in old C (lol who uses those anymore?), they decided to make a subpar ABI just to be able to claim "dude we use only one ABI with no special cases hah!!!", which degrades the "quality" of ALL the rest of the code, the majority 99.999999% of functions which don't give a shit about variadic or "non prototyped" functions. Aiming for the average no matter the cost is just stupid especially since compilers can easily deal with multiple calling conventions (so maintainability is not a concern).

I will never want to end up with a stupid convention like the x64 MS ABI that "works everywhere, equally" and has that thing that gets on my nerves called the Shadow Space. (for example in the x64 Linux ABI, it's damn difficult to implement variadic functions and not even faster than passing them directly on the stack since they'll have to be spilled anyway -- they could have just used a different calling convention to pass all parameters on the stack instead, but no, "purity" bullshit to keep the same calling convention everywhere leads to trash code like this).


ISO C does allow the ABI to change for variadic functions, is that what you meant? Some targets did that, AFAIK, hence always needing to "#include <stdio.h>" before using printf(), etc.

Just a hunch, but this is probably moreso due to other complications (or supporting other languages??) than just ideological purity. Obviously, not all languages (e.g. Pascal) support variadic functions. While we may think of MS these days as preferring C++ almost exclusively, they do have several other languages they support. Maybe overall, in the long run, it made other things easier to implement.

Of course using only one API, ABI, language, compiler, etc. (e.g. OberonOS) would simplify things, but it's hard to get people to agree or standardize anything.
Post 22 Feb 2018, 20:48
View user's profile Send private message Visit poster's website Reply with quote
Furs



Joined: 04 Mar 2016
Posts: 2545
Furs 23 Feb 2018, 14:22
DimonSoft wrote:
Now you work on the next version and there appear 5 places where ECX is the best choice. Should I go change the procedure and the 4 callers, or should I leave it? Remember, in the next version you’re going to make changes to some other procedures and those changes would make EDX a better choice in 3 places out of these 5.

So, you can spend your time changing the register choice to suit the needs of… I don’t know… the most often used caller, most of the callers, somebody else’s needs. Or you can measure what you’ve gained and just go release a new version. No, seriously, you have to measure it on real projects (synthetic tests are synthetic). What you’re talking about is just using multiple variations of fastcall which itself doesn’t make any significant diference for an out-of-order pipelined CPU running OS with pre-emptive multitasking, since any 5 ticks you posibly gain will get lost on the very first context switch.
Well if the function doesn't matter I'd just code it in a HLL Wink

I'm not going to quote the rest since I'll just reply here with the gist of it. You can get rid of "msvcrt" hell very easily and just treat C++ as a higher level assembler. That's what I do anyway, at least with GCC. Just pass -nostdlib and use your own entry point.

You can still use the std lib APIs, mind you, like any other library APIs. Import them, etc. Just not the ones that would get embedded in your code, and besides you can just use Windows APIs (or whatever the platform is).

Yeah, you won't be able to use the standard library easily, but so what? You can't use it in asm anyway. I'm just saying use it instead of asm as a "higher level assembler", not as "pure C++" code.

BTW, good to see another x64 MS ABI hater. I thought I was alone. Smile

DimonSoft wrote:
Nobody here does. I claim that sticking with one calling convention (with rare exceptions of performance-critical code paths) makes maintaining projects easier and is thus worth it.
Well, in my case, I do use custom calling conventions in most places but because they're HLL code -- I use my GCC plugin etc.

Which either uses (very simple) heuristics, or I just manually change it if I see it matters. I only have to change the function's prototype (attribute) when changing it, GCC will automatically use the new registers in the most efficient way it knows how to (of course it's not perfect, since it's still a compiler).

For functions that DO matter, of course I have to manually change them, but since they're fully in asm, it makes sense that I have to squeeze everything out of them Wink


rugxulo wrote:
Does GCC always know if it's used? I don't think so. There's optional switches like (uncommon) "-momit-leaf-frame-pointer" and (common) "-fomit-frame-pointer". Supposedly it makes debugging harder (on some targets), and the benefit is usually little to nothing anyways. I guess it's enabled by default on some but not on i386 (AFAIK).
It always considers "callee saved" registers as unused, since the calling convention mandates them to be saved if used.

GCC has an optimization IPA RA (interprocedural analysis register allocation) which tracks exactly which registers a function uses. Then it can avoid spilling those regs and reloading them in the caller if it knows the target doesn't use them (mostly in short functions obviously).

I've relied on it to create efficient custom calling conventions in my GCC plugin. I just made it think all function calls can trash any register, but then told it (artificial) information about which registers (via IPA RA) it doesn't use. So even if 'ebx' is callee-saved, it still considers it trashed (leading to less optimal code without IPA RA), but then I tell IPA RA that it's unused (even if it's used, because it's saved). This way I give it different information for functions which don't save 'ebx' (because they use a different calling convention, for example).

So the resulting code is as optimal as it can be, but of course, whatever GCC considers "optimal", I'm not changing its core register allocator...

rugxulo wrote:
Maybe overall, in the long run, it made other things easier to implement.
That's exactly the issue here.

Make an ABI, that will be implemented once (or in a compiler, once) "easier" for a few functions (variadic) at the cost of billions or trillions or more of function calls being wasted is pathetic to me. This is why I have major beef with most compiler vendors too. They just love to make something easier in the compiler, which is only done once or in one product, when millions of apps depend on it. So you decide to make one product easier to implement at the cost of bloat in ALL other apps that use it and they don't even give as an option (without plugins) to get rid of it!!! Most selfish behavior I've seen in tech.

It's even more pathetic how this crap is ENFORCED in almost all compilers and you have to use plugins to go around it. x86 at least has multiple calling conventions that you can use in your code without plugins.

The point is, it pisses me off that they want "one true ABI for EVERYTHING" instead of making specialized ABIs or calling conventions. Oh, even Microsoft had to learn the hard way that a generic ABI sucks, they added "__vectorcall" now, but for non-vector crap we're still stuck with that stupid thing.

One "average" ABI just sucks.

BTW even for variadic functions it's suboptimal. Since the callee will have to spill the registers on the stack (in the shadow space) anyway, why not just push them in the caller and avoid placing them in registers uselessly??? It's just suboptimal for everything.
Post 23 Feb 2018, 14:22
View user's profile Send private message Reply with quote
DimonSoft



Joined: 03 Mar 2010
Posts: 1228
Location: Belarus
DimonSoft 23 Feb 2018, 19:42
Furs wrote:
I'm just saying use it instead of asm as a "higher level assembler", not as "pure C++" code.

Seems like using airplane as a bicycle. Well, to be honest, I strongly dislike the whole C/C++ ecosystem starting from multiple inconsistencies in the languages themselves (due to historical reasons) and up to the way the ecosystem evolves. So, I’m actually using asm as C++ without insanities and unnecessary over-engineering.

Furs wrote:
BTW, good to see another x64 MS ABI hater. I thought I was alone. Smile

Time to start haters’ club, I guess Smile
Post 23 Feb 2018, 19:42
View user's profile Send private message Visit poster's website Reply with quote
Melissa



Joined: 12 Apr 2012
Posts: 125
Melissa 27 Feb 2018, 06:21
DimonSoft wrote:
Furs wrote:
I'm just saying use it instead of asm as a "higher level assembler", not as "pure C++" code.

Seems like using airplane as a bicycle. Well, to be honest, I strongly dislike the whole C/C++ ecosystem starting from multiple inconsistencies in the languages themselves (due to historical reasons) and up to the way the ecosystem evolves. So, I’m actually using asm as C++ without insanities and unnecessary over-engineering.

Furs wrote:
BTW, good to see another x64 MS ABI hater. I thought I was alone. Smile

Time to start haters’ club, I guess Smile


C has different calling conventions stdcall cdecl and fastcall, you have to look up into declaration to figure that out. eg Windows uses stdcall for Windows procedures, rest cdecl.
Linux kernel has it's own calling convention which is not C compatible...
If code is pure assembly, I don't see a reason to use cdecl at all
Post 27 Feb 2018, 06:21
View user's profile Send private message 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-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.