Skip to content

Conversation

@kito-cheng
Copy link
Collaborator

_BitInt (N) is the type defined in C23, allow user to define an arbitrary-sized integer type, where N is a postive integer larger than zero.

This proposal defined the size and alignment of _BitInt, and define the unused bits as unspecified which is same as x86-64 and AArch64.

For the calling convention part, we keep unused bits as unspecified.

Ref:

Fix #300

@kito-cheng
Copy link
Collaborator Author

@kito-cheng
Copy link
Collaborator Author

Plan to putting more table for the code gen among 3 different options, I was too optimistic that I could have time to doing that...:( anyway will put that once I have one.

@kito-cheng
Copy link
Collaborator Author

I just realized I didn't have enough bandwidth on this, so I asked my colleague @BeMg to help on this.

We plan to did few more comparison and analysis for moving this forward.

@BeMg
Copy link

BeMg commented Jun 18, 2025

Here is a comparison of three strategies for how to deal with unused bits for bitInt:

  1. Unspecified
  2. Sign-extend
  3. Zero-extend

This table shows the number of instructions for common operations between different strategies.

  unspecific signed-extend zero-extend
Sum 326 300 306

And the following table shows the number of instructions for each operation.

unspecific bitint(65)  signed bitint(65)  unsigned bitint(65)  Sum
add 5 5 5 15
sub 5 5 5 15
mul 7 7 7 21
div 10 8 10 28
eq 6 6 6 18
ge 11 9 11 31
gt 9 7 9 25
le 11 9 11 31
lt 9 7 9 25
mod 10 8 10 28
shift_left 15 15 15 45
shift_right 14 16 14 44
Sum 112 102 112 326
signed-extend bitint(65)  signed bitint(65)  unsigned bitint(65)  Sum
add 7 7 7 21
sub 7 7 7 21
mul 9 9 9 27
div 8 10 8 26
eq 5 5 5 15
ge 7 7 7 21
gt 5 5 5 15
le 7 7 7 21
lt 5 5 5 15
mod 8 10 8 26
shift_left 17 17 17 51
shift_right 12 17 12 41
Sum 97 106 97 300
zero-extend bitint(65)  signed bitint(65)  unsigned bitint(65)   Sum
add 6 6 6 18
sub 6 6 6 18
mul 8 8 8 24
div 9 7 9 25
eq 5 5 5 15
ge 9 7 9 25
gt 7 5 7 19
le 9 7 9 25
lt 7 5 7 19
mod 9 7 9 25
shift_left 16 16 16 48
shift_right 15 15 15 45
Sum 106 94 106 306

Also, here is the doc that includes the detailed assembly and the C source code for reference.

cc @kito-cheng

@topperc
Copy link
Contributor

topperc commented Jul 1, 2025

Here is a comparison of three strategies for how to deal with unused bits for bitInt:

  1. Unspecified
  2. Sign-extend
  3. Zero-extend

What if we use sign-extend strategy for signed BitInts and zero-extend strategy for unsigned BitInts as suggested here #419 (comment) This would match what we do for uint16_t/uint8_t/int16_t/uint16_t. That should give the shortest sequence for shift_right, div, mod, and compares.

@BeMg
Copy link

BeMg commented Jul 4, 2025

Updated with more combination code generation results

There are four kinds of configurations for BitInt with three different ABI policies:

  1. BitInt(65)
  2. BitInt(17)
  3. BitInt(76)
  4. BitInt(66)

And here is the table summarizing the number of assembly instructions

  unspecific signed-extend zero-extend
BitInt(65) 206 195 192
BitInt(66) 209 200 207
BitInt(76) 224 205 220
BitInt(17) 114 88 105

The details of the sheet can be found here

cc @kito-cheng

@BeMg
Copy link

BeMg commented Jul 4, 2025

Here is a comparison of three strategies for how to deal with unused bits for bitInt:

  1. Unspecified
  2. Sign-extend
  3. Zero-extend

What if we use sign-extend strategy for signed BitInts and zero-extend strategy for unsigned BitInts as suggested here #419 (comment) This would match what we do for uint16_t/uint8_t/int16_t/uint16_t. That should give the shortest sequence for shift_right, div, mod, and compares.

Update: Add mix type extend

BitInt(65)
BitInt(17)
BitInt(76)
BitInt(66)

  unspecific signed-extend zero-extend mix-extend
BitInt(65) 206 195 195 183
BitInt(66) 209 200 200 187
BitInt(76) 224 205 205 193
BitInt(17) 114 88 88 73

And detail instruction count sheet

cc @topperc @kito-cheng

@kito-cheng
Copy link
Collaborator Author

According to @BeMg's experiments, doing the same kind of extension as the original data type gives the best code gen performance, so I'm prefer that. The only issue is that BitInt(32) on RV64 wouldn’t match the behavior of a normal unsigned/uint32_t. and there are two options:

  • Let BitInt(32) differ from unsigned/uint32_t in order to keep BitInt's ABI consistent.
  • Make BitInt(32) match unsigned/uint32_t to stay consistent with the Integer ABI.

Let me know if anyone has any preferecne or comment, otherwise we will decide that in next psABI meeting and moving forward :)

@lenary
Copy link
Contributor

lenary commented Aug 14, 2025

It was raised at the psabi tg that special casing N=32 might make the libgcc/compiler-rt implementations more difficult, as they would need to be implemented differently. I asked whether there were separate implementations for different N, or a shared implementation.

According to the docs, it seems the answer is there is a shared implementation: https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html#Bit-precise-integer-arithmetic-functions

This also does not use the conventional ABI, it uses a buffer+length encoding, which avoids the "does this fit into a register" questions usually present.

We probably still need to agree on __LIBGCC_BITINT_LIMB_WIDTH__ and __LIBGCC_BITINT_ORDER__ for the ABI though.

@kito-cheng
Copy link
Collaborator Author

Changes:

  • Adjust the chunk size is 64 (XLEN*2) for RV32
  • Tweak description for the endianness, N <= 2 * XLEN will follow the scalar endianness rule, and 2×XLEN chunk are always stored in memory with lower-addressed chunks containing less significant bits, regardless of the system endianness, each chunk will follow the scalar endianness rule.
  • Adjust the unused bit to use the same signedness as input type.

@kito-cheng
Copy link
Collaborator Author

Changes:

@kito-cheng
Copy link
Collaborator Author

Also some survey on GCC implementation:

LE BE Comment
x86 LE LE I guess x86 BE is not maintained?
s390 x BE
AArch64 LE BE But bitint support for BE is disabled for trunk GCC
loongarch LE x No big endian support.

@jrtc27
Copy link
Collaborator

jrtc27 commented Oct 9, 2025

Where are you testing _BitInt for s390x? I can't get it to work on godbolt.org, so I'm not sure where you're getting the information that it orders the chunks from most to least significant. Is that only in trunk or something?

@jrtc27
Copy link
Collaborator

jrtc27 commented Oct 9, 2025

As for which makes more sense, least to most significant is the sensible ordering for the chunks, as if you're doing, say, addition, you need to iterate from least to most significant chunk, no?

@xypron
Copy link

xypron commented Oct 9, 2025 via email

BeMg added a commit to llvm/llvm-project that referenced this pull request Oct 14, 2025
Implement riscv-non-isa/riscv-elf-psabi-doc#419.

This patch makes the type extension based on the variable type for
BIGINT, rather than using sign extension for all cases.
llvm-sync bot pushed a commit to arm/arm-toolchain that referenced this pull request Oct 14, 2025
Implement riscv-non-isa/riscv-elf-psabi-doc#419.

This patch makes the type extension based on the variable type for
BIGINT, rather than using sign extension for all cases.
akadutta pushed a commit to akadutta/llvm-project that referenced this pull request Oct 14, 2025
Implement riscv-non-isa/riscv-elf-psabi-doc#419.

This patch makes the type extension based on the variable type for
BIGINT, rather than using sign extension for all cases.
`_BitInt (N)` is the type defined in C23, allow user to define an
arbitrary-sized integer type, where N is a postive integer larger than zero.

This proposal defined the size and alignment of _BitInt, and define the
unused bits as unspecified which is same as x86-64 and AArch64.

For the calling convention part, we keep unused bits as unspecified.

Ref:

- ISO/IEC WG14 N2763:
  https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2763.pdf

- [AArch64] Rationale Document for ABI related to the C23 _BitInt type.
  https://github.com/ARM-software/abi-aa/tree/main/design-documents/bit-precise-types.rst

- AAPCS64 for _BitInt(N)
  ARM-software/abi-aa@d621417

- x86-64 ABI for _BitInt(N)
  https://gitlab.com/x86-psABIs/x86-64-ABI/-/commit/8ca45392570e96920f8a15d903d6122f6d263cd0

Fix #300
Changed _BitInt(N) unused bits from undefined to use sign/zero extension
based on the type's signedness. This makes the behavior more predictable
and consistent with the type semantics.

- Signed _BitInt(N): unused bits are sign-extended
- Unsigned _BitInt(N): unused bits are zero-extended
- Added note clarifying that _BitInt defaults to signed per C spec
- Clarified passing rules for different sized _BitInt types
Updated _BitInt(N) endianness rules to be more precise:
- N ≤ 2×XLEN: follows standard scalar endianness
- N > 2×XLEN: divided into 2×XLEN chunks, each chunk follows scalar
  endianness, but chunks are always stored lower-first regardless
  of system endianness
- Clarify that little-endian chunk ordering is specified explicitly
- Move big-endian behavior to a separate NOTE section
- Document that big-endian ABI is experimental and may change
- Chunk ordering now follows system endianness consistently
- Little-endian: lower-addressed chunks contain less significant bits
- Big-endian: lower-addressed chunks contain more significant bits
- Retain note that big-endian ABI is experimental
@kito-cheng
Copy link
Collaborator Author

Changes:

  • Adding special rule for _BitInt(32) on RV64 to make sure it consistent with int/int32_t

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Define _BitInt ABI