-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for 128-bit integers #1504
Conversation
👍 They were originally removed citing the lack of compiler backend support, however this was incorrect, they are completely supported by LLVM, so there should not be anything blocking it. |
AFAIR, the actual (underlying) reason was LLVM’s support for operations on f128 being slightly(?) broken. Since the 128 floats were being removed, 128 integers went along with it. |
I don't think Rust has ever had support for 128-bit integers. The f128 stuff was completely separate. |
Right, I misread the title as 128-bit floats. |
# Detailed design | ||
[design]: #detailed-design | ||
|
||
From a quick look at Clang's source, 128-bit integers are supported on all 64-bit platforms and a few 32-bit ones (those with 64-bit registers: x32 and MIPS n32). To allow users to determine whether 128-bit integers are available, a `target_has_int128` cfg is added. The `i128` and `u128` types are only available when this flag is set. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LLVM supports arbitrary sized integers (and operations on them) and will software-emulate operations on them if necessary – as inefficient as they get. LLVM produces perfectly correct assembly even on targets where clang claims there’s no support, so looking at what clang does is not the way to go here IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Furthermore, the emulation (which might or might not happen) is often performing pretty decent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just ran a few quick tests on LLVM bitcode with i128
and i192
(test.ll), here are my results (LLVM 3.7.1):
add
andsub
are compiled down to a sequence of add with carry instructions for bothi128
andi192
.mul
andudiv
are compiled to a call to__multi3
and__udivti3
fori128
. These functions are implemented incompiler-rt
.mul
andudiv
generate an LLVM error fori192
:LLVM ERROR: Unsupported library call operation!
compiler-rt
contains variations of each function for 32, 64 and 128-bit integers (__mulsi3
,__muldi3
,__multi3
). However, the 128-bit versions are only present on 64-bit platforms (#ifdef __LP64__
).
In conclusion, LLVM doesn't support many operations on arbitrary sized integers. While we could in theory support 128-bit integers on all architectures, this would require extending compiler-rt
on 32-bit architectures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aha! I didn’t think of multiplication. How silly of me. I’m not too surprised about i192
– it’s not technically a power-of-two-bytes, but it certainly comes across as strange to me that there’s no generic-implementation-in-C (even if not fast at all) for more complex operations like multiplication for at least i128.
All in all, I’m all +1 for the general idea, but I’m not so keen on following what clang does. The RFC states the integers are not (can’t be?) supported on certain platforms. If there’s specific examples of such platforms, it could be good to know some, because otherwise it seems like LLVM’s emulation covers the ground. |
As I said in my comment above, it seems that LLVM's emulation does not cover all the needed operations. |
I've updated the RFC to support 128-bit integers on all architectures, even 32-bit ones. This will require implementing some functions that |
# Drawbacks | ||
[drawbacks]: #drawbacks | ||
|
||
One possible complication is that primitive types aren't currently part of the prelude, instead they are directly added to the global namespace by the compiler. The new `i128` and `u128` types will behave differently and will need to be explicitly imported. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's still possible to improve name resolution for primitive types and prelude backward compatibly.
I'd define the new i128
/u128
in some dedicated module like core::primitives
, there's a good chance other primitive types will be defined there as well in some time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding primitive types is a backwards compatible thing to do (for 99.999% of all code).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'll break all the i128
/u128
emulations using the same names, like https://crates.io/crates/extprim, and their dependencies. (Also, using playpen often prevents posting misinformation.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
u128, i128 are not keywords, so that one is ruled out. However, defining types shadowing primitive types results in E0317, however I think this is justified, since the crates depending on crates using these emulations can easily drop the dependencies, without changing any code (except use
and extern crate
).
👍 |
Yes please, I'd love to have 128 bit integers. |
It could be behind stable feature gate that would give user more descriptive output when compiling on unsupported platform. |
That shouldn't be needed, this RFC proposes adding 128-bit integers for all architectures. LLVM is capable of doing this if we implement some runtime library functions. |
Seems good to me. I'd love to have 128 bit integers in Rust (if we're sure we're able to support it on all platforms). |
what are the advantages of this RFC over the emulations we currently have? |
@gnzlbg Performance on supported platforms. It will be an order of magnitude faster in some cases. |
That x86-64 and some other CPUs already have native support for 128-bit integers. IIRC Intel provides support for 256 and 512-bit integers in their new procesors. Łukasz Jan Niemier Dnia 23 lut 2016 o godz. 08:45 gnzlbg [email protected] napisał(a):
|
Aren’t you confusing it with the AVX/SSE? I’m not aware of any features/instructions even in the newest intel processors which would allow for operations on 128/256/512-bit integers. |
There's at least divq, idivq, mulq, imulq on all x86-64 for 128 bit integer multiplication/division with 64 bit operands |
@sdroege Right, x86 does multiplication and division on double-word-sized operands to allow for all results and operands to fit. This is true all the way back to the 8086 where multiplication result was What you cannot do, though, is given a 128bit number in |
@nagisa True (well, it's not available in old 32 bit x86, is it?). Fortunately that's all I need for my own use case here https://github.com/sdroege/rust-muldiv But as long as LLVM emulates i128/u128 and/or all (other) operations are implemented in Rust for now, this seems like a useful feature to have |
@gnzlbg because |
Oh, I thought llvm would offer intrinsics for 128bit integers in the same way it does offer intrinsics for SIMD. |
We can just copy/port the existing implementations in compiler-rt.
I'm not quite sure, but my best guess would be that it was done this way in Clang for compatibility with GCC. |
Regarding common C impls not having 128 bit integers when targeting 32bit machines, it might be a gambit to avoid enlarging the size of |
That's not it since uintmax_t is a 64-bit type on all systems (at least for GCC and LLVM). My guess is that GCC just felt that supporting 128-bit integers on 32-bit systems was too much trouble. |
@Amanieu huh, I didn't expect that. Thinking it was too much trouble sounds like a reasonable guess. Perhaps they were worried someone might then want 256-bit integers on 64 bit machines? :) |
The linked atomic thread explains this in detail, but this currently blocks CC @ezrosent |
This RFC is now entering is final comment period. |
I personally feel that the addition of Implementation-wise I would predict that this is going to take a lot of work to do well. Much of this RFC leans on "compiler-rt will solve our problems" but historically compiler-rt has been very very difficult to work with, especially when it comes to portability. It seems that the correctness of a number of operations (like multiplication) would rely on the implementations in compiler-rt, which I'm not sure how well battle-tested they are in LLVM. That being said, I don't personally feel that it should necessarily block this RFC per se. I would still prefer to have a relatively high quality implementation before merging (as opposed to just adding a bunch of "if bits == 128" cases in the compiler), but that's a matter for another time! |
I'm in agreement with @alexcrichton. To clarify the process here, I think we can accept the RFC but with some specific stipulations about the implementation, which perhaps @alexcrichton could elaborate. |
Note that as it is currently written, this RFC does not propose modifying the compiler-rt code. Instead some functions will need to be implemented in Rust because they are required by LLVM on 32-bit platforms and not provided by compiler-rt on those platforms. |
However, this meshes well with the plans discussed (though by no means made official, IIUC) in #34400: Eventually replacing compiler-rt with pure Rust code. |
My threshold for "this is feature-complete enough to merge as unstable" would be to get a program like past our CI (x86/x86_64 on three major platforms, arm Android on one): fn main() {
let a: i128 = 1;
println!("{}", a);
println!("{}", a + 2);
println!("{}", a + a);
println!("{}", a * a);
println!("{}", a / 5);
println!("{}", a / -1);
println!("{}", a << 127);
assert_eq!(a as u64, 1);
assert_eq!(a, 1u32 as i128);
let a: u128 = 147573952589676412928; // 2^67
println!("{}", a);
assert_eq!(a, a);
} Basically:
|
I agree with @alexcrichton's comments; and of course we can also use the stability bar to gate if we feel there are flaws in the implementation, I imagine. |
I started working on an implementation of this, but progress has stalled since I'm going to be very busy finishing my thesis in the next few months. In any case the work didn't seem too complicated, it's just a matter of replacing most uses of u64 in the compiler with u128 and providing a emulated u128/i128 types for stage0. |
Huzzah! The @rust-lang/lang team has decided to accept this RFC. |
Tracking issue: rust-lang/rust#35118 If you'd like to keep following the development of this feature, please subscribe to that issue, thanks! :) |
Relevant issue: #521
Rendered
[edited to link to final rendered version]