From c3b4a8d6b4063528a9880cc88c9531f10a911e62 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Sun, 21 Feb 2016 02:39:06 +0000 Subject: [PATCH 1/6] Add support for 128-bit integers --- text/0000-int128.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 text/0000-int128.md diff --git a/text/0000-int128.md b/text/0000-int128.md new file mode 100644 index 00000000000..55db47159ff --- /dev/null +++ b/text/0000-int128.md @@ -0,0 +1,42 @@ +- Feature Name: int128 +- Start Date: 21-02-2016 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +This RFC adds the `i128` and `u128` types to Rust. Because these types are not available on all platforms, a new target flag (`target_has_int128`) is added to allow users to check whether 128-bit integers are supported. The `i128` and `u128` are not added to the prelude, and must instead be explicitly imported with `use core::{i128, u128}`. + +# Motivation +[motivation]: #motivation + +Some algorithms need to work with very large numbers that don't fit in 64 bits, such as certain cryptographic algorithms. One possibility would be to use a BigNum library, but these use heap allocation and tend to have high overhead. LLVM has support for very efficient 128-bit integers, which are exposed by Clang in C as the `__int128` type. + +# 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. + +The actual `i128` and `u128` types are not added to the Rust prelude since that would break compatibility. Instead they must be explicitly imported with `use core::{i128, u128}` or `use std::{i128, u128}`. This will also catch attempts to use 128-bit integers when they are not supported by the underlying platform since the import will fail if `target_has_int128` is not defined. + +Implementation-wise, this should just be a matter of adding a new primitive type to the compiler and adding trait implementations for `i128`/`u128` in libcore. A new entry will need to be added to target specifications to specify whether the target supports 128-bit integers. + +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. + +Another possible issue is that a `u128` can hold a very large number that doesn't fit in a `f32`. We need to make sure this doesn't lead to any `undef`s from LLVM. + +# Drawbacks +[drawbacks]: #drawbacks + +It adds a type to the language that may or may not be present depending on the target architecture. + +# Alternatives +[alternatives]: #alternatives + +There have been several attempts to create `u128`/`i128` wrappers based on two `u64` values, but these can't match the performance of LLVM's native 128-bit integers. + +# Unresolved questions +[unresolved]: #unresolved-questions + +How should 128-bit literals be handled? The easiest solution would be to limit integer literals to 64 bits, which is what GCC does (no support for `__int128` literals). From ad25344d63ca3a787f6cc739cfcdcba6fe9ac5be Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Mon, 22 Feb 2016 01:00:52 +0000 Subject: [PATCH 2/6] Change RFC to supporting 128-bit integers on all architectures --- text/0000-int128.md | 60 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/text/0000-int128.md b/text/0000-int128.md index 55db47159ff..1d12a9f93df 100644 --- a/text/0000-int128.md +++ b/text/0000-int128.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -This RFC adds the `i128` and `u128` types to Rust. Because these types are not available on all platforms, a new target flag (`target_has_int128`) is added to allow users to check whether 128-bit integers are supported. The `i128` and `u128` are not added to the prelude, and must instead be explicitly imported with `use core::{i128, u128}`. +This RFC adds the `i128` and `u128` types to Rust. The `i128` and `u128` are not added to the prelude, and must instead be explicitly imported with `use core::{i128, u128}`. # Motivation [motivation]: #motivation @@ -16,20 +16,56 @@ Some algorithms need to work with very large numbers that don't fit in 64 bits, # 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. - -The actual `i128` and `u128` types are not added to the Rust prelude since that would break compatibility. Instead they must be explicitly imported with `use core::{i128, u128}` or `use std::{i128, u128}`. This will also catch attempts to use 128-bit integers when they are not supported by the underlying platform since the import will fail if `target_has_int128` is not defined. - -Implementation-wise, this should just be a matter of adding a new primitive type to the compiler and adding trait implementations for `i128`/`u128` in libcore. A new entry will need to be added to target specifications to specify whether the target supports 128-bit integers. - -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. - -Another possible issue is that a `u128` can hold a very large number that doesn't fit in a `f32`. We need to make sure this doesn't lead to any `undef`s from LLVM. +The `i128` and `u128` types are not added to the Rust prelude since that would break compatibility. Instead they must be explicitly imported with `use core::{i128, u128}` or `use std::{i128, u128}`. + +Implementation-wise, this should just be a matter of adding a new primitive type to the compiler and adding trait implementations for `i128`/`u128` in libcore. Literals will need to be extended to support `i128`/`u128`. + +LLVM fully supports 128-bit integers on all architectures, however it will emit calls to functions in `compiler-rt` for many operations such as multiplication and division (addition and subtraction are implemented natively). However, `compiler-rt` only provides the functions for 128-bit integers on 64-bit platforms (`#ifdef __LP64__`). We will need to provide our own implementations of the following functions to allow `i128`/`u128` to be available on all architectures: + +```c +// si_int = i32 +// su_int = u32 +// ti_int = i128 +// tu_int = u128 +ti_int __absvti2(ti_int a); +ti_int __addvti3(ti_int a, ti_int b); +ti_int __ashlti3(ti_int a, si_int b); +ti_int __ashrti3(ti_int a, si_int b); +si_int __clzti2(ti_int a); +si_int __cmpti2(ti_int a, ti_int b); +si_int __ctzti2(ti_int a); +ti_int __divti3(ti_int a, ti_int b); +si_int __ffsti2(ti_int a); +ti_int __fixdfti(double a); +ti_int __fixsfti(float a); +tu_int __fixunsdfti(double a); +tu_int __fixunssfti(float a); +double __floattidf(ti_int a); +float __floattisf(ti_int a); +double __floatuntidf(tu_int a); +float __floatuntisf(tu_int a); +ti_int __lshrti3(ti_int a, si_int b); +ti_int __modti3(ti_int a, ti_int b); +ti_int __muloti4(ti_int a, ti_int b, int* overflow); +ti_int __multi3(ti_int a, ti_int b); +ti_int __mulvti3(ti_int a, ti_int b); +ti_int __negti2(ti_int a); +ti_int __negvti2(ti_int a); +si_int __parityti2(ti_int a); +si_int __popcountti2(ti_int a); +ti_int __subvti3(ti_int a, ti_int b); +si_int __ucmpti2(tu_int a, tu_int b); +tu_int __udivmodti4(tu_int a, tu_int b, tu_int* rem); +tu_int __udivti3(tu_int a, tu_int b); +tu_int __umodti3(tu_int a, tu_int b); +``` # Drawbacks [drawbacks]: #drawbacks -It adds a type to the language that may or may not be present depending on the target architecture. +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. + +Another possible issue is that a `u128` can hold a very large number that doesn't fit in a `f32`. We need to make sure this doesn't lead to any `undef`s from LLVM. See [this comment](https://github.com/rust-lang/rust/issues/10185#issuecomment-110955148), and [this example code](https://gist.github.com/Amanieu/f87da5f0599b343c5500). # Alternatives [alternatives]: #alternatives @@ -39,4 +75,4 @@ There have been several attempts to create `u128`/`i128` wrappers based on two ` # Unresolved questions [unresolved]: #unresolved-questions -How should 128-bit literals be handled? The easiest solution would be to limit integer literals to 64 bits, which is what GCC does (no support for `__int128` literals). +None From 60926a68527186a3ac510cf422670626753739b9 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Tue, 8 Mar 2016 04:35:40 -0800 Subject: [PATCH 3/6] Remove unnecessary runtime library functions --- text/0000-int128.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/text/0000-int128.md b/text/0000-int128.md index 1d12a9f93df..3ebb408ced0 100644 --- a/text/0000-int128.md +++ b/text/0000-int128.md @@ -27,15 +27,9 @@ LLVM fully supports 128-bit integers on all architectures, however it will emit // su_int = u32 // ti_int = i128 // tu_int = u128 -ti_int __absvti2(ti_int a); -ti_int __addvti3(ti_int a, ti_int b); ti_int __ashlti3(ti_int a, si_int b); ti_int __ashrti3(ti_int a, si_int b); -si_int __clzti2(ti_int a); -si_int __cmpti2(ti_int a, ti_int b); -si_int __ctzti2(ti_int a); ti_int __divti3(ti_int a, ti_int b); -si_int __ffsti2(ti_int a); ti_int __fixdfti(double a); ti_int __fixsfti(float a); tu_int __fixunsdfti(double a); @@ -48,14 +42,6 @@ ti_int __lshrti3(ti_int a, si_int b); ti_int __modti3(ti_int a, ti_int b); ti_int __muloti4(ti_int a, ti_int b, int* overflow); ti_int __multi3(ti_int a, ti_int b); -ti_int __mulvti3(ti_int a, ti_int b); -ti_int __negti2(ti_int a); -ti_int __negvti2(ti_int a); -si_int __parityti2(ti_int a); -si_int __popcountti2(ti_int a); -ti_int __subvti3(ti_int a, ti_int b); -si_int __ucmpti2(tu_int a, tu_int b); -tu_int __udivmodti4(tu_int a, tu_int b, tu_int* rem); tu_int __udivti3(tu_int a, tu_int b); tu_int __umodti3(tu_int a, tu_int b); ``` From e019825d6b2b7bb4c8537bced61bd9d180cf6c2b Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 30 Mar 2016 22:20:01 +0100 Subject: [PATCH 4/6] Update RFC based on feedback --- text/0000-int128.md | 63 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/text/0000-int128.md b/text/0000-int128.md index 3ebb408ced0..7882f528b23 100644 --- a/text/0000-int128.md +++ b/text/0000-int128.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -This RFC adds the `i128` and `u128` types to Rust. The `i128` and `u128` are not added to the prelude, and must instead be explicitly imported with `use core::{i128, u128}`. +This RFC adds the `i128` and `u128` primitive types to Rust. # Motivation [motivation]: #motivation @@ -16,11 +16,29 @@ Some algorithms need to work with very large numbers that don't fit in 64 bits, # Detailed design [design]: #detailed-design -The `i128` and `u128` types are not added to the Rust prelude since that would break compatibility. Instead they must be explicitly imported with `use core::{i128, u128}` or `use std::{i128, u128}`. +## Compiler support -Implementation-wise, this should just be a matter of adding a new primitive type to the compiler and adding trait implementations for `i128`/`u128` in libcore. Literals will need to be extended to support `i128`/`u128`. +The first step for implementing this feature is to add support for the `i128`/`u128` primitive types to the compiler. This will requires changes to many parts of the compiler, from libsyntax to trans. -LLVM fully supports 128-bit integers on all architectures, however it will emit calls to functions in `compiler-rt` for many operations such as multiplication and division (addition and subtraction are implemented natively). However, `compiler-rt` only provides the functions for 128-bit integers on 64-bit platforms (`#ifdef __LP64__`). We will need to provide our own implementations of the following functions to allow `i128`/`u128` to be available on all architectures: +The compiler will need to be bootstrapped from an older compiler which does not support `i128`/`u128`, but rustc will want to use these types internally for things like literal parsing and constant propagation. This can be solved by using a "software" implementation of these types, similar to the one in the [extprim](https://github.com/kennytm/extprim) crate. Once stage1 is built, stage2 can be compiled using the native LLVM `i128`/`u128` types. + +## Runtime library support + +The LLVM code generator supports 128-bit integers on all architectures, however it will lower some operations to runtime library calls. This similar to how we currently handle `u64` and `i64` on 32-bit platforms: "complex" operations such as multiplication or division are lowered by LLVM backends into calls to functions in the `compiler-rt` runtime library. + +Here is a rough breakdown of which operations are handled natively instead of through a library call: +- Add/Sub/Neg: native, including checked overflow variants +- Compare (eq/ne/gt/ge/lt/le): native +- Bitwise and/or/xor/not: native +- Shift left/right: native on most architectures (some use libcalls instead) +- Bit counting, parity, leading/trailing ones/zeroes: native +- Byte swapping: native +- Mul/Div/Mod: libcall (including checked overflow multiplication) +- Conversion to/from f32/f64: libcall + +The `compiler-rt` library that comes with LLVM only implements runtime library functions for 128-bit integers on 64-bit platforms (`#ifdef __LP64__`). We will need to provide our own implementations of the relevant functions to allow `i128`/`u128` to be available on all architectures. Note that this can only be done with a compiler that already supports `i128`/`u128` to match the calling convention that LLVM is expecting. + +Here is the list of functions that need to be implemented: ```c // si_int = i32 @@ -46,17 +64,46 @@ tu_int __udivti3(tu_int a, tu_int b); tu_int __umodti3(tu_int a, tu_int b); ``` +Implementations of these functions will be written in Rust and will be included in libcore. + +## Modifications to libcore + +Several changes need to be done to libcore: +- `src/libcore/num/i128.rs`: Define `MIN` and `MAX`. +- `src/libcore/num/u128.rs`: Define `MIN` and `MAX`. +- `src/libcore/num/mod.rs`: Implement inherent methods, `Zero`, `One`, `From` and `FromStr` for `u128` and `i128`. +- `src/libcore/num/wrapping.rs`: Implement methods for `Wrapping` and `Wrapping`. +- `src/libcore/fmt/num.rs`: Implement `Binary`, `Octal`, `LowerHex`, `UpperHex`, `Debug` and `Display` for `u128` and `i128`. +- `src/libcore/cmp.rs`: Implement `Eq`, `PartialEq`, `Ord` and `PartialOrd` for `u128` and `i128`. +- `src/libcore/nonzero.rs`: Implement `NonZero` for `u128` and `i128`. +- `src/libcore/iter.rs`: Implement `Step` for `u128` and `i128`. +- `src/libcore/clone.rs`: Implement `Clone` for `u128` and `i128`. +- `src/libcore/default.rs`: Implement `Default` for `u128` and `i128`. +- `src/libcore/hash/mod.rs`: Implement `Hash` for `u128` and `i128` and add `write_i128` and `write_u128` to `Hasher`. +- `src/libcore/lib.rs`: Add the `u128` and `i128` modules. + +## Modifications to libstd + +A few minor changes are required in libstd: +- `src/libstd/lib.rs`: Re-export `core::{i128, u128}`. +- `src/libstd/primitive_docs.rs`: Add documentation for `i128` and `u128`. + +## Modifications to other crates + +A few external crates will need to be updated to support the new types: +- `rustc-serialize`: Add the ability to serialize `i128` and `u128`. +- `serde`: Add the ability to serialize `i128` and `u128`. +- `rand`: Add the ability to generate random `i128`s and `u128`s. + # 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. - -Another possible issue is that a `u128` can hold a very large number that doesn't fit in a `f32`. We need to make sure this doesn't lead to any `undef`s from LLVM. See [this comment](https://github.com/rust-lang/rust/issues/10185#issuecomment-110955148), and [this example code](https://gist.github.com/Amanieu/f87da5f0599b343c5500). +One possible issue is that a `u128` can hold a very large number that doesn't fit in a `f32`. We need to make sure this doesn't lead to any `undef`s from LLVM. See [this comment](https://github.com/rust-lang/rust/issues/10185#issuecomment-110955148), and [this example code](https://gist.github.com/Amanieu/f87da5f0599b343c5500). # Alternatives [alternatives]: #alternatives -There have been several attempts to create `u128`/`i128` wrappers based on two `u64` values, but these can't match the performance of LLVM's native 128-bit integers. +There have been several attempts to create `u128`/`i128` wrappers based on two `u64` values, but these can't match the performance of LLVM's native 128-bit integers. For example LLVM is able to lower a 128-bit add into just 2 instructions on 64-bit platforms and 4 instructions on 32-bit platforms. # Unresolved questions [unresolved]: #unresolved-questions From 4c4423f98e96362652decf13040fc1401ac5cbff Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Wed, 30 Mar 2016 22:26:19 +0100 Subject: [PATCH 5/6] Change function prototypes to Rust --- text/0000-int128.md | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/text/0000-int128.md b/text/0000-int128.md index 7882f528b23..1e20368a5fc 100644 --- a/text/0000-int128.md +++ b/text/0000-int128.md @@ -40,28 +40,24 @@ The `compiler-rt` library that comes with LLVM only implements runtime library f Here is the list of functions that need to be implemented: -```c -// si_int = i32 -// su_int = u32 -// ti_int = i128 -// tu_int = u128 -ti_int __ashlti3(ti_int a, si_int b); -ti_int __ashrti3(ti_int a, si_int b); -ti_int __divti3(ti_int a, ti_int b); -ti_int __fixdfti(double a); -ti_int __fixsfti(float a); -tu_int __fixunsdfti(double a); -tu_int __fixunssfti(float a); -double __floattidf(ti_int a); -float __floattisf(ti_int a); -double __floatuntidf(tu_int a); -float __floatuntisf(tu_int a); -ti_int __lshrti3(ti_int a, si_int b); -ti_int __modti3(ti_int a, ti_int b); -ti_int __muloti4(ti_int a, ti_int b, int* overflow); -ti_int __multi3(ti_int a, ti_int b); -tu_int __udivti3(tu_int a, tu_int b); -tu_int __umodti3(tu_int a, tu_int b); +```rust +fn __ashlti3(a: i128, b: i32) -> i128; +fn __ashrti3(a: i128, b: i32) -> i128; +fn __divti3(a: i128, b: i128) -> i128; +fn __fixdfti(a: f64) -> i128; +fn __fixsfti(a: f32) -> i128; +fn __fixunsdfti(a: f64) -> u128; +fn __fixunssfti(a: f32) -> u128; +fn __floattidf(a: i128) -> f64; +fn __floattisf(a: i128) -> f32; +fn __floatuntidf(a: u128) -> f64; +fn __floatuntisf(a: u128) -> f32; +fn __lshrti3(a: i128, b: i32) -> i128; +fn __modti3(a: i128, b: i128) -> i128; +fn __muloti4(a: i128, b: i128, overflow: &mut i32) -> i128; +fn __multi3(a: i128, b: i128) -> i128; +fn __udivti3(a: u128, b: u128) -> u128; +fn __umodti3(a: u128, b: u128) -> u128; ``` Implementations of these functions will be written in Rust and will be included in libcore. From a4b68d4704088971afce23e83549212fde941f06 Mon Sep 17 00:00:00 2001 From: Amanieu d'Antras Date: Thu, 31 Mar 2016 12:56:07 +0100 Subject: [PATCH 6/6] Explain why we can't use the compiler-rt functions or write them in C --- text/0000-int128.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-int128.md b/text/0000-int128.md index 1e20368a5fc..f5e5b57942e 100644 --- a/text/0000-int128.md +++ b/text/0000-int128.md @@ -60,7 +60,7 @@ fn __udivti3(a: u128, b: u128) -> u128; fn __umodti3(a: u128, b: u128) -> u128; ``` -Implementations of these functions will be written in Rust and will be included in libcore. +Implementations of these functions will be written in Rust and will be included in libcore. Note that it is not possible to write these functions in C or use the existing implementations in `compiler-rt` since the `__int128` type is not available in C on 32-bit platforms. ## Modifications to libcore