RFC: 'XCall' calling convention...



well, I beat together a general spec for the calling convention I am
considering (and have thus far, more or less implemented within my existing
compiler, although what I have implemented thus far differs from this spec
on a few minor points, but these would be easily resolved).

yeah, it probably all seems complicated, but as I see it, SysV is worse (it
adds a good deal more complexity to the compiler, whereas mine should be
easily implementable with a linear-stack-machine based compiler, although
granted a 2-pass design may be needed, which is what is used in my case...).


I would like comments on any off this, like if anyone feels I have messed
something up or if there is something in need of improvement...


so, I will include some of the relevant text:

<---
Idea:

XCall is a calling convention for x86-64, and may be used in place of the
SysV or Win64 conventions. Its purpose is to simplify code generation in
some cases (such as when writing a compiler).


Convention:

Arguments are passed on the stack in right-to-left order, with each argument
taking up 1 or more spots on the stack depending on the size of the object;
Items are to be aligned on natural boundaries, which for 128 bit types
(int128, float128, vec3/4, quat, ...), is 16 bytes;
The stack at the call point is required to be aligned on a 16-byte boundary.

So, Like SysV, stack alignment is required, but unlike SysV, all arguments
are to be passed on the stack, and not in registers.


The addition of the 128-bit alignment rule is new, but will help with the
performance of SSE vectors passed on on the stack.


As another way of viewing it, imagine that pushing certain types may involve
pushing a special "dummy" item, and that this item can be safely discared
when retrieving meaningful values. In the case of a function call, it can be
determined that pushing arguments would cause an uneven stack alignment, and
so a dummy value is pushed prior to the arguments in order to correct the
alignment (beyond this, it is up to the compiler).

Note: Assuming that pushing types onto the stack will cause them to
automatically align as needed, it is still necessary to align prior to
pushing arguments, as otherwise it could cause a "non-deterministic" stack
layout at the call site.

Another possible option (if left-to-right evaluation is desired), would be
to pre-allocate space for the arguments, and then as each is evaluated it is
moved into its assigned spot, and when all arguments are evaluated then the
call is performed.

As another rule (for increased compatibility with Win64), RSP may be
required to be kept in proper 16-byte alignment and below any of the working
data of the function (XCall will not assume the presence of a "Red Zone").


Register Usage:

The registers RAX, RCX, RDX, R8, R9, R10, R11 are caller-save registers, and
may be freely used in a called function. A caller can't safely assume that
these registers will be retained across a function call.

The registers RBX, RBP, R12, R13, R14, and R15 are callee save registers. A
caller may be assume that the values are preserved across a call, and a
callee is required to preserve their values prior to use and restore their
values prior to returning.

The registers RSI and RDI are debated, and should be treated as caller-save
by the caller, and as callee save by the callee. An exception to this rule
is that they may be treated as caller-save if the calling convention is
known to be SysV, and as callee save if it is known to be Win64.

RBP is is to be used as the frame pointer (technically, this rule will be
similar to Win64's epilogue requirement, only that RBP is reserved
specifically for this purpose if a frame pointer is used).

So:
<function>:
[push reg]*
[mov rbp, rsp]
....
(mov rsp, rbp) | (lea rsp, [rbp+<const>]) | (add rsp, <const>)
[pop reg]*
ret

The reason for this is to simplify the task of function unwinding.



Function Naming:

XCall will make use of name mangling for all names. This will simplify the
autogeneration of stubs when linking against existing code which may be
compiled to use a different calling convention (the mangled name will serve
to tell the stub generator how the stack frame is layed out in order so that
it can re-package the arguments as needed).

The function name will include a prefix:
'_XC_' is used for ordinary functions and for calls to a function which
accepts a fixed number of arguments;
'_XV_' is used when calling vararg functions, and also for the call-target
of such a call (this symbol will be either an alias or a jump to the actual
function implementing the vararg function, or a conversion stub if an
inter-convention call is taking place, however this will never be the proper
name of the vararg function in question).

This prefix will be followed by a mangled version of the name and signature
string.

Note: Include section on Signature Strings if needed.


The name and signature will be mangled by replacing certain characters with
escape sequences:
'_' with '_1';
';' with '_2';
'[' with '_3';
'(' with '_4';
')' with '_5'.

Alphanumeric characters are embedded unchanged.
'_9xx' encodes a character in the range of 1 to 127 (ASCII range);
'_0xxxx' encodes a character outside this range (Unicode space).


Note that it is the case with ordinary function calls that the mangled name
used by the caller and reciever are required to be equivalent.

The signature in this case will represent the arguments being accepted by
the reciever, and not the types of values on the stack from the POV of the
caller (It is reasonable that the types not match exactly between them, for
example, as the result of a cast or implicit type conversion).

In the case of Vararg functions, the signature will represent the values
passed on the stack from the POV of the caller, and so the same target
function may be called by any number of possible names (It is then the
responsibility of the linker to locate the correct call-target for a given
name and signature).
--->


<---
This will attempt to specify Signature strings as applied to data types.
These may be used for variables, objects, functions or methods, ...

Each type is intended to be parsable fairly easily, and where multiple types
may be present in the same string (them being placed end to end). However,
only the types in-sequence are specified, and this does not necessarily
specify how these will be placed in memory.


Qualifiers:
P*, pointer to type
R*, reference to type (invisible pointer)
C*, complex type (f,d,e,g,k)
G*, imaginary type (f,d,e,g,k)
U*, extended type qualifier ('<name>;')

X*, compound type (struct or union), followed by '<name>;'.
T*, will specify a tagged type (deprecated).
W*, Wide pointer type
L*, reference to object type ('<classname>;')
Q*, dynamic array of type

A*, B*, used for basic extension types

Basic Types:
a, signed char
b, bool
c, char
d, double
e, long double
f, float
g, 128-bit float
h, unsigned char/byte
i, int
j, unsigned int
k, hfloat (16-bit float)
l, long
m, unsigned long
n, 128-bit int
o, unsigned 128-bit int
p, (reserved)
q, (reserved)
r, variant (dynamicly typed reference)
s, short
t, unsigned short
u, custom type
v, void
w, wchar/short
x, long long
y, unsigned long long
z, ... (varargs, ...)

Extended Types:
m64, 64-bit raw SSE vector
m128, 128-bit raw SSE vector
quat, quaternion
hquat, hyperbolic quaternion
vec2, 2-elem geometric vector
vec3, 3-elem geometric vector
vec4, 4-elem geometric vector
mat2, 2x2 matrix
mat3, 3x3 matrix
mat4, 4x4 matrix
v2f, 2-elem raw vector
v3f, 3-elem raw vector
v4f, 4-elem raw vector
v2d, 2-elem raw vector (double)


If the type is followed by a number, it indicates that this is an array,
with a comma allowing multidimensional arrays.

Example:
bar:PXfoo;4,4
struct foo *bar[4][4];

Example:
Uvec4;16
Array of 16 4-vectors.

These arrays will specify memory with the specified physical layout.


Object References
'L<classname>;'

These will refer to a specific object type, where classname will be the
qualified class-name for the object in question.

This will differ from 'PX<name>;' in that 'X<name>;' will refer to a
specific and known structural type (the physical layout is thus known).
Likewise, I says nothing about the pointer itself, or the nature of the
pointed-to memory.

On the other hand, 'r' specifies that a dynamically-typed reference is
given, and thus the nature of the pointer and the kind of memory it can
point to, however, 'r' does not specify anything about 'what' is referenced.

'L<classname>;' will then specify that this is a dynamically typed reference
(similar to 'r'), but will also specify that it is a reference to a specific
abstract type (for example, the class with the given classname, or a class
derived from this class), but will not specify the physical layout or
concrete type of the referenced memory (unlike 'X' or 'PX'). In many cases,
'L' may be treated as simply a special case of 'r' (differing primarily in
terms of assignment, where the implementation is strongly encouraged to
ensure that the object being referenced is of the appropriate class, as
otherwise things may be allowed to break).

The exact structure and meaning of 'classname' will be internal to the
object system, but the current idea is that it will represent a heirarchy of
the form '[<name>/]*<class>'. For example, "myApp/custom/Foo".


Dynamic Arrays
'Q' will be similar to 'P' in spirit, but differ in practice similarly to
how 'L' differs from 'PX'. 'Q' will specify that a reference is used to a
dynamically-managed array holding members with the given type, but will
specify neither the physical layout of the array nor of the referenced
values.
Presently this will be limited to reference based types, such as 'Q', 'L',
and 'r'.

For example: 'QQr' will be an array of arrays of references, and
"QLmyApp/custom/Foo;" will be an array of objects.


Functions and Methods
Signature strings as applied to functions and methods will have a slightly
modified notation, namely in that a specific designation of args and return
type may be given.

The basic layout will be:
'(<args>)<ret>'.

Examples:
"int foo();" gives "()i";
"double bar(int x, int y);" gives "(ii)d".

This whole unit will be treated as a single type-unit, so, for example, a
function-pointer could be specified like this:
"P(d)i" for the type "int (*)(double)".

The exact meaning or interpretation of this type will depend on the context
of its usage.
--->


.



Relevant Pages

  • Re: How function calls work
    ... it depends on the compiler. ... For reference arguments, what's made available to the function is ... hardware-supported) stack on any platform that has reasonable support for ... that invocation record are deallocated appropriately, ...
    (comp.lang.cpp)
  • Re: Re (long): A Tale of Two Memory Managers (long)
    ... Create objects always on the heap, with the reference on ... > the compiler add code to create and clean up the objects ... the stack will contain information about type of object. ... with records somewhat complex than manipulations with simple pointers ...
    (borland.public.delphi.non-technical)
  • Re: How to dynamically rename a component in the object inspector ?
    ... The former is a _reference_ to the object, replaced by the compiler ... the characters you used as a reference to the object. ... I think what you really want to do is to specify two variables, ...
    (comp.lang.pascal.delphi.misc)
  • Re: cobol programs and functions in c (call *obj with param)
    ... If the compiler does not use some kind of template to specify if parameters ... explict BY REFERENCE or BY CONTENT in the COBOL CALL USING. ...
    (comp.lang.cobol)
  • Re: Automatic local variables, why not?
    ... Is there any reason not to add "stack variables" to Delphi? ... Delphi supports another reference count element -- interface. ... the compiler can't do that. ...
    (borland.public.delphi.non-technical)