Minimal acceptable integer size - i32min, u8min, etc

When developing a library that can be used on different cpu architectures, it would be very beneficial if the programmers could easily specify and track the minimum required integer size while still using the more efficient cpu integer size (register width) size for their processor.

Specifically, many programmers will specify a u64 even when a u8 or u16 would work.

Later, when that code is used in a smaller cpu, say an 8 or 32 bit cpu to make the point, the software gets unnecessarily slower because it's processing 64 bit variables even when the value knowingly needs less space.

The proposal:

Add more integer types:

i8min, u8min, i16min, u16min, or i8min, u8min, i16min, etc. The compiler could enlarge them to the optimal width for alignment. This might even be helpful with f32 and f64's. f64s run very much slower on hardware that doesn't support an f64 math co-processor.

In debug mode, the same bounds checking could be applied for the chosen integer size, but release code would run more efficiently on both big and small processors.

It goes nearly without saying that this is most helpful when the author of the library uses the smallest type possible. This feature will be very important for the use of rust in 8 bit cpus.

This message was heavily edited to reduce confusion. Originally it was suggested to call the variable u8arch for example which caused some understandable confusion.

1 Like

I suspect that you have under-described your suggested additions. Which uN is equivalent to u16arch for an architecture with

  1. usize = 8?
  2. usize = 16?
  3. usize = 32?
  4. usize = 64?
  5. usize = 128?

Note that the above numerical index is log2(usize/8)+1.

I'm still trying to understand the original suggestion. The thread title, with the non-word "reducable", is not helping. The reference to float types, which in Rust will eventually likely include f16, fb16, f32, f64, f128, and perhaps f256, is just causing me more confusion. Please try to restate the proposal.

Are you asking for type names that are related to architectural data-path width, similar to how usize and isize relate to architectural address width?

3 Likes

I must admit I don't understand what the proposal trying to do.

1 Like

A u16arch would only ever store numbers between 0 and 65535, but be implemented with a normal uarch as long as uarch was u16 or larger.

On a 64 bit cpu, it would be implemented as a 64 bit uarch. ("safety" debug code would ensure that it would never overflow a u16.)

On an 8 bit cpu, it would be implemented as a u16.

This datatype would allow programmers to better specify the minimum size requirement for variables, while allowing the more performant architecture sized variable to be used in practice.

You do realize that a u16 is almost certainly faster than a u64 on a 64 bit processor? Multiple u16s can fit in a cache line.

It seems that you are concerned that rustc's optimizer, usually LLVM, will fail to make the optimal choice of representation. That is not usually the case. If you want to program at the assembly level for selected architectures, as this and your contemporaneous Adding access to the carry flag bit thread imply, Rust supports doing that via intrinsics and an in-process RFC for a generic assembly language.

1 Like

It's also useful to realize that optimization for debug, optimization for minimal code space, and optimization for minimal execution time each may result in different code for a specific ISA (instruction set architecture) with specific ISA-enhancement options, and that a different ISA or different ISA-enhancement options often results in different code.

With regard to data-path width, in modern CPU architectures that can vary within the CPU, with different widths for non-address integers, addresses, floating point, vector, SIMD, etc.

2 Likes

Exactly. The type for a 16-bit value with the fastest representation on the target architecture is u16. It's the compiler's job to make that as fast as possible.

1 Like

Upon some reflection, the idea is to be able to specify a minimum size integer that is automatically upgraded to the arch size. I just changed the post title to better communicate the idea.

LLVM is experimenting with 8 bit code. Libraries that are used in various architectures would benefit from being able to use the arch size whenever possible.

So this topic is really about non-address integer-data-path width, with the presumption that there is only one such natural width in the ISA? If we look back to the IBM 360, we see that the CPU's integer data-path width varied from model to model, all with the same nominal "IBM 360" architecture. Doesn't that mean that, for this proposal to be useful, you would need the compiler to target a specific model CPU, rather than just the nominal CPU architecture?

Yes, it is about being able to specify the minimum integer-width in code that is designed to efficiently operate on a variety of cpu architectures after being compiled.

The actual cpu model won't matter, but the goal is for the same source code to generate performant code regardless of cpu architecture, whether it's an 8-bit microcontroller or a 64 bit server. That being said, it probably would have a problem fully utilizing all of the various IBM 360's if implemented.

This is less important if programmers are already using the smallest integer possible, but often I find programmers using 32 and 64 bit variables not realizing their code may someday be used on a 8 bit microcontroller.

It’s not clear to me how i8arch improves this situation. Switching from i64 to i8arch might improve performance on 8-bit platforms, while leaving it unchanged on 64-bit platforms. But switching to i8 instead would likely improve performance on all platforms. So it would be better to simply use i8.

As someone mentioned above, it seems like there may be an unstated assumption that on 64-bit processors, 64-bit operations are faster than 8-bit operations. This is not generally true.

If you have found code where a regular i8 is too expensive on some platform, it would be useful to see the exact benchmarks or generated assembly code that led you to this conclusion. Otherwise, we can’t tell what problem you are trying to solve. (If you don't have benchmarks showing a problem, then proposing a solution seems premature.)

11 Likes

Rust doesn't support architectures with an 8-bit usize.

1 Like

First, I'm certain you understand the point of the list in my query. Second, the thread originator had previously said

so the fact that Rust today does not support an 8-bit usize is no guarantee that it won't tomorrow. It already does support microcontrollers with 8-bit data paths such as AVR.

C had uint_fast32_t, and AFAIK that never got any traction, and is generally considered worthless.

That's because whether a type is fast or not depends mainly on context where it's used. For example for SIMD the smaller the better, because you can fit more items into the SIMD registers. Same for memory I/O.

If it's just a type alias with size_of::<u16arch>() == size_of::<usize>() then I don't think it'd be helpful.

I think this could work only if it was a completely abstract type that can change sizes at any moment whenever optimizer wants it to (e.g. a 16-bit value loaded from memory into a 64-bit register, used in the register without preserving 16-bit overflow semantics). That would be a big departure from Rust's strict fixed type sizes.

6 Likes

The thread originator made a terrible mistake using the ARCH term in this thread. I've heavily edited the title and first message to better explain this idea is to suggest a minimum data integer size, or auto-enlarging data integers to the size of a system register/efficient data alignment, and not necessarily to the isize/usize size of addressable memory.

Additionally, I was thinking of micro cpu's that have an 8 bit data path, not a micro architecture with an 8 bit usize 256 or less bytes of addressable program space.

Effects of Caching

There may be a battle between memory/register alignment, which could benefit from enlarging some integers, and simd instructions and working on the cache line, which supports using the smallest datatype possible. My current understanding is that different cpu architectures have advantages one way or the other.

Yes, Kornel, the idea is that the type would change size, but the thought is that it would be a virtual type that would only change size during compilation based on the cpu register width and the optimal width for efficient data alignment.

The original idea was that rust would still support overflow detection for the stated minimum integer size, not the expanded size.

I still don't know why you'd want to use a larger integer than necessary. As I previously stated, smaller integers are more efficient, even on larger-bit architectures.

1 Like

We actually do guarantee that we will never support it. There is a From<u8> and From<u16> impl for usize, but not From<u32> or bigger.

3 Likes

That may not always be true on all architectures. Kornel pointed out the uint_fast32_t which was a 32 bit int that could be expanded to a 64 bit int intended to solve the same issue in the c language, and that failed.

However, it's clear there isn't a compelling use case at this time.

Thanks