Author Topic: Are register-saving conventions for assembler procedures not documented?  (Read 1539 times)


  • New Member
  • *
  • Posts: 41
I found that regardless of MODE, the push/pop ebx/esi/edi frame is always generated for assembler procedures with pascal calling convention, but never for stdcall. Is safecall changing something? I have not seen. (and when I try to set {$calling safеcall}, the FPC stops with internal error on those turbopascal sources that now compile with $calling pascal/stdcall)
«Signaling changed registers» doesn't seem to change anything for procedures at -Rintel too (but I have not experimented with this a lot).
Also, regardless of {$MODE TP}, registers are not saved in the case of asm inclusions.
In FPC, destroying ebx/esi/edi registers easily disrupts, for example, «for» loops. However, in Turbo Pascal, the programmer did not have to save registers in both cases:
Register-saving conventions
Procedures and functions should preserve the BP, SP, SS, and DS registers. All other registers can be modified.

An asm statement must preserve the BP, SP, SS, and DS registers, but can freely modify the AX, BX, CX, DX, SI, DI, ES, and Flags registers.


  • Hero Member
  • *****
  • Posts: 3183
  • Compiler Developer
Re: Are register-saving conventions for assembler procedures not documented?
« Reply #1 on: January 20, 2020, 10:45:42 pm »
Please note that you're throwing everything together here.

First of the safecall calling convention is only intended for functions that normally return a HRESULT on the non-Pascal side (mainly in the context of Windows COM (as in Component Object Model) programming). So this calling convention is not what you want.

Second the Mode has nothing to do with the calling convention or the prologues of routines. This is only influenced by the calling convention and the nostackframe modifier.

Third if you have an assembly block inside a function you need to specify which registers are modified so that the compiler's register allocator can spill them correctly (this is more efficient than saving/restoring all registers; depending on the architecture there can be many registers (e.g. x86_64 or PPC), also this allows the use of unknown assembly opcodes by manually encoding them). The syntax for this is as follows:

Code: Pascal  [Select][+][-]
  1. begin
  2.   // some Pascal code
  3.   asm
  4.     // let's assume the following:
  5.     mov ebx, ecx
  6.     mov ecx, eax
  7.     mov eax, ebx
  8.   end ['ebx', 'ecx', 'eax']; // these registers where modified
  9.   // some more Pascal code
  10. end;

This is not necessary for pure assembly functions (those consisting only of a asmend block).

Fourth you're quoting the Turbo Pascal calling convention. However 32-bit or 64-bit code is not using the Turbo Pascal calling convention. In fact that calling convention is provided by FPC only on the 16-bit i8086 target. On other platforms you need to adhere to the corresponding ABIs. On nearly all platforms FPC follows the default ABI of the C compiler (aka cdecl). The only exceptions are i386 where there are also the register (default), stdcall and pascal calling conventions, x86_64-win64 with the vectorcall calling convention and m68k with the register (default) calling convention.
When you work with assembly and functions you must learn at least about those calling conventions that you are using for those platforms that you are supporting with assembly.

See also: X86 calling conventions on Wikipedia
« Last Edit: January 20, 2020, 10:52:49 pm by PascalDragon »


  • New Member
  • *
  • Posts: 41
In newer versions of FPC (3.2.0/3.2.2) assembly routines are no longer lined by saving ebx/edi/esi even with {$CALLING PASCAL} directive.

Code: Pascal  [Select][+][-]
  2. function test: Integer; assembler;
  3. var J: Integer;
  4. asm
  5.   mov J, 5
  6.   mov eax, J
  7. end;

If such a function was previously compiled into such code:

Code: ASM  [Select][+][-]
  1. push    ebp
  2. mov     ebp,esp
  3. lea     esp,[esp-8]
  5. push    ebx
  6. push    esi
  7. push    edi
  9. mov     dword [ebp-8],5
  10. mov     eax,dword [ebp-8]
  12. pop     edi
  13. pop     esi
  14. pop     ebx
  16. leave
  17. ret

now to this one:

Code: ASM  [Select][+][-]
  1. push    ebp
  2. mov     ebp,esp
  3. lea     esp,[esp-8]
  5. mov     dword [ebp-8],5
  6. mov     eax,dword [ebp-8]
  8. mov     esp,ebp
  9. pop     ebp
  10. ret

What was compiled and worked with 3.0.4, now crashes due to register corruption in «for» loops.
Taking into account some other things about which I wrote a year ago, I have the feeling that my complaints were taken into account in the sense that exactly the opposite was done.


  • Hero Member
  • *****
  • Posts: 4738
include in the line "NoStackFrame" along with your Assembler etc..
The only true wisdom is knowing you know nothing


TinyPortal © 2005-2018