12
$\begingroup$

The LLVM-IR Reference Manual's section on Calling Conventions lists as many as 15 supported calling conventions. These vary in ways including whether they support tail call optimization, how many and which registers they preserve, what existing languages use them, and so on.

There seems to be an overwhelming variety, even if you know what you want to focus on, such as speed or TCO support (e.g., should I use fastcc, tailcc, cc 10, or cc 11?).

When designing a compiler, using LLVM-IR or otherwise, how should one choose what calling convention(s) to use?

$\endgroup$

2 Answers 2

7
$\begingroup$

Any externally-accessible function (such as for libraries) should 100% use the standardized calling convention for the operating environment (what LLVM calls "ccc" I suppose). I believe on Windows there's not exactly a standardized convention since lots of legacy code still uses Pascal convention, so it would be optimum to allow some sort of decoration or attribute to specify the expected calling convention.

$\endgroup$
1
  • 3
    $\begingroup$ It's worth noting that the move to 64-bit consolidated most of the older Windows calling conventions. $\endgroup$
    – Pseudonym
    May 17, 2023 at 1:48
7
+50
$\begingroup$

I would split the 14 (I'm not including cc <n> here) calling conventions listed in three categories:

  • generic (those that would make sense for any function to have)
  • specific (those that some functions could benefit from)
  • targeted (those that were designed for specifically targeted situations) Each group requires a different analysis, so I think it is best to just consider them separately.

Generic

I include the following CCs in this category:

  • ccc - The C calling convention
  • fastcc - The fast calling convention
  • tailcc - Tail callable calling convention

The choice here is very dependent on what your language is like. If your language is designed imperatively and has little need for tail calls (or doesn't support them at all), or needs strict C compatibility ccc is probably best. If your language has no vararg support (or you compile varargs to something different than LLVM IR varargs), fastcc and tailcc are both very good (and, as far as I can tell, tailcc should generally be best), and you can probably always use them. I can't recommend either choice unless I know what the language is like, though from experience I can say that unless you really want c/llvm-style varargs, fastcc and tailcc are the better choice. Choosing between the two is probably about choosing whether you want tail calling to be idiomatic code, or just something that is supported. If the first, tailcc ensures that optimization happens. However, note this:

To support tail call optimization in situations where the callee has more arguments than the caller a ‘callee pops arguments’ convention is used. This currently causes each fastcc call that is not tail call optimized (because one or more of above constraints are not met) to be followed by a readjustment of the stack. So performance might be worse in such cases.

I have not found this to ever be a problem, but if you are very concerned about performance, you might want to statically decide where tail calls can happen (or let the user decide) and switch conventions based on that.

Specific

  • coldcc - The cold calling convention
  • preserve_mostcc - The PreserveMost calling convention
  • preserve_allcc - The PreserveAll calling convention

There is pretty much no way to decide that a function can benefit from coldcc (which marks it as unlikely to be called and is optimized for that), unless you let users mark/annotate a function as such. preserve_mostcc is useful, for example, when you have memoized functions, which generally just load a value and return it (the hot path mentioned on the page), but once need to do computation (the cold path). PreserveMost removes all register saving-and-loading instructions when you load the saved value, which is almost every time the function is called. preserve_allcc is, quoting the page, to support calls to runtime functions that don’t need to call out to any other functions, and tries to be as little damaging as possible to the caller's registers. I have never used preserve_allcc, so I can't really give an example here.

Targeted

Everything else, so cc 10, cc 11, webkit_jscc, anyregcc, cxx_fast_tlscc, swiftcc, swifttailcc, cfguard_checkcc.

You don't need these. There isn't much more to be said, you just won't need these 99.99% of the time. (the remaining 0.01% is, for example, if you want close Swift interop, in which case you might want swift[tail]cc)

$\endgroup$
5
  • $\begingroup$ I just realized I basically rephrased the linked page and added like one example. this is probably a somewhat bad answer, looking for opinions for deleting. $\endgroup$
    – RubenVerg
    Jun 20, 2023 at 20:32
  • 3
    $\begingroup$ I found this a useful way of organizing the information, definitely no reason to delete it IMO $\endgroup$ Jun 20, 2023 at 21:19
  • $\begingroup$ @RydwolfPrograms alr thanks $\endgroup$
    – RubenVerg
    Jun 21, 2023 at 6:50
  • $\begingroup$ Another reason to not use swiftcc: If you use it while compiling for RISC-V, LLVM segfaults. $\endgroup$
    – Bbrk24
    Jul 21, 2023 at 19:22
  • $\begingroup$ @Bbrk24 lmao I suppose that is a reason $\endgroup$
    – RubenVerg
    Jul 21, 2023 at 20:14

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .