Skip to content
This repository was archived by the owner on Mar 27, 2025. It is now read-only.
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 187 additions & 1 deletion docs/LoongArch-ELF-ABI-EN.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ v1.00

== Register Convention

.Integer Register Convention
.General-purpose Register Convention
[%header,cols="2,2,^5,^3"]
|===
|Name
Expand Down Expand Up @@ -657,3 +657,189 @@ with check 32-bit unsigned overflow
|Linux, Glibc
|/lib32/ld-linux-loongarch-ilp32s.so.1
|===

== Procedure Calling Convention

=== Abbreviations

In this document, *GRLEN* is the bit width of general-purpose register, *FRLEN* is the bit width of floating-point register and *WOA* is the bit width of the argument.
The general-purpose argument register is denoted as *GAR* and the floating-point argument register is denoted as *FAR*.

=== Argument Registers

The basic principle of the LoongArch procedure calling convention is to pass arguments in registers as much as possible (i.e. floating-point arguments are passed in floating-point registers and non floating-point arguments are passed in general-purpose registers, as much as possible); arguments are passed on the stack only when no appropriate register is available.

The argument registers are:

. Eight floating-point registers `fa0-fa7` used for passing pass floating-point arguments, and `fa0-fa1` are also used to return values.

. Eight general-purpose registers `a0-a7` used for passing pass integer arguments, with `a0-a1` reused to return values.

Generally, the GARs are used to pass fixed-point arguments, and floating-point arguments when no FAR is available.
Bit fields are stored in little endian.
In addition, subroutines should ensure that the values of general-purpose registers `s0-s9` and floating-point registers `fs0-fs7` are preserved across procedure calls.

=== ABI LP64D

That is, *GRLEN* = 64, *FRLEN* = 64.

=== C Data Types and Alignment

The C data types and alignment in the LP64D ABI are defined in the <<Type Size and Alignment, table 3>>.

In most cases, the unsigned integer data types are zero-extended when stored in general-purpose register, and the signed integer data types are sign-extended.
However, in the *LP64D* ABI, unsigned 32-bit types, such as `*unsigned int*`, are stored in general-purpose registers as proper sign extensions of their 32-bit values.

=== Argument passing

Generally speaking, FARs are only used to pass floating-point arguments, GARs are used to pass non floating-point arguments and floating-point arguments when no FAR is available(`long double` type is also passed in a pair of GARs) and the reference.

Arguments passed by reference may be modified by the callee.

==== Scalar

There are two cases:

. 0 < WOA ≤ GRLEN

.. Argument is passed in a single argument register, or on the stack by value if none is available.

... If the argument is floating-point type, the argument is passed in FAR.
if no FAR is available, it’s passed in GAR. If no GAR is available, it’s passed on the stack.
When passed in registers or on the stack, floating-point types narrower than GRLEN bits are widened to GRLEN bits, with the upper bits undefined.

... If the argument is integer or pointer type, the argument is passed in GAR.
If no GAR is available, it’s passed on the stack.
When passed in registers or on the stack, the unsigned integer scalars narrower than GRLEN bits are zero-extended to GRLEN bits, and the signed integer scalars are sign-extended.

. GRLEN < WOA ≤ 2 × GRLEN

.. The argument is passed in a pair of GAR, with the low-order GRLEN bits in the lower-numbered register and the high-order GRLEN bits in the higher-numbered register.
If exactly one register is available, the low-order GRLEN bits are passed in the register and the high-order GRLEN bits are passed on the stack.
If no GAR is available, it’s passed on the stack.

==== Structure

Empty structures are ignored by C compilers which support them as a non-standard extension(same as union arguments and return values).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this trying to say that empty structures do not occupy any registers or space on stack?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I think so.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Bits unused due to padding, and bits past the end of a structure whose size in bits is not divisible by GRLEN, are undefined.
And the layout of the structure on the stack is consistent with that in memory.

. 0 < WOA ≤ GRLEN

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miss a blank line.


.. The structure has only fixed-point members.
If there is an available GAR, the structure is passed through the GAR by value passing; If no GAR is available, it’s passed on the stack.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that integers and trivial struct wrappers of integers are not passed the same way. Integers are sign-extented; trivial struct wrappers of integers are not sign-extended (upper bits are undefined).

Is this example correct?

void A(int x); // x is passed in `a0` register, upper 32-bit of `a0` are sign-extended

struct S
{
    int f;
};

void B(struct S s); // s is passed in `a0` registers, upper 32-bit of `a0` registers are undefined

@shushanhf shushanhf Dec 20, 2021

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that integers and trivial struct wrappers of integers are not passed the same way. Integers are sign-extented; trivial struct wrappers of integers are not sign-extended (upper bits are undefined).

Is this example correct?

void A(int x); // x is passed in `a0` register, upper 32-bit of `a0` are sign-extended

struct S
{
    int f;
};

void B(struct S s); // s is passed in `a0` registers, upper 32-bit of `a0` registers are undefined

There is only one field, the upper 32-bits should be sign extened.

If struct S {char a; cha b;} , the upper bits:63-16 are also sign extened.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds contrary to https://github.com/loongson/LoongArch-Documentation/pull/32/files#diff-711b3e7b6a005b492898ac6d93f2d8d37c00e0831e210993a6f9dbb26c043717R699 : Bits unused due to padding, and bits past the end of a structure whose size in bits is not divisible by GRLEN, are undefined.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is only one field, the upper 32-bits should be sign extened.

This's right.

If struct S {char a; char b;} , the upper bits:63-16 are also sign extened.

The upper bits are undefiened


.. The structure has only floating-point members:

... One floating-point member.
The argument is passed in a FAR; If no FAR is available, the value is passed in a GAR; if no GAR is available, the value is passed on the stack.

... Two floating-point members.
The argument is passed in a pair of available FAR, with the low-order `float` member bits in the lower-numbered FAR and the high-order `float` member bits in the higher-numbered FAR.
If the number of available FAR is less than 2, it’s passed in a GAR, and passed on the stack if no GAR is available.

.. The structure has both fixed-point and floating-point members, i.e. the structure has one `float` member and...

... Multiple fixed-point members.
If there are available GAR, the structure is passed in a GAR, and passed on the stack if no GAR is available.

... Only one fixed-point member.
If one FAR and one GAR are available, the floating-point member of the structure is passed in the FAR, and the integer member of the structure is passed in the GAR; If no floating-point register but one GAR is available, it’s passed in GAR; If no GAR is available, it’s passed on the stack.

. GRLEN < WOA ≤ 2 × GRLEN

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miss a blank line.


.. Only fixed-point members.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Miss a blank line.


... The argument is passed in a pair of available GAR, with the low-order bits in the lower-numbered GAR and the high-order bits in the higher-numbered GAR.
If only one GAR is available, the low-order bits are in the GAR and the high-order bits are on the stack, and passed on the stack if no GAR is available.

.. Only floating-point members.

... The structure has one `long double` member or one `double` member and two adjacent `float` members or 3-4 `float` members.
The argument is passed in a pair of available GAR, with the low-order bits in the lower-numbered GAR and the high-order bits in the higher-numbered GAR.
If only one GAR is available, the low-order bits are in the GAR and the high-order bits are on the stack, and passed on the stack if no GAR is available.
... The structure with two `double` members is passed in a pair of available FARs. If no a pair of available FARs, it's passed in GARs. A structure with one `double` member and one `float` member is same.

.. Both fixed-point and floating-point members.

... The structure has one `double` member and only one fixed-point member.

.... If one FAR and one GAR are available, the floating-point member of the structure is passed in the FAR, and the integer member of the structure is passed in the GAR; If no floating-point registers but two GARs are available, it’s passed in the two GARs; If only one GAR is available, the low-order bits are in the GAR and the high-order bits are on the stack; And it’s passed on the stack if no GAR is available.

... Others

.... The argument is passed in a pair of available GAR, with the low-order bits in the lower-numbered GAR and the high-order bits in the higher-numbered GAR.
If only one GAR is available, the low-order bits are in the GAR and the high-order bits are on the stack, and passed on the stack if no GAR is available.

. WOA > 2 × GRLEN

.. It’s passed by reference and are replaced in the argument list with the address.
If there is an available GAR, the reference is passed in the GAR, and passed on the stack if no GAR is available.

Structure and scalars passed on the stack are aligned to the greater of the type alignment and GRLEN bits, but never more than the stack alignment.

==== Union
Comment thread
Calring marked this conversation as resolved.

Union is passed in GAR or stack.

. 0 < WOA ≤ GRLEN

.. The argument is passed in a GAR, or on the stack by value if no GAR is available.

. GRLEN < WOA ≤ 2 × GRLEN

.. The argument is passed in a pair of available GAR, with the low-order bits in the lower-numbered GAR and the high-order bits in the higher-numbered GAR.
If only one GAR is available, the low-order bits are in the GAR and the high-order bits are on the stack.
The arguments are passed on the stack when no GAR is available.

. WOA > 2 × GRLEN

.. It’s passed by reference and are replaced in the argument list with the address.
If there is an available GAR, the reference is passed in the GAR, and passed on the stack if no GAR is available.

==== Complex

A complex floating-point number, or a structure containing just one complex floating-point number, is passed as though it were a structure containing two floating-point reals.

==== Variadic arguments

Variadic arguments are passed in GARs in the same manner as named arguments. And after a variadic argument has been passed on the stack, all future arguments will also be passed on the stack, i.e., the last argument register may be left unused due to the aligned register pair rule.

. 0 < WOA ≤ GRLEN

.. The variadic arguments are passed in a GAR, or on the stack by value if no GAR is available.

. GRLEN < WOA ≤ 2 × GRLEN

.. The variadic arguments are passed in a pair of GARs. If only one GAR is available, the low-order bits are in the GAR and the high-order bits are on the stack, and passed on the stack if no GAR is available. or on the stack by value if none is available. It should be noted that `long double` data tpye is passed in an aligned GAR pair(the first register in the pair is even-numbered).

. WOA > 2 × GRLEN

.. It’s passed by reference and are replaced in the argument list with the address.
If there is an available GAR, the reference is passed in the GAR, and passed on the stack if no GAR is available.

=== Return values

. Generally speaking, `a0` and `a1` are used to return non floating-point values, and `fa0` and `fa1` are used to return floating-point values.

. Values are returned in the same manner as a first named argument of the same type would be passed.
If such an argument would have been passed by reference, the caller allocates memory for the return value, and passes the address as an implicit first argument.

. The reference of the return value is returned that is stored in GAR `a0` if the size of the return value is larger than 2×GRLEN bits.

=== Stack

. In general, the stack frame for a subroutine may contain space to contain the following:

.. Space to store arguments passed to subroutines that this subroutine calls.

.. A place to store the subroutine’s return address.

.. A place to store the values of saved registers.

.. A place for local data storage.

. The stack grows downwards (towards lower addresses) and the stack pointer shall be aligned to a 128-bit boundary upon procedure entry.
The first argument passed on the stack is located at offset zero of the stack pointer on function entry; following arguments are stored at correspondingly higher addresses.

. Procedures must not rely upon the persistence of stack-allocated data whose addresses lies below the stack pointer.