Numbers in Rust

Hello world. In many languages, It's not possible to use an i64 on a 32bits architecture, or an i128 on a 64bits arch. Is it also true in Rust ?

1 Like

No, it's not true. The core integer types are always available, but they are emulated in software if necessary. (LLVM takes care of all of this, and it's pretty good at generating optimized code for wider-than-native types that's only slightly slower.)

In the future, you can find ou the answer to this kind of question for yourself, e.g. using Godbolt.

6 Likes

Rust offers all integer types all the way up to 128-bit ones on all architectures. As far as I’m aware, for architectures that don’t natively support the larger types, the values will be put into multiple registers and calculations will proceed in multiple steps to emulate the same behavior.

6 Likes

I'm wondering in what languages this true?

1 Like

in C. No ?

Maybe i'm wrong.

Godbolt's x64 GCC version (whatever it may be) is perfectly happy to compile __i128, although it generates somewhat longer code than LLVM.

Then is it necessary to compile seperatly a 32 and a 64 bit program ?

I don't understand what exactly you mean by that. Certainly, a 32 and a 64 bit platform are still different, so they will run different executables, if that's what you are asking. You can't just run a 64-bit executable on a 32-bit platform.

Out of interest, I walked through a few of the 128-bit operations on normal 64-bit architectures. Examples compiled on godbolt:

pub fn f(x: i128, y: i128) -> i128 {
    x + y
}
example::f:
        mov     rax, rdi
        add     rax, rdx
        adc     rsi, rcx
        mov     rdx, rsi
        ret

pub fn f(x: i128, y: i128) -> i128 {
    x * y
}
example::f:
        mov     r8, rdx
        mov     rax, rdx
        mul     rdi
        imul    rsi, r8
        add     rdx, rsi
        imul    rcx, rdi
        add     rdx, rcx
        ret

or, with -C target-cpu=native

example::f:
        imul    rsi, rdx
        mulx    rdx, rax, rdi
        add     rdx, rsi
        imul    rcx, rdi
        add     rdx, rcx
        ret

pub fn f(x: u128, y: u128) -> u128 {
    x / y
}
example::f:
        push    rax
        mov     rax, rdx
        or      rax, rcx
        je      .LBB0_2
        call    qword ptr [rip + __udivti3@GOTPCREL]
        pop     rcx
        ret
.LBB0_2:
        lea     rdi, [rip + str.0]
        lea     rdx, [rip + .L__unnamed_1]
        mov     esi, 25
        call    qword ptr [rip + core::panicking::panic@GOTPCREL]
        ud2

.L__unnamed_2:
        .ascii  "/app/example.rs"

.L__unnamed_1:
        .quad   .L__unnamed_2
        .asciz  "\017\000\000\000\000\000\000\000\002\000\000\000\005\000\000"

str.0:
        .ascii  "attempt to divide by zero"

pub fn f(x: i128, y: i128) -> i128 {
    x / y
}
example::f:
        push    rax
        mov     rax, rdx
        or      rax, rcx
        je      .LBB0_4
        movabs  rax, -9223372036854775808
        xor     rax, rsi
        or      rax, rdi
        jne     .LBB0_5
        mov     rax, rdx
        and     rax, rcx
        cmp     rax, -1
        je      .LBB0_3
.LBB0_5:
        call    qword ptr [rip + __divti3@GOTPCREL]
        pop     rcx
        ret
.LBB0_4:
        lea     rdi, [rip + str.0]
        lea     rdx, [rip + .L__unnamed_1]
        mov     esi, 25
        call    qword ptr [rip + core::panicking::panic@GOTPCREL]
        ud2
.LBB0_3:
        lea     rdi, [rip + str.1]
        lea     rdx, [rip + .L__unnamed_1]
        mov     esi, 31
        call    qword ptr [rip + core::panicking::panic@GOTPCREL]
        ud2

.L__unnamed_2:
        .ascii  "/app/example.rs"

.L__unnamed_1:
        .quad   .L__unnamed_2
        .asciz  "\017\000\000\000\000\000\000\000\002\000\000\000\005\000\000"

str.0:
        .ascii  "attempt to divide by zero"

str.1:
        .ascii  "attempt to divide with overflow"

right… that’s kinda long, looks like it handles division by zero, overflow, and signs, then calls an existing function called __divti3

3 Likes

As far as I know C supports all its integer widths on all platforms: C data types - Wikipedia

Although 128 bit is not specified in the C standard but may be provided my an implementation's language extensions.

Of course things like 'int' change width depending on implementation. Rather like Rust's 'isize' and 'usize'.

1 Like

Thank you for the examples

Thanks to all you for the explanations. Please put up with my stupid questions. I come from the web universe. I still have a lot to learn to become a full system programmer. :wink:

The bit width of the platform refers to the width of native memory pointers. 32-bit platforms use 32-bit pointers and thus may address at most 2^32 = 4294967296 bytes of memory. 64-bit platforms can similarly address at most 2^64 = 18446744073709551616 bytes of memory. In practice these will be lower, e.g. Windows reserves an entire half of address space for its purposes on 32-bit systems, while on 64-bit systems the upper bits are reserved by processor architectures, or the total memory may be restricted by the OS (link).

This in unrelated to the width of supported integer types, although in practice the CPU integer register sizes are often the same as its pointer size. Regardless, the compiler can emulate any integer type in software, so the concern about CPU integer register size is mostly about performance optimization.

2 Likes

Hmmm... Except, back in the day 8 bit machines had 16 bit pointers so as to be able to access a typical 64KByte memory space. Actually they still do in the form of Atmel, Microchip and other micro-controllers.

The 16 bit Intel 8086 had 16 bit data registers but FAR pointers are 24 bits.

The Motorola 68000 has 32 bit data registers and instructions but only 24 bit address registers.

My whole life I have interpreted a processors "bit width" as the width of data processors ALU worked on.

3 Likes

That, in turn, is complicated by superscalar architectures, but I guess one could slightly amend your definition to use the phrase "width of the largest scalar the ALU can work with" or similar. In any case, I would think that at least since 1990 or so, all mainstream architectures have had scalar width equal to address width, no?

1 Like

You are probably right. I gave up following what is inside the new fangled processors since about 1990 :slight_smile: