2

I have a X64 ASM routine assembled with MASM64/ML64. Its a stand alone leaf function, and not inline assembly. It is used in a C/C++ program within a Visual Studio solution.

I found two references on preserving registers on MSDN:

The first is for inline assembly, but it specifically states to preserve ECX when using __fastcall. It also appears to lack a treatment of X64 because it refers to 32-bit registers.

The second tells us "RAX, RCX, RDX, R8, R9, R10, R11 are considered volatile and must be considered destroyed on function calls". Unfortunately, it does not clearly state whether they need to be preserved. (If you look closely, its using misdirection rather than stating the action to take).

I think the second article is controlling in this case, but I want to be clear to avoid confusion... Does CX/ECX/RCX need be be preserved for X64 Fastcall Leaf Functions?

phuclv
  • 37,963
  • 15
  • 156
  • 475
jww
  • 97,681
  • 90
  • 411
  • 885
  • 1
    No, it doesn't need to be preserved. See [here](https://msdn.microsoft.com/en-us/library/ms235286.aspx). – Jester Oct 16 '15 at 21:12
  • Visual Studio 64 bit doesn't support inline assembly. – rkhb Oct 16 '15 at 21:13
  • @rkhb OP said _it's not inline assembly_ :) – Jester Oct 16 '15 at 21:14
  • Why do you worry about `_fastcall` in x86-64? The default calling conventions pass arguments in registers. – EOF Oct 16 '15 at 21:19
  • 3
    I thought that x86-64 has only one calling convention. – Michael Burr Oct 16 '15 at 21:28
  • @EOF: [This article](https://msdn.microsoft.com/library/ms235286.aspx) creates the confusion. – rkhb Oct 16 '15 at 21:38
  • @Michael - as far as I know, it uses Fastcall. I'm not confused about that. But I found Microsoft documents that tell me to preserve the CX register when using Fastcall. Hence the confusion I am trying to avoid. – jww Oct 16 '15 at 21:58
  • @MichaelBurr it used to have only one, until [`__vectorcall` was introduced in 2013](https://devblogs.microsoft.com/cppblog/introducing-vector-calling-convention/) – phuclv Oct 08 '21 at 01:34
  • If callers have to assume that functions destroy those registers, callees *are* allowed to destroy them without preserving them. i.e. those registers are [volatile, aka call-clobbered](https://stackoverflow.com/a/56178078). Inline `__asm` interfacing with surrounding C/C++ code within a function is essentially unrelated to the ABI / calling convention between functions. e.g. x86 (32-bit) fastcall has call-clobbered ECX, just like always for arg-passing registers. – Peter Cordes Jul 25 '22 at 08:23

2 Answers2

6

The "Using and Preserving Registers in Inline Assembly" article discusses only x86 and does not apply to x86-64.

The "Caller/Callee Saved Registers" article is about the x86-64 calling convention, and clearly states that the RCX register is volatile so does not need to be saved by the callee.

A comment by @rkhb mentions that the "Overview of x64 Calling Conventions" article is the source of the confusion, presumably because it says:

x64 just uses the __fastcall calling convention and a RISC-based exception-handling model

However, if you follow the __fastcall link in that quote, you'll see that it says, "This calling convention [__fastcall] only applies to the x86 architecture". I think that the Overview article really means to say something like, "x64 uses a calling convention similar to __fastcall where registers are used to pass arguments".

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • OK, thanks. Sorry about this question. I was having trouble following the links and finding ***the*** controlling document on MSDN. (I found a few documents, but it was not clear to me which one exerted the most control in this case). – jww Oct 16 '15 at 23:32
  • *presumably* = exactly. Thank you for the clarification. – rkhb Oct 17 '15 at 00:39
  • 1
    Now that x64 vectorcall exists, MS does use the name "fastcall" to describe their original x64 calling convention in their official docs. – Peter Cordes Oct 07 '21 at 22:48
0

Because callers have to assume that functions destroy those registers, callees are allowed to destroy them without preserving them. i.e. those registers are volatile, aka call-clobbered. That applies the that whole list of 64-bit registers, including all the arg-passing registers.


Inline __asm interfacing with surrounding C/C++ code within a function is essentially unrelated to the ABI / calling convention between functions. e.g. x86 (32-bit) fastcall has call-clobbered ECX, just like always for arg-passing registers. 32-bit fastcall is a separate calling convention from Windows x64 fastcall anyway, so nothing you read about 32-bit fastcall is authoritative for 64-bit.

MSVC inline asm support is so brittle and hackily implemented inside their compiler that they need your asm to jump through hoops in functions with register args, because their compiler is too dumb to save them somewhere safe like it does for ordinary variables. e.g. the doc you linked says "This can create problems in functions with __asm blocks because a function has no way to tell which parameter is in which register." but that's obvious nonsense because the calling convention nails that down. Otherwise the compiler couldn't make asm to access its parameters in functions not using inline asm. (Or it's a poorly phrased description of the fact that the inline asm code in the compiler wasn't written to deal with register-arg calling conventions, and never got re-written.) Other compilers, including clang-cl, don't have this problem.

That's where all this nonsense about preserving ECX is coming from, nothing to do with the way functions call each other in asm, only the requirements on function internals if you're using MSVC's brittle inline asm.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847