Qual è il vero vantaggio dell'uso di CDECL? (più specificatamente spingendo invece di reging)

6

Quindi sto imparando l'assembly e ho imparato a conoscere ABI e ho ottenuto alcuni test di base lavorando usando la cdecl chiamata convenzione per usare lo stdlib di c in nasm. Ma ho visto altre Convenzioni di chiamata (come il registro di alta velocità / Clarion / JPI / watcom / borland (delphi), fastcall, ecc.). E mi chiedo, quali sono i reali vantaggi dell'uso di cdecl al posto di Clarion. Più specificamente Pushing invece di usare i registri.

Questi sono alcuni dei vantaggi che ho immaginato per favore fammi sapere quale di questi si applica.

  1. Ho letto che cdecl è importante perché permette di usare parametri variabili, ma come vedo io, posso fare lo stesso usando i registri. I problemi sono conoscere il conteggio, il tipo e l'ordine dei param, ma quel problema esiste anche su cdecl. Può essere dedotto dalla stringa di formato (in printf) o dalla firma della funzione. E se esaurisco i registri posso spingere il resto dei parametri.

  2. Immagino che le prestazioni non dovrebbero essere una grande cosa perché i produttori di cpus potrebbero aver ottimizzato tutto ciò che potevano a prescindere dalla convenzione di chiamata del sindaco (e questa volta è cdecl? ¿?). Ma se penso alle operazioni non elaborate (e ignoro le cache e i buffer di writeback), penso dovrebbe essere più veloce usare i registri invece dello stack (che dovrebbe trovarsi nella ram giusto?). Voglio dire, è come "inc eax" vs "aggiungi eax, 1" (ho ragione?).

  3. Premendo i creo uno spazio di memoria che si spegne quando ritorno (proprio come la creazione di una variabile locale). Potrebbe sembrare utile (avere una var locale solo al momento della chiamata).
    Ma, dato che la maggior parte dei parametri (nella mia esperienza) sono usati come valore di lettura, e pochissime volte abbiamo bisogno di essi come variabile, per memorizzare valori mutati, e quando lo fai si tratta effettivamente di strutture complesse che vengono passate come puntatori in ogni caso .
    Quindi non vedo davvero il valore sulla creazione di uno spazio di memoria PRIMA di averne effettivamente bisogno.

    Come vedo, se io do ho bisogno di una variabile è meglio avere l'opzione per crearla (come in Clarion), ma se io don ' t sarebbe bello poterlo non crearlo (come non è possibile in cdecl).

  4. Conservi i registri.
    Non riesco nemmeno a pensare ad un lato positivo. AFAIK i registri sono utilizzati per i calcoli intermedi e, in quanto tali, sono di natura volatile. Se li considero volatili, potrei spingerli / pop / spostarli ogni volta che ho bisogno di "mantenere" il valore e solo in questi casi. In questo senso lo considero l'uso più efficiente (accedo solo alla ram quando ne ho bisogno).

Ma se non lo faccio, prova a "preservare i registri":

  • callee: Non ho la certezza di cosa farà il codice di chiamata con un registro (a meno che non sia documentato o aderisca alla stessa convenzione di chiamata). Sperando che li conservi, impone un limite artificiale al callee. Poiché il destinatario non ha idea di quali registri debbano essere veramente conservati, tenderanno a sovrastimare i registri non necessari. (come pusha / popa in x86?) Quali suoni davvero inefficienti per me.

  • chiamante: il chiamante non ha idea di quali registri useranno il callee [s] taint? [/ s], quindi li conserverà tutti. Finirà con lo stesso risultato inefficiente di prima.

Ho notato che linux syscalls e 8086 (dalle mie vecchie classi) usano i registri invece dello stack per passare i parametri. Che cosa è successo lì?

Quindi quelli sono i miei pensieri, grazie per tutti i chiarimenti possibili.

Note:

  • Sto imparando, la maggior parte di questi sono presupposti basati su ciò che ho letto / provato finora. Sarò felice se mi correggerai come necessario (solo essere gentile).
  • Capisco che questo sia tutto x86 CC, e in 64 ce n'è un altro (che non ho familiarità con).
  • Non sto chiedendo quale sia il "migliore", voglio solo capire di più e chiarire i miei presupposti .
  • Sto cercando i vantaggi della convenzione di chiamata da sola (in contrasto con gli altri). non dai suoi effetti collaterali (come i compilatori e la cpus in fase di ottimizzazione o la sua ubiquità)
  • Fondamentalmente una domanda teorica per comprendere meglio il motivo per cui è stata scelta questa strada
posta Nande 21.10.2015 - 05:55
fonte

2 risposte

5

Raymond Chen ha messo insieme una cronologia delle convenzioni di chiamata qui. Mentre non tocca Clarion, fa touch su Fastcall, che mentre non è lo stesso di Clarion usa più di un approccio basato sul registro.

Ha questo da dire:

Fastcall (__fastcall)

The Fastcall calling convention passes the first parameter in the DX register and the second in the CX register (I think). Whether this was actually faster depended on your call usage. It was generally faster since parameters passed in registers do not need to be spilled to the stack, then reloaded by the callee. On the other hand, if significant computation occurs between the computation of the first and second parameters, the caller has to spill it anyway. To add insult to injury, the called function often spilled the register into memory because it needed to spare the register for something else, which in the "significant computation between the first two parameters" case means that you get a double-spill. Ouch!

Consequently, __fastcall was typically faster only for short leaf functions, and even then it might not be.

Credo che le critiche qui applicate siano ancora pertinenti: Clarion è probabilmente più veloce per determinati tipi di chiamate, ma non per altre.

Detto questo, i tuoi punti sull'utilizzo del registro sono abbastanza validi. Sebbene tu non volessi considerare x64 nell'ambito della tua domanda, lo schema discusso più avanti in quella serie per Itanium potrebbe interessarti!

    
risposta data 24.10.2015 - 03:06
fonte
1

Le convenzioni di chiamata dipendono in qualche modo dalla piattaforma. Su Linux, GCC fornisce lo standard defacto. Questa pagina fornisce informazioni dettagliate sui pro e contro di varie convenzioni di chiamata su Linux (anche se con un po 'di iperbole):

Often you will be told that using C library (libc) is the only way, and direct system calls are bad. This is true, to some extent. In general, you must know that libc is not sacred, and in most cases it only does some checks, then calls kernel, and then sets errno. You can easily do this in your program as well (if you need to), and your program will be dozen times smaller, and this will result in improved performance as well, just because you're not using shared libraries (static binaries are faster)

Nel mondo Windows, ci sono un certo numero di convenzioni di chiamata, alcune delle quali sono specifiche di Microsoft, e alcune che non sono più supportate. Questa pagina spiega alcuni pro e contro di ciascuno. In particolare:

__cdecl is the default calling convention for C and C++ programs. Because the stack is cleaned up by the caller, it can do vararg functions. The __cdecl calling convention creates larger executables than __stdcall, because it requires each function call to include stack cleanup code.

The __stdcall calling convention is used to call Win32 API functions. The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype.

    
risposta data 21.10.2015 - 06:48
fonte

Leggi altre domande sui tag