Implement 128-bit integers; the first commit's rejection was wrong
`ty::rejected` has said "128-bit integers have no faithful Nim equivalent" since the first commit. That was true of Nim and false of the target: GCC and Clang provide `__int128`, which is what rustc itself lowers `u128`/`i128` to, and Nim can bind it the same way it binds any other C type. So this is a binding, on the same footing as `extern "C"`, not a model of 128-bit arithmetic. `src/prelude_i128.nim` carries the C shims -- arithmetic, comparison, bitwise, conversion, and a division loop for decimal output, since C has no `printf` conversion for the type. Literals are rebuilt from two 64-bit halves because C has no 128-bit literal either. The prelude is conditional, gated like the async and sync ones, so programs that never mention 128 bits do not pay for it. Two limits recorded in DESIGN.md rather than papered over: `__int128` is a GCC/Clang extension on 64-bit targets, and *signed* overflow is undefined in C where Rust's debug profile panics. Unsigned wrapping is exact. tests/cases/049 checks it against rustc: the wrap at 2^128, u128::MAX, i128::MIN, division, bitwise ops, and casts in both directions. Byte-identical. tests/cases/900 pinned the old rejection and is deleted. A reject case is worth keeping only while the rejection is still true; this one had stopped being so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
f9ca982 parent: 8f6401f modified
DESIGN.md +34 -14 | @@ -52,8 +52,13 @@ first rule of this project is the one that codebase broke: | ||
| 52 | 52 | |
| 53 | 53 | > **Never approximate a semantic you cannot represent. Fail loudly instead.** |
| 54 | 54 | |
| 55 | -`src/ty.rs` already does this: `i128`/`u128` are rejected with a reason rather | |
| 56 | -than widened or truncated. | |
| 55 | +`src/ty.rs` already does this. Note what the rule does *not* say: it does not | |
| 56 | +say "reject anything hard". `i128`/`u128` were the first commit's example of a | |
| 57 | +type with no Nim equivalent, and they were rejected for two hundred commits -- | |
| 58 | +until it turned out Nim could reach the *same* C `__int128` that rustc lowers | |
| 59 | +them to, at which point the honest move was to implement them rather than keep | |
| 60 | +a rejection that had stopped being true. The rule forbids pretending, not | |
| 61 | +effort. See "128-bit integers" below. | |
| 57 | 62 | |
| 58 | 63 | ## Architecture |
| 59 | 64 | |
| @@ -258,8 +263,19 @@ runner) rather than a wrong answer. | ||
| 258 | 263 | |
| 259 | 264 | ## Mapping decisions made so far |
| 260 | 265 | |
| 261 | -- **Integers**: exact width. `i32`→`int32`, `usize`→`uint`, etc. `i128`/`u128` | |
| 262 | - rejected. | |
| 266 | +- **Integers**: exact width. `i32`→`int32`, `usize`→`uint`, etc. | |
| 267 | +- **128-bit integers**: `u128`→`RsU128`, `i128`→`RsI128`, bound to C's | |
| 268 | + `unsigned __int128`/`__int128` via `{.emit.}` + `importc` in | |
| 269 | + `src/prelude_i128.nim` -- the same machine arithmetic rustc itself emits, so | |
| 270 | + this is a binding rather than a model, on the same footing as `extern "C"`. | |
| 271 | + Arithmetic, comparison, bitwise ops and the halves-based literal | |
| 272 | + construction are all C shims; decimal printing is a division loop because C | |
| 273 | + has no `printf` conversion for the type. Verified byte-identical against | |
| 274 | + rustc in `tests/cases/049`, including the wrap at 2^128, `u128::MAX` and | |
| 275 | + `i128::MIN`. **Two limits, stated rather than hidden:** `__int128` is a | |
| 276 | + GCC/Clang extension on 64-bit targets only (no MSVC), and *signed* overflow | |
| 277 | + is undefined in C where Rust's debug profile panics -- unsigned wrapping is | |
| 278 | + exact, signed overflow is the one behaviour here that is not reproduced. | |
| 263 | 279 | - **Indexing**: both 0-based. Direct. |
| 264 | 280 | - **`&T`** → plain value. **`&mut T`** → `var T` parameter. |
| 265 | 281 | - **`&[T]`** → `openArray[T]` in parameter position, `seq[T]` when owned. |
| @@ -364,14 +380,14 @@ Run without fixing anything, to see where the wall is rather than to move it: | ||
| 364 | 380 | | crate | outcome | |
| 365 | 381 | |---|---| |
| 366 | 382 | | `adler2` 2.0.1 | **works**, byte-identical | |
| 367 | -| `siphasher` 1.0.1 | rejected: `u128` | | |
| 368 | -| `rustc-hash` 2.1.1 | rejected: `u128` | | |
| 383 | +| `siphasher` 1.0.1 | *was* rejected: `u128` -- now implemented | | |
| 384 | +| `rustc-hash` 2.1.1 | *was* rejected: `u128` -- now implemented | | |
| 369 | 385 | | `hex` 0.4.3 | rejected: `impl Iterator` needs an associated type | |
| 370 | 386 | | `crc32fast` 1.5.0 | rejected: directory modules (`specialized/mod.rs`), then SIMD intrinsics | |
| 371 | 387 | |
| 372 | -Two of the five stop at `u128`, which is the founding rule doing its job | |
| 373 | -rather than a gap: they are told they cannot be translated instead of being | |
| 374 | -handed a silently truncated hasher. The other two are honest missing | |
| 388 | +Two of the five stopped at `u128`. At the time that read as the founding rule | |
| 389 | +doing its job; it was in fact a gap wearing the rule's clothes, and `u128` is | |
| 390 | +now implemented against C's `__int128`. The other two are honest missing | |
| 375 | 391 | features — associated types, and `mod` directories. |
| 376 | 392 | |
| 377 | 393 | ## How far off is a crate like `libcosmic`? |
| @@ -400,9 +416,11 @@ rustnim on that took **two** blockers to get through: | ||
| 400 | 416 | 1. destructuring parameters — `fn from((column, row): (u16, u16))`, which |
| 401 | 417 | `iced` writes several times. Named here and taken apart at the top of the |
| 402 | 418 | body, as Rust does. Now `tests/cases/048`. |
| 403 | -2. `u128` — and here it stops, **deliberately**. 16 occurrences, from | |
| 404 | - `enumflags2`'s bit arithmetic and an `iced` `DragId(pub u128)`. That is the | |
| 405 | - founding rule refusing to truncate, not a missing feature. | |
| 419 | +2. `u128` — 16 occurrences, from `enumflags2`'s bit arithmetic and an `iced` | |
| 420 | + `DragId(pub u128)`. This looked like the founding rule refusing to | |
| 421 | + truncate. It was not: C's `__int128` is exactly the arithmetic Rust | |
| 422 | + compiles `u128` to, and Nim can bind it. Implemented; see "128-bit | |
| 423 | + integers" above. | |
| 406 | 424 | |
| 407 | 425 | So the expanded crate clears the language and halts at the project's own |
| 408 | 426 | first principle. That is a much more interesting place to stop than "generics |
| @@ -1126,8 +1144,10 @@ Cases carry directives in leading `//@` comments: | ||
| 1126 | 1144 | | `//@ extern: <crate>` | the **oracle** links this crate; rustnim does not get it | |
| 1127 | 1145 | |
| 1128 | 1146 | `reject` cases are how the "fail loudly" rule is tested rather than merely |
| 1129 | -stated: `900`–`904` pin the rejections of `i128`, an unmapped standard-library | |
| 1130 | -method, a float→int cast, an unimplemented format spec, and a closure. | |
| 1147 | +stated: `901`–`904` pin the rejections of an unmapped standard-library method, | |
| 1148 | +a float→int cast, an unimplemented format spec, and a closure. (`900` pinned | |
| 1149 | +the `i128` rejection and was deleted when `i128` became implementable — a | |
| 1150 | +rejection test is only worth keeping while the rejection is still true.) | |
| 1131 | 1151 | |
| 1132 | 1152 | Run one case with `RUSTNIM_CASE=005 cargo test --test differential -- |
| 1133 | 1153 | --nocapture`. Nim is found at `.nim-toolchain/bin/nim` in the repository root |
| @@ -52,8 +52,13 @@ first rule of this project is the one that codebase broke: | |||
| 52 | 52 | ||
| 53 | > **Never approximate a semantic you cannot represent. Fail loudly instead.** | 53 | > **Never approximate a semantic you cannot represent. Fail loudly instead.** |
| 54 | 54 | ||
| 55 | -`src/ty.rs` already does this: `i128`/`u128` are rejected with a reason rather | 55 | +`src/ty.rs` already does this. Note what the rule does *not* say: it does not |
| 56 | -than widened or truncated. | 56 | +say "reject anything hard". `i128`/`u128` were the first commit's example of a |
| 57 | +type with no Nim equivalent, and they were rejected for two hundred commits -- | ||
| 58 | +until it turned out Nim could reach the *same* C `__int128` that rustc lowers | ||
| 59 | +them to, at which point the honest move was to implement them rather than keep | ||
| 60 | +a rejection that had stopped being true. The rule forbids pretending, not | ||
| 61 | +effort. See "128-bit integers" below. | ||
| 57 | 62 | ||
| 58 | ## Architecture | 63 | ## Architecture |
| 59 | 64 | ||
| @@ -258,8 +263,19 @@ runner) rather than a wrong answer. | |||
| 258 | 263 | ||
| 259 | ## Mapping decisions made so far | 264 | ## Mapping decisions made so far |
| 260 | 265 | ||
| 261 | -- **Integers**: exact width. `i32`→`int32`, `usize`→`uint`, etc. `i128`/`u128` | 266 | +- **Integers**: exact width. `i32`→`int32`, `usize`→`uint`, etc. |
| 262 | - rejected. | 267 | +- **128-bit integers**: `u128`→`RsU128`, `i128`→`RsI128`, bound to C's |
| 268 | + `unsigned __int128`/`__int128` via `{.emit.}` + `importc` in | ||
| 269 | + `src/prelude_i128.nim` -- the same machine arithmetic rustc itself emits, so | ||
| 270 | + this is a binding rather than a model, on the same footing as `extern "C"`. | ||
| 271 | + Arithmetic, comparison, bitwise ops and the halves-based literal | ||
| 272 | + construction are all C shims; decimal printing is a division loop because C | ||
| 273 | + has no `printf` conversion for the type. Verified byte-identical against | ||
| 274 | + rustc in `tests/cases/049`, including the wrap at 2^128, `u128::MAX` and | ||
| 275 | + `i128::MIN`. **Two limits, stated rather than hidden:** `__int128` is a | ||
| 276 | + GCC/Clang extension on 64-bit targets only (no MSVC), and *signed* overflow | ||
| 277 | + is undefined in C where Rust's debug profile panics -- unsigned wrapping is | ||
| 278 | + exact, signed overflow is the one behaviour here that is not reproduced. | ||
| 263 | - **Indexing**: both 0-based. Direct. | 279 | - **Indexing**: both 0-based. Direct. |
| 264 | - **`&T`** → plain value. **`&mut T`** → `var T` parameter. | 280 | - **`&T`** → plain value. **`&mut T`** → `var T` parameter. |
| 265 | - **`&[T]`** → `openArray[T]` in parameter position, `seq[T]` when owned. | 281 | - **`&[T]`** → `openArray[T]` in parameter position, `seq[T]` when owned. |
| @@ -364,14 +380,14 @@ Run without fixing anything, to see where the wall is rather than to move it: | |||
| 364 | | crate | outcome | | 380 | | crate | outcome | |
| 365 | |---|---| | 381 | |---|---| |
| 366 | | `adler2` 2.0.1 | **works**, byte-identical | | 382 | | `adler2` 2.0.1 | **works**, byte-identical | |
| 367 | -| `siphasher` 1.0.1 | rejected: `u128` | | 383 | +| `siphasher` 1.0.1 | *was* rejected: `u128` -- now implemented | |
| 368 | -| `rustc-hash` 2.1.1 | rejected: `u128` | | 384 | +| `rustc-hash` 2.1.1 | *was* rejected: `u128` -- now implemented | |
| 369 | | `hex` 0.4.3 | rejected: `impl Iterator` needs an associated type | | 385 | | `hex` 0.4.3 | rejected: `impl Iterator` needs an associated type | |
| 370 | | `crc32fast` 1.5.0 | rejected: directory modules (`specialized/mod.rs`), then SIMD intrinsics | | 386 | | `crc32fast` 1.5.0 | rejected: directory modules (`specialized/mod.rs`), then SIMD intrinsics | |
| 371 | 387 | ||
| 372 | -Two of the five stop at `u128`, which is the founding rule doing its job | 388 | +Two of the five stopped at `u128`. At the time that read as the founding rule |
| 373 | -rather than a gap: they are told they cannot be translated instead of being | 389 | +doing its job; it was in fact a gap wearing the rule's clothes, and `u128` is |
| 374 | -handed a silently truncated hasher. The other two are honest missing | 390 | +now implemented against C's `__int128`. The other two are honest missing |
| 375 | features — associated types, and `mod` directories. | 391 | features — associated types, and `mod` directories. |
| 376 | 392 | ||
| 377 | ## How far off is a crate like `libcosmic`? | 393 | ## How far off is a crate like `libcosmic`? |
| @@ -400,9 +416,11 @@ rustnim on that took **two** blockers to get through: | |||
| 400 | 1. destructuring parameters — `fn from((column, row): (u16, u16))`, which | 416 | 1. destructuring parameters — `fn from((column, row): (u16, u16))`, which |
| 401 | `iced` writes several times. Named here and taken apart at the top of the | 417 | `iced` writes several times. Named here and taken apart at the top of the |
| 402 | body, as Rust does. Now `tests/cases/048`. | 418 | body, as Rust does. Now `tests/cases/048`. |
| 403 | -2. `u128` — and here it stops, **deliberately**. 16 occurrences, from | 419 | +2. `u128` — 16 occurrences, from `enumflags2`'s bit arithmetic and an `iced` |
| 404 | - `enumflags2`'s bit arithmetic and an `iced` `DragId(pub u128)`. That is the | 420 | + `DragId(pub u128)`. This looked like the founding rule refusing to |
| 405 | - founding rule refusing to truncate, not a missing feature. | 421 | + truncate. It was not: C's `__int128` is exactly the arithmetic Rust |
| 422 | + compiles `u128` to, and Nim can bind it. Implemented; see "128-bit | ||
| 423 | + integers" above. | ||
| 406 | 424 | ||
| 407 | So the expanded crate clears the language and halts at the project's own | 425 | So the expanded crate clears the language and halts at the project's own |
| 408 | first principle. That is a much more interesting place to stop than "generics | 426 | first principle. That is a much more interesting place to stop than "generics |
| @@ -1126,8 +1144,10 @@ Cases carry directives in leading `//@` comments: | |||
| 1126 | | `//@ extern: <crate>` | the **oracle** links this crate; rustnim does not get it | | 1144 | | `//@ extern: <crate>` | the **oracle** links this crate; rustnim does not get it | |
| 1127 | 1145 | ||
| 1128 | `reject` cases are how the "fail loudly" rule is tested rather than merely | 1146 | `reject` cases are how the "fail loudly" rule is tested rather than merely |
| 1129 | -stated: `900`–`904` pin the rejections of `i128`, an unmapped standard-library | 1147 | +stated: `901`–`904` pin the rejections of an unmapped standard-library method, |
| 1130 | -method, a float→int cast, an unimplemented format spec, and a closure. | 1148 | +a float→int cast, an unimplemented format spec, and a closure. (`900` pinned |
| 1149 | +the `i128` rejection and was deleted when `i128` became implementable — a | ||
| 1150 | +rejection test is only worth keeping while the rejection is still true.) | ||
| 1131 | 1151 | ||
| 1132 | Run one case with `RUSTNIM_CASE=005 cargo test --test differential -- | 1152 | Run one case with `RUSTNIM_CASE=005 cargo test --test differential -- |
| 1133 | --nocapture`. Nim is found at `.nim-toolchain/bin/nim` in the repository root | 1153 | --nocapture`. Nim is found at `.nim-toolchain/bin/nim` in the repository root |
modified
src/lower.rs +67 -0 | @@ -300,6 +300,7 @@ pub struct Lowerer { | ||
| 300 | 300 | /// a type, which happens behind `&self`, and the answer has to be known |
| 301 | 301 | /// before the prelude is written. |
| 302 | 302 | uses_sync: std::cell::Cell<bool>, |
| 303 | + uses_i128: std::cell::Cell<bool>, | |
| 303 | 304 | /// Module names supplied as separate input files. A `mod x;` naming one |
| 304 | 305 | /// of these is satisfied by that file having been passed in. |
| 305 | 306 | pub modules: Vec<String>, |
| @@ -370,6 +371,7 @@ impl Lowerer { | ||
| 370 | 371 | pending_locks: Vec::new(), |
| 371 | 372 | uses_async: false, |
| 372 | 373 | uses_sync: std::cell::Cell::new(false), |
| 374 | + uses_i128: std::cell::Cell::new(false), | |
| 373 | 375 | modules: Vec::new(), |
| 374 | 376 | emitted: 0, |
| 375 | 377 | features: Vec::new(), |
| @@ -534,6 +536,9 @@ impl Lowerer { | ||
| 534 | 536 | out.push_str(include_str!("prelude_async.nim")); |
| 535 | 537 | out.push('\n'); |
| 536 | 538 | } |
| 539 | + if self.uses_i128.get() { | |
| 540 | + out.push_str(include_str!("prelude_i128.nim")); | |
| 541 | + } | |
| 537 | 542 | if self.uses_sync.get() { |
| 538 | 543 | out.push_str(include_str!("prelude_sync.nim")); |
| 539 | 544 | out.push('\n'); |
| @@ -3912,6 +3917,15 @@ impl Lowerer { | ||
| 3912 | 3917 | if matches!(name.as_str(), "MAX" | "MIN") { |
| 3913 | 3918 | if let Some(q) = p.path.segments.iter().rev().nth(1) { |
| 3914 | 3919 | if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) { |
| 3920 | + if let Some(signed) = t.as_i128() { | |
| 3921 | + self.uses_i128.set(true); | |
| 3922 | + let n = format!( | |
| 3923 | + "rs{}128{}", | |
| 3924 | + if signed { "I" } else { "U" }, | |
| 3925 | + name.to_lowercase(), | |
| 3926 | + ); | |
| 3927 | + return Ok(Val::new(n, Some(t))); | |
| 3928 | + } | |
| 3915 | 3929 | if t.is_integer() { |
| 3916 | 3930 | let f = if name == "MAX" { "high" } else { "low" }; |
| 3917 | 3931 | return Ok(Val::new( |
| @@ -4368,6 +4382,20 @@ impl Lowerer { | ||
| 4368 | 4382 | } else { |
| 4369 | 4383 | ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))? |
| 4370 | 4384 | }; |
| 4385 | + if let Some(signed) = t.as_i128() { | |
| 4386 | + // C has no 128-bit literal either, so the value is rebuilt | |
| 4387 | + // from its two 64-bit halves. Parsed as `u128` in both | |
| 4388 | + // cases: for `i128` the digits are the bit pattern, which | |
| 4389 | + // is what lets `-170141183460469231731687303715884105728` | |
| 4390 | + // round-trip through a unary minus. | |
| 4391 | + let v: u128 = digits | |
| 4392 | + .parse() | |
| 4393 | + .map_err(|_| format!("integer literal `{digits}` does not fit in 128 bits"))?; | |
| 4394 | + self.uses_i128.set(true); | |
| 4395 | + let f = if signed { "rsI128Parts" } else { "rsU128Parts" }; | |
| 4396 | + let code = format!("{}({}'u64, {}'u64)", f, (v >> 64) as u64, v as u64); | |
| 4397 | + return Ok(Val::new(code, Some(t))); | |
| 4398 | + } | |
| 4371 | 4399 | Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t))) |
| 4372 | 4400 | } |
| 4373 | 4401 | Lit::Float(f) => { |
| @@ -4537,6 +4565,43 @@ impl Lowerer { | ||
| 4537 | 4565 | })?; |
| 4538 | 4566 | |
| 4539 | 4567 | let code = match (&from, &to) { |
| 4568 | + // Widening into 128 bits, or truncating back out of it. C's | |
| 4569 | + // implicit conversions do exactly what Rust's `as` does here, so | |
| 4570 | + // each direction is a single named shim rather than a `cast`, | |
| 4571 | + // whose Nim meaning (reinterpret these bytes) would be wrong | |
| 4572 | + // between types of different size. | |
| 4573 | + (f, t) if t.as_i128().is_some() && f.is_integer() && f.as_i128().is_none() => { | |
| 4574 | + self.uses_i128.set(true); | |
| 4575 | + let signed = t.as_i128() == Some(true); | |
| 4576 | + let half = if f.is_unsigned() { "uint64" } else { "int64" }; | |
| 4577 | + format!( | |
| 4578 | + "rs{}128From{}({}({}))", | |
| 4579 | + if signed { "I" } else { "U" }, | |
| 4580 | + if f.is_unsigned() { "U" } else { "I" }, | |
| 4581 | + half, | |
| 4582 | + v.code, | |
| 4583 | + ) | |
| 4584 | + } | |
| 4585 | + (f, t) if f.as_i128().is_some() && t.is_integer() && t.as_i128().is_none() => { | |
| 4586 | + self.uses_i128.set(true); | |
| 4587 | + let signed = f.as_i128() == Some(true); | |
| 4588 | + let lo = format!( | |
| 4589 | + "rs{}128Lo({})", | |
| 4590 | + if signed { "I" } else { "U" }, | |
| 4591 | + v.code, | |
| 4592 | + ); | |
| 4593 | + format!("cast[{}]({})", t.render(), lo) | |
| 4594 | + } | |
| 4595 | + (f, t) if f.as_i128().is_some() && t.as_i128().is_some() => { | |
| 4596 | + self.uses_i128.set(true); | |
| 4597 | + let to_signed = t.as_i128() == Some(true); | |
| 4598 | + format!( | |
| 4599 | + "rs{}128Of{}({})", | |
| 4600 | + if to_signed { "I" } else { "U" }, | |
| 4601 | + if f.as_i128() == Some(true) { "I" } else { "U" }, | |
| 4602 | + v.code, | |
| 4603 | + ) | |
| 4604 | + } | |
| 4540 | 4605 | (f, t) if f.is_integer() && t.is_integer() => { |
| 4541 | 4606 | // Rust's `as` between integers is a pure bit-width truncation |
| 4542 | 4607 | // or sign-extension, never a range check. `cast` says exactly |
| @@ -6468,6 +6533,8 @@ fn default_of(t: &Nim) -> Option<String> { | ||
| 6468 | 6533 | Nim::Prim(p) if p == "string" => "\"\"".into(), |
| 6469 | 6534 | Nim::Prim(p) if p == "bool" => "false".into(), |
| 6470 | 6535 | Nim::Prim(p) if p.starts_with("float") => format!("{}(0.0)", p), |
| 6536 | + Nim::Prim(p) if p == "RsU128" => "rsU128Parts(0'u64, 0'u64)".into(), | |
| 6537 | + Nim::Prim(p) if p == "RsI128" => "rsI128Parts(0'u64, 0'u64)".into(), | |
| 6471 | 6538 | Nim::Prim(_) if t.is_integer() => format!("{}(0)", t.render()), |
| 6472 | 6539 | Nim::Seq(e) => format!("newSeq[{}]()", e.render()), |
| 6473 | 6540 | _ => return None, |
| @@ -300,6 +300,7 @@ pub struct Lowerer { | |||
| 300 | /// a type, which happens behind `&self`, and the answer has to be known | 300 | /// a type, which happens behind `&self`, and the answer has to be known |
| 301 | /// before the prelude is written. | 301 | /// before the prelude is written. |
| 302 | uses_sync: std::cell::Cell<bool>, | 302 | uses_sync: std::cell::Cell<bool>, |
| 303 | + uses_i128: std::cell::Cell<bool>, | ||
| 303 | /// Module names supplied as separate input files. A `mod x;` naming one | 304 | /// Module names supplied as separate input files. A `mod x;` naming one |
| 304 | /// of these is satisfied by that file having been passed in. | 305 | /// of these is satisfied by that file having been passed in. |
| 305 | pub modules: Vec<String>, | 306 | pub modules: Vec<String>, |
| @@ -370,6 +371,7 @@ impl Lowerer { | |||
| 370 | pending_locks: Vec::new(), | 371 | pending_locks: Vec::new(), |
| 371 | uses_async: false, | 372 | uses_async: false, |
| 372 | uses_sync: std::cell::Cell::new(false), | 373 | uses_sync: std::cell::Cell::new(false), |
| 374 | + uses_i128: std::cell::Cell::new(false), | ||
| 373 | modules: Vec::new(), | 375 | modules: Vec::new(), |
| 374 | emitted: 0, | 376 | emitted: 0, |
| 375 | features: Vec::new(), | 377 | features: Vec::new(), |
| @@ -534,6 +536,9 @@ impl Lowerer { | |||
| 534 | out.push_str(include_str!("prelude_async.nim")); | 536 | out.push_str(include_str!("prelude_async.nim")); |
| 535 | out.push('\n'); | 537 | out.push('\n'); |
| 536 | } | 538 | } |
| 539 | + if self.uses_i128.get() { | ||
| 540 | + out.push_str(include_str!("prelude_i128.nim")); | ||
| 541 | + } | ||
| 537 | if self.uses_sync.get() { | 542 | if self.uses_sync.get() { |
| 538 | out.push_str(include_str!("prelude_sync.nim")); | 543 | out.push_str(include_str!("prelude_sync.nim")); |
| 539 | out.push('\n'); | 544 | out.push('\n'); |
| @@ -3912,6 +3917,15 @@ impl Lowerer { | |||
| 3912 | if matches!(name.as_str(), "MAX" | "MIN") { | 3917 | if matches!(name.as_str(), "MAX" | "MIN") { |
| 3913 | if let Some(q) = p.path.segments.iter().rev().nth(1) { | 3918 | if let Some(q) = p.path.segments.iter().rev().nth(1) { |
| 3914 | if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) { | 3919 | if let Some(t @ Nim::Prim(_)) = ty::prim(&q.ident.to_string()) { |
| 3920 | + if let Some(signed) = t.as_i128() { | ||
| 3921 | + self.uses_i128.set(true); | ||
| 3922 | + let n = format!( | ||
| 3923 | + "rs{}128{}", | ||
| 3924 | + if signed { "I" } else { "U" }, | ||
| 3925 | + name.to_lowercase(), | ||
| 3926 | + ); | ||
| 3927 | + return Ok(Val::new(n, Some(t))); | ||
| 3928 | + } | ||
| 3915 | if t.is_integer() { | 3929 | if t.is_integer() { |
| 3916 | let f = if name == "MAX" { "high" } else { "low" }; | 3930 | let f = if name == "MAX" { "high" } else { "low" }; |
| 3917 | return Ok(Val::new( | 3931 | return Ok(Val::new( |
| @@ -4368,6 +4382,20 @@ impl Lowerer { | |||
| 4368 | } else { | 4382 | } else { |
| 4369 | ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))? | 4383 | ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))? |
| 4370 | }; | 4384 | }; |
| 4385 | + if let Some(signed) = t.as_i128() { | ||
| 4386 | + // C has no 128-bit literal either, so the value is rebuilt | ||
| 4387 | + // from its two 64-bit halves. Parsed as `u128` in both | ||
| 4388 | + // cases: for `i128` the digits are the bit pattern, which | ||
| 4389 | + // is what lets `-170141183460469231731687303715884105728` | ||
| 4390 | + // round-trip through a unary minus. | ||
| 4391 | + let v: u128 = digits | ||
| 4392 | + .parse() | ||
| 4393 | + .map_err(|_| format!("integer literal `{digits}` does not fit in 128 bits"))?; | ||
| 4394 | + self.uses_i128.set(true); | ||
| 4395 | + let f = if signed { "rsI128Parts" } else { "rsU128Parts" }; | ||
| 4396 | + let code = format!("{}({}'u64, {}'u64)", f, (v >> 64) as u64, v as u64); | ||
| 4397 | + return Ok(Val::new(code, Some(t))); | ||
| 4398 | + } | ||
| 4371 | Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t))) | 4399 | Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t))) |
| 4372 | } | 4400 | } |
| 4373 | Lit::Float(f) => { | 4401 | Lit::Float(f) => { |
| @@ -4537,6 +4565,43 @@ impl Lowerer { | |||
| 4537 | })?; | 4565 | })?; |
| 4538 | 4566 | ||
| 4539 | let code = match (&from, &to) { | 4567 | let code = match (&from, &to) { |
| 4568 | + // Widening into 128 bits, or truncating back out of it. C's | ||
| 4569 | + // implicit conversions do exactly what Rust's `as` does here, so | ||
| 4570 | + // each direction is a single named shim rather than a `cast`, | ||
| 4571 | + // whose Nim meaning (reinterpret these bytes) would be wrong | ||
| 4572 | + // between types of different size. | ||
| 4573 | + (f, t) if t.as_i128().is_some() && f.is_integer() && f.as_i128().is_none() => { | ||
| 4574 | + self.uses_i128.set(true); | ||
| 4575 | + let signed = t.as_i128() == Some(true); | ||
| 4576 | + let half = if f.is_unsigned() { "uint64" } else { "int64" }; | ||
| 4577 | + format!( | ||
| 4578 | + "rs{}128From{}({}({}))", | ||
| 4579 | + if signed { "I" } else { "U" }, | ||
| 4580 | + if f.is_unsigned() { "U" } else { "I" }, | ||
| 4581 | + half, | ||
| 4582 | + v.code, | ||
| 4583 | + ) | ||
| 4584 | + } | ||
| 4585 | + (f, t) if f.as_i128().is_some() && t.is_integer() && t.as_i128().is_none() => { | ||
| 4586 | + self.uses_i128.set(true); | ||
| 4587 | + let signed = f.as_i128() == Some(true); | ||
| 4588 | + let lo = format!( | ||
| 4589 | + "rs{}128Lo({})", | ||
| 4590 | + if signed { "I" } else { "U" }, | ||
| 4591 | + v.code, | ||
| 4592 | + ); | ||
| 4593 | + format!("cast[{}]({})", t.render(), lo) | ||
| 4594 | + } | ||
| 4595 | + (f, t) if f.as_i128().is_some() && t.as_i128().is_some() => { | ||
| 4596 | + self.uses_i128.set(true); | ||
| 4597 | + let to_signed = t.as_i128() == Some(true); | ||
| 4598 | + format!( | ||
| 4599 | + "rs{}128Of{}({})", | ||
| 4600 | + if to_signed { "I" } else { "U" }, | ||
| 4601 | + if f.as_i128() == Some(true) { "I" } else { "U" }, | ||
| 4602 | + v.code, | ||
| 4603 | + ) | ||
| 4604 | + } | ||
| 4540 | (f, t) if f.is_integer() && t.is_integer() => { | 4605 | (f, t) if f.is_integer() && t.is_integer() => { |
| 4541 | // Rust's `as` between integers is a pure bit-width truncation | 4606 | // Rust's `as` between integers is a pure bit-width truncation |
| 4542 | // or sign-extension, never a range check. `cast` says exactly | 4607 | // or sign-extension, never a range check. `cast` says exactly |
| @@ -6468,6 +6533,8 @@ fn default_of(t: &Nim) -> Option<String> { | |||
| 6468 | Nim::Prim(p) if p == "string" => "\"\"".into(), | 6533 | Nim::Prim(p) if p == "string" => "\"\"".into(), |
| 6469 | Nim::Prim(p) if p == "bool" => "false".into(), | 6534 | Nim::Prim(p) if p == "bool" => "false".into(), |
| 6470 | Nim::Prim(p) if p.starts_with("float") => format!("{}(0.0)", p), | 6535 | Nim::Prim(p) if p.starts_with("float") => format!("{}(0.0)", p), |
| 6536 | + Nim::Prim(p) if p == "RsU128" => "rsU128Parts(0'u64, 0'u64)".into(), | ||
| 6537 | + Nim::Prim(p) if p == "RsI128" => "rsI128Parts(0'u64, 0'u64)".into(), | ||
| 6471 | Nim::Prim(_) if t.is_integer() => format!("{}(0)", t.render()), | 6538 | Nim::Prim(_) if t.is_integer() => format!("{}(0)", t.render()), |
| 6472 | Nim::Seq(e) => format!("newSeq[{}]()", e.render()), | 6539 | Nim::Seq(e) => format!("newSeq[{}]()", e.render()), |
| 6473 | _ => return None, | 6540 | _ => return None, |
added
src/prelude_i128.nim +174 -0 | new file mode 100644 | ||
| @@ -0,0 +1,174 @@ | ||
| 1 | +# --------------------------------------------------------------------------- | |
| 2 | +# 128-bit integers. | |
| 3 | +# | |
| 4 | +# Nim has no 128-bit integer, but C does: GCC and Clang provide `__int128` on | |
| 5 | +# 64-bit targets, and that is what Rust's `u128`/`i128` compile to as well. So | |
| 6 | +# this is a binding to the same machine arithmetic rather than a simulation -- | |
| 7 | +# the same reasoning that makes `extern "C"` an exact mapping. | |
| 8 | +# | |
| 9 | +# Unsigned arithmetic wraps at 2^128 in both languages; verified against rustc, | |
| 10 | +# including `u64::MAX * u64::MAX` and `u128::MAX.wrapping_add(1)`. | |
| 11 | +# | |
| 12 | +# Two limits, recorded rather than papered over: | |
| 13 | +# | |
| 14 | +# * `__int128` is a GCC/Clang extension and exists only on 64-bit targets. | |
| 15 | +# MSVC has no equivalent. | |
| 16 | +# * **Signed** overflow is undefined in C where Rust's debug profile panics. | |
| 17 | +# Unsigned is exact; signed `i128` overflow is the one case where this | |
| 18 | +# does not reproduce Rust's behaviour, and nothing here detects it. | |
| 19 | +# | |
| 20 | +# C has no `printf` conversion for `__int128`, so decimal rendering is done by | |
| 21 | +# repeated division, which is why it is written out below. | |
| 22 | +# --------------------------------------------------------------------------- | |
| 23 | + | |
| 24 | +{.emit: """ | |
| 25 | +typedef unsigned __int128 rs_u128; | |
| 26 | +typedef __int128 rs_i128; | |
| 27 | + | |
| 28 | +static rs_u128 rs_u128_parts(unsigned long long hi, unsigned long long lo) { | |
| 29 | + return (((rs_u128)hi) << 64) | (rs_u128)lo; | |
| 30 | +} | |
| 31 | +static rs_i128 rs_i128_parts(unsigned long long hi, unsigned long long lo) { | |
| 32 | + return (rs_i128)((((rs_u128)hi) << 64) | (rs_u128)lo); | |
| 33 | +} | |
| 34 | +static void rs_u128_str(rs_u128 v, char* out) { | |
| 35 | + char buf[48]; int i = 0; | |
| 36 | + if (v == 0) { out[0] = '0'; out[1] = 0; return; } | |
| 37 | + while (v > 0) { buf[i++] = (char)('0' + (int)(v % 10)); v /= 10; } | |
| 38 | + int j = 0; while (i > 0) out[j++] = buf[--i]; | |
| 39 | + out[j] = 0; | |
| 40 | +} | |
| 41 | +static void rs_i128_str(rs_i128 v, char* out) { | |
| 42 | + int neg = v < 0; rs_u128 m; | |
| 43 | + if (neg) { m = (rs_u128)(-(v + 1)) + 1; } else { m = (rs_u128)v; } | |
| 44 | + char tmp[48]; rs_u128_str(m, tmp); | |
| 45 | + int j = 0; if (neg) out[j++] = '-'; | |
| 46 | + int k = 0; while (tmp[k]) out[j++] = tmp[k++]; | |
| 47 | + out[j] = 0; | |
| 48 | +} | |
| 49 | +static rs_u128 rs_u128_add(rs_u128 a, rs_u128 b) { return a + b; } | |
| 50 | +static rs_u128 rs_u128_sub(rs_u128 a, rs_u128 b) { return a - b; } | |
| 51 | +static rs_u128 rs_u128_mul(rs_u128 a, rs_u128 b) { return a * b; } | |
| 52 | +static rs_u128 rs_u128_div(rs_u128 a, rs_u128 b) { return a / b; } | |
| 53 | +static rs_u128 rs_u128_mod(rs_u128 a, rs_u128 b) { return a % b; } | |
| 54 | +static rs_u128 rs_u128_and(rs_u128 a, rs_u128 b) { return a & b; } | |
| 55 | +static rs_u128 rs_u128_or(rs_u128 a, rs_u128 b) { return a | b; } | |
| 56 | +static rs_u128 rs_u128_xor(rs_u128 a, rs_u128 b) { return a ^ b; } | |
| 57 | +static int rs_u128_eq(rs_u128 a, rs_u128 b) { return a == b; } | |
| 58 | +static int rs_u128_lt(rs_u128 a, rs_u128 b) { return a < b; } | |
| 59 | +static int rs_u128_le(rs_u128 a, rs_u128 b) { return a <= b; } | |
| 60 | +static rs_u128 rs_u128_shl(rs_u128 a, unsigned b) { return a << b; } | |
| 61 | +static rs_u128 rs_u128_shr(rs_u128 a, unsigned b) { return a >> b; } | |
| 62 | +static rs_u128 rs_u128_not(rs_u128 a) { return ~a; } | |
| 63 | +static unsigned long long rs_u128_lo(rs_u128 a) { return (unsigned long long)a; } | |
| 64 | +static rs_i128 rs_i128_add(rs_i128 a, rs_i128 b) { return a + b; } | |
| 65 | +static rs_i128 rs_i128_sub(rs_i128 a, rs_i128 b) { return a - b; } | |
| 66 | +static rs_i128 rs_i128_mul(rs_i128 a, rs_i128 b) { return a * b; } | |
| 67 | +static rs_i128 rs_i128_div(rs_i128 a, rs_i128 b) { return a / b; } | |
| 68 | +static rs_i128 rs_i128_mod(rs_i128 a, rs_i128 b) { return a % b; } | |
| 69 | +static rs_i128 rs_i128_and(rs_i128 a, rs_i128 b) { return a & b; } | |
| 70 | +static rs_i128 rs_i128_or(rs_i128 a, rs_i128 b) { return a | b; } | |
| 71 | +static rs_i128 rs_i128_xor(rs_i128 a, rs_i128 b) { return a ^ b; } | |
| 72 | +static int rs_i128_eq(rs_i128 a, rs_i128 b) { return a == b; } | |
| 73 | +static int rs_i128_lt(rs_i128 a, rs_i128 b) { return a < b; } | |
| 74 | +static int rs_i128_le(rs_i128 a, rs_i128 b) { return a <= b; } | |
| 75 | +static rs_i128 rs_i128_shl(rs_i128 a, unsigned b) { return a << b; } | |
| 76 | +static rs_i128 rs_i128_shr(rs_i128 a, unsigned b) { return a >> b; } | |
| 77 | +static rs_i128 rs_i128_not(rs_i128 a) { return ~a; } | |
| 78 | +static unsigned long long rs_i128_lo(rs_i128 a) { return (unsigned long long)a; } | |
| 79 | +static rs_u128 rs_u128_fromu(unsigned long long v) { return (rs_u128)v; } | |
| 80 | +static rs_u128 rs_u128_fromi(long long v) { return (rs_u128)(rs_i128)v; } | |
| 81 | +static rs_i128 rs_i128_fromu(unsigned long long v) { return (rs_i128)(rs_u128)v; } | |
| 82 | +static rs_i128 rs_i128_fromi(long long v) { return (rs_i128)v; } | |
| 83 | +static rs_u128 rs_u128_ofi(rs_i128 v) { return (rs_u128)v; } | |
| 84 | +static rs_u128 rs_u128_ofu(rs_u128 v) { return v; } | |
| 85 | +static rs_i128 rs_i128_ofu(rs_u128 v) { return (rs_i128)v; } | |
| 86 | +static rs_i128 rs_i128_ofi(rs_i128 v) { return v; } | |
| 87 | +static rs_i128 rs_i128_neg(rs_i128 v) { return -v; } | |
| 88 | +static rs_u128 rs_u128_neg(rs_u128 v) { return -v; } | |
| 89 | +""".} | |
| 90 | + | |
| 91 | +type | |
| 92 | + RsU128* {.importc: "rs_u128", nodecl.} = object | |
| 93 | + RsI128* {.importc: "rs_i128", nodecl.} = object | |
| 94 | + | |
| 95 | +proc rsU128Parts*(hi, lo: uint64): RsU128 {.importc: "rs_u128_parts", nodecl.} | |
| 96 | +proc rsI128Parts*(hi, lo: uint64): RsI128 {.importc: "rs_i128_parts", nodecl.} | |
| 97 | +proc rsU128StrRaw(v: RsU128, o: cstring) {.importc: "rs_u128_str", nodecl.} | |
| 98 | +proc rsI128StrRaw(v: RsI128, o: cstring) {.importc: "rs_i128_str", nodecl.} | |
| 99 | + | |
| 100 | +proc `$`*(v: RsU128): string = | |
| 101 | + var b = newString(48) | |
| 102 | + rsU128StrRaw(v, cstring(b)) | |
| 103 | + b.setLen(b.cstring.len) | |
| 104 | + b | |
| 105 | + | |
| 106 | +proc `$`*(v: RsI128): string = | |
| 107 | + var b = newString(48) | |
| 108 | + rsI128StrRaw(v, cstring(b)) | |
| 109 | + b.setLen(b.cstring.len) | |
| 110 | + b | |
| 111 | + | |
| 112 | +proc rsDisplay*(v: RsU128): string = $v | |
| 113 | +proc rsDebug*(v: RsU128): string = $v | |
| 114 | +proc rsDisplay*(v: RsI128): string = $v | |
| 115 | +proc rsDebug*(v: RsI128): string = $v | |
| 116 | + | |
| 117 | +proc `+`*(a, b: RsU128): RsU128 {.importc: "rs_u128_add", nodecl.} | |
| 118 | +proc `-`*(a, b: RsU128): RsU128 {.importc: "rs_u128_sub", nodecl.} | |
| 119 | +proc `*`*(a, b: RsU128): RsU128 {.importc: "rs_u128_mul", nodecl.} | |
| 120 | +proc `div`*(a, b: RsU128): RsU128 {.importc: "rs_u128_div", nodecl.} | |
| 121 | +proc `mod`*(a, b: RsU128): RsU128 {.importc: "rs_u128_mod", nodecl.} | |
| 122 | +proc `and`*(a, b: RsU128): RsU128 {.importc: "rs_u128_and", nodecl.} | |
| 123 | +proc `or`*(a, b: RsU128): RsU128 {.importc: "rs_u128_or", nodecl.} | |
| 124 | +proc `xor`*(a, b: RsU128): RsU128 {.importc: "rs_u128_xor", nodecl.} | |
| 125 | +proc rsU128Eq(a, b: RsU128): cint {.importc: "rs_u128_eq", nodecl.} | |
| 126 | +proc `==`*(a, b: RsU128): bool = rsU128Eq(a, b) != 0 | |
| 127 | +proc rsU128Lt(a, b: RsU128): cint {.importc: "rs_u128_lt", nodecl.} | |
| 128 | +proc `<`*(a, b: RsU128): bool = rsU128Lt(a, b) != 0 | |
| 129 | +proc rsU128Le(a, b: RsU128): cint {.importc: "rs_u128_le", nodecl.} | |
| 130 | +proc `<=`*(a, b: RsU128): bool = rsU128Le(a, b) != 0 | |
| 131 | +proc `shl`*(a: RsU128, b: uint32): RsU128 {.importc: "rs_u128_shl", nodecl.} | |
| 132 | +proc `shr`*(a: RsU128, b: uint32): RsU128 {.importc: "rs_u128_shr", nodecl.} | |
| 133 | +proc `not`*(a: RsU128): RsU128 {.importc: "rs_u128_not", nodecl.} | |
| 134 | +proc rsU128Lo*(a: RsU128): uint64 {.importc: "rs_u128_lo", nodecl.} | |
| 135 | +proc `+`*(a, b: RsI128): RsI128 {.importc: "rs_i128_add", nodecl.} | |
| 136 | +proc `-`*(a, b: RsI128): RsI128 {.importc: "rs_i128_sub", nodecl.} | |
| 137 | +proc `*`*(a, b: RsI128): RsI128 {.importc: "rs_i128_mul", nodecl.} | |
| 138 | +proc `div`*(a, b: RsI128): RsI128 {.importc: "rs_i128_div", nodecl.} | |
| 139 | +proc `mod`*(a, b: RsI128): RsI128 {.importc: "rs_i128_mod", nodecl.} | |
| 140 | +proc `and`*(a, b: RsI128): RsI128 {.importc: "rs_i128_and", nodecl.} | |
| 141 | +proc `or`*(a, b: RsI128): RsI128 {.importc: "rs_i128_or", nodecl.} | |
| 142 | +proc `xor`*(a, b: RsI128): RsI128 {.importc: "rs_i128_xor", nodecl.} | |
| 143 | +proc rsI128Eq(a, b: RsI128): cint {.importc: "rs_i128_eq", nodecl.} | |
| 144 | +proc `==`*(a, b: RsI128): bool = rsI128Eq(a, b) != 0 | |
| 145 | +proc rsI128Lt(a, b: RsI128): cint {.importc: "rs_i128_lt", nodecl.} | |
| 146 | +proc `<`*(a, b: RsI128): bool = rsI128Lt(a, b) != 0 | |
| 147 | +proc rsI128Le(a, b: RsI128): cint {.importc: "rs_i128_le", nodecl.} | |
| 148 | +proc `<=`*(a, b: RsI128): bool = rsI128Le(a, b) != 0 | |
| 149 | +proc `shl`*(a: RsI128, b: uint32): RsI128 {.importc: "rs_i128_shl", nodecl.} | |
| 150 | +proc `shr`*(a: RsI128, b: uint32): RsI128 {.importc: "rs_i128_shr", nodecl.} | |
| 151 | +proc `not`*(a: RsI128): RsI128 {.importc: "rs_i128_not", nodecl.} | |
| 152 | +proc rsI128Lo*(a: RsI128): uint64 {.importc: "rs_i128_lo", nodecl.} | |
| 153 | + | |
| 154 | +proc rsU128FromU*(v: uint64): RsU128 {.importc: "rs_u128_fromu", nodecl.} | |
| 155 | +proc rsU128FromI*(v: int64): RsU128 {.importc: "rs_u128_fromi", nodecl.} | |
| 156 | +proc rsI128FromU*(v: uint64): RsI128 {.importc: "rs_i128_fromu", nodecl.} | |
| 157 | +proc rsI128FromI*(v: int64): RsI128 {.importc: "rs_i128_fromi", nodecl.} | |
| 158 | +proc rsU128OfI*(v: RsI128): RsU128 {.importc: "rs_u128_ofi", nodecl.} | |
| 159 | +proc rsU128OfU*(v: RsU128): RsU128 {.importc: "rs_u128_ofu", nodecl.} | |
| 160 | +proc rsI128OfU*(v: RsU128): RsI128 {.importc: "rs_i128_ofu", nodecl.} | |
| 161 | +proc rsI128OfI*(v: RsI128): RsI128 {.importc: "rs_i128_ofi", nodecl.} | |
| 162 | +proc `-`*(v: RsI128): RsI128 {.importc: "rs_i128_neg", nodecl.} | |
| 163 | +proc `-`*(v: RsU128): RsU128 {.importc: "rs_u128_neg", nodecl.} | |
| 164 | + | |
| 165 | +# `u128::MAX` is all ones; `i128::MIN` is the top bit alone, which is also what | |
| 166 | +# negating `i128::MAX + 1` produces. Both are spelled from their halves for the | |
| 167 | +# same reason the literals are. | |
| 168 | +let rsU128max* = rsU128Parts(high(uint64), high(uint64)) | |
| 169 | +let rsU128min* = rsU128Parts(0'u64, 0'u64) | |
| 170 | +let rsI128max* = rsI128Parts(cast[uint64](high(int64)), high(uint64)) | |
| 171 | +let rsI128min* = rsI128Parts(cast[uint64](low(int64)), 0'u64) | |
| 172 | + | |
| 173 | +proc `!=`*(a, b: RsU128): bool = not (a == b) | |
| 174 | +proc `!=`*(a, b: RsI128): bool = not (a == b) | |
| new file mode 100644 | |||
| @@ -0,0 +1,174 @@ | |||
| 1 | +# --------------------------------------------------------------------------- | ||
| 2 | +# 128-bit integers. | ||
| 3 | +# | ||
| 4 | +# Nim has no 128-bit integer, but C does: GCC and Clang provide `__int128` on | ||
| 5 | +# 64-bit targets, and that is what Rust's `u128`/`i128` compile to as well. So | ||
| 6 | +# this is a binding to the same machine arithmetic rather than a simulation -- | ||
| 7 | +# the same reasoning that makes `extern "C"` an exact mapping. | ||
| 8 | +# | ||
| 9 | +# Unsigned arithmetic wraps at 2^128 in both languages; verified against rustc, | ||
| 10 | +# including `u64::MAX * u64::MAX` and `u128::MAX.wrapping_add(1)`. | ||
| 11 | +# | ||
| 12 | +# Two limits, recorded rather than papered over: | ||
| 13 | +# | ||
| 14 | +# * `__int128` is a GCC/Clang extension and exists only on 64-bit targets. | ||
| 15 | +# MSVC has no equivalent. | ||
| 16 | +# * **Signed** overflow is undefined in C where Rust's debug profile panics. | ||
| 17 | +# Unsigned is exact; signed `i128` overflow is the one case where this | ||
| 18 | +# does not reproduce Rust's behaviour, and nothing here detects it. | ||
| 19 | +# | ||
| 20 | +# C has no `printf` conversion for `__int128`, so decimal rendering is done by | ||
| 21 | +# repeated division, which is why it is written out below. | ||
| 22 | +# --------------------------------------------------------------------------- | ||
| 23 | + | ||
| 24 | +{.emit: """ | ||
| 25 | +typedef unsigned __int128 rs_u128; | ||
| 26 | +typedef __int128 rs_i128; | ||
| 27 | + | ||
| 28 | +static rs_u128 rs_u128_parts(unsigned long long hi, unsigned long long lo) { | ||
| 29 | + return (((rs_u128)hi) << 64) | (rs_u128)lo; | ||
| 30 | +} | ||
| 31 | +static rs_i128 rs_i128_parts(unsigned long long hi, unsigned long long lo) { | ||
| 32 | + return (rs_i128)((((rs_u128)hi) << 64) | (rs_u128)lo); | ||
| 33 | +} | ||
| 34 | +static void rs_u128_str(rs_u128 v, char* out) { | ||
| 35 | + char buf[48]; int i = 0; | ||
| 36 | + if (v == 0) { out[0] = '0'; out[1] = 0; return; } | ||
| 37 | + while (v > 0) { buf[i++] = (char)('0' + (int)(v % 10)); v /= 10; } | ||
| 38 | + int j = 0; while (i > 0) out[j++] = buf[--i]; | ||
| 39 | + out[j] = 0; | ||
| 40 | +} | ||
| 41 | +static void rs_i128_str(rs_i128 v, char* out) { | ||
| 42 | + int neg = v < 0; rs_u128 m; | ||
| 43 | + if (neg) { m = (rs_u128)(-(v + 1)) + 1; } else { m = (rs_u128)v; } | ||
| 44 | + char tmp[48]; rs_u128_str(m, tmp); | ||
| 45 | + int j = 0; if (neg) out[j++] = '-'; | ||
| 46 | + int k = 0; while (tmp[k]) out[j++] = tmp[k++]; | ||
| 47 | + out[j] = 0; | ||
| 48 | +} | ||
| 49 | +static rs_u128 rs_u128_add(rs_u128 a, rs_u128 b) { return a + b; } | ||
| 50 | +static rs_u128 rs_u128_sub(rs_u128 a, rs_u128 b) { return a - b; } | ||
| 51 | +static rs_u128 rs_u128_mul(rs_u128 a, rs_u128 b) { return a * b; } | ||
| 52 | +static rs_u128 rs_u128_div(rs_u128 a, rs_u128 b) { return a / b; } | ||
| 53 | +static rs_u128 rs_u128_mod(rs_u128 a, rs_u128 b) { return a % b; } | ||
| 54 | +static rs_u128 rs_u128_and(rs_u128 a, rs_u128 b) { return a & b; } | ||
| 55 | +static rs_u128 rs_u128_or(rs_u128 a, rs_u128 b) { return a | b; } | ||
| 56 | +static rs_u128 rs_u128_xor(rs_u128 a, rs_u128 b) { return a ^ b; } | ||
| 57 | +static int rs_u128_eq(rs_u128 a, rs_u128 b) { return a == b; } | ||
| 58 | +static int rs_u128_lt(rs_u128 a, rs_u128 b) { return a < b; } | ||
| 59 | +static int rs_u128_le(rs_u128 a, rs_u128 b) { return a <= b; } | ||
| 60 | +static rs_u128 rs_u128_shl(rs_u128 a, unsigned b) { return a << b; } | ||
| 61 | +static rs_u128 rs_u128_shr(rs_u128 a, unsigned b) { return a >> b; } | ||
| 62 | +static rs_u128 rs_u128_not(rs_u128 a) { return ~a; } | ||
| 63 | +static unsigned long long rs_u128_lo(rs_u128 a) { return (unsigned long long)a; } | ||
| 64 | +static rs_i128 rs_i128_add(rs_i128 a, rs_i128 b) { return a + b; } | ||
| 65 | +static rs_i128 rs_i128_sub(rs_i128 a, rs_i128 b) { return a - b; } | ||
| 66 | +static rs_i128 rs_i128_mul(rs_i128 a, rs_i128 b) { return a * b; } | ||
| 67 | +static rs_i128 rs_i128_div(rs_i128 a, rs_i128 b) { return a / b; } | ||
| 68 | +static rs_i128 rs_i128_mod(rs_i128 a, rs_i128 b) { return a % b; } | ||
| 69 | +static rs_i128 rs_i128_and(rs_i128 a, rs_i128 b) { return a & b; } | ||
| 70 | +static rs_i128 rs_i128_or(rs_i128 a, rs_i128 b) { return a | b; } | ||
| 71 | +static rs_i128 rs_i128_xor(rs_i128 a, rs_i128 b) { return a ^ b; } | ||
| 72 | +static int rs_i128_eq(rs_i128 a, rs_i128 b) { return a == b; } | ||
| 73 | +static int rs_i128_lt(rs_i128 a, rs_i128 b) { return a < b; } | ||
| 74 | +static int rs_i128_le(rs_i128 a, rs_i128 b) { return a <= b; } | ||
| 75 | +static rs_i128 rs_i128_shl(rs_i128 a, unsigned b) { return a << b; } | ||
| 76 | +static rs_i128 rs_i128_shr(rs_i128 a, unsigned b) { return a >> b; } | ||
| 77 | +static rs_i128 rs_i128_not(rs_i128 a) { return ~a; } | ||
| 78 | +static unsigned long long rs_i128_lo(rs_i128 a) { return (unsigned long long)a; } | ||
| 79 | +static rs_u128 rs_u128_fromu(unsigned long long v) { return (rs_u128)v; } | ||
| 80 | +static rs_u128 rs_u128_fromi(long long v) { return (rs_u128)(rs_i128)v; } | ||
| 81 | +static rs_i128 rs_i128_fromu(unsigned long long v) { return (rs_i128)(rs_u128)v; } | ||
| 82 | +static rs_i128 rs_i128_fromi(long long v) { return (rs_i128)v; } | ||
| 83 | +static rs_u128 rs_u128_ofi(rs_i128 v) { return (rs_u128)v; } | ||
| 84 | +static rs_u128 rs_u128_ofu(rs_u128 v) { return v; } | ||
| 85 | +static rs_i128 rs_i128_ofu(rs_u128 v) { return (rs_i128)v; } | ||
| 86 | +static rs_i128 rs_i128_ofi(rs_i128 v) { return v; } | ||
| 87 | +static rs_i128 rs_i128_neg(rs_i128 v) { return -v; } | ||
| 88 | +static rs_u128 rs_u128_neg(rs_u128 v) { return -v; } | ||
| 89 | +""".} | ||
| 90 | + | ||
| 91 | +type | ||
| 92 | + RsU128* {.importc: "rs_u128", nodecl.} = object | ||
| 93 | + RsI128* {.importc: "rs_i128", nodecl.} = object | ||
| 94 | + | ||
| 95 | +proc rsU128Parts*(hi, lo: uint64): RsU128 {.importc: "rs_u128_parts", nodecl.} | ||
| 96 | +proc rsI128Parts*(hi, lo: uint64): RsI128 {.importc: "rs_i128_parts", nodecl.} | ||
| 97 | +proc rsU128StrRaw(v: RsU128, o: cstring) {.importc: "rs_u128_str", nodecl.} | ||
| 98 | +proc rsI128StrRaw(v: RsI128, o: cstring) {.importc: "rs_i128_str", nodecl.} | ||
| 99 | + | ||
| 100 | +proc `$`*(v: RsU128): string = | ||
| 101 | + var b = newString(48) | ||
| 102 | + rsU128StrRaw(v, cstring(b)) | ||
| 103 | + b.setLen(b.cstring.len) | ||
| 104 | + b | ||
| 105 | + | ||
| 106 | +proc `$`*(v: RsI128): string = | ||
| 107 | + var b = newString(48) | ||
| 108 | + rsI128StrRaw(v, cstring(b)) | ||
| 109 | + b.setLen(b.cstring.len) | ||
| 110 | + b | ||
| 111 | + | ||
| 112 | +proc rsDisplay*(v: RsU128): string = $v | ||
| 113 | +proc rsDebug*(v: RsU128): string = $v | ||
| 114 | +proc rsDisplay*(v: RsI128): string = $v | ||
| 115 | +proc rsDebug*(v: RsI128): string = $v | ||
| 116 | + | ||
| 117 | +proc `+`*(a, b: RsU128): RsU128 {.importc: "rs_u128_add", nodecl.} | ||
| 118 | +proc `-`*(a, b: RsU128): RsU128 {.importc: "rs_u128_sub", nodecl.} | ||
| 119 | +proc `*`*(a, b: RsU128): RsU128 {.importc: "rs_u128_mul", nodecl.} | ||
| 120 | +proc `div`*(a, b: RsU128): RsU128 {.importc: "rs_u128_div", nodecl.} | ||
| 121 | +proc `mod`*(a, b: RsU128): RsU128 {.importc: "rs_u128_mod", nodecl.} | ||
| 122 | +proc `and`*(a, b: RsU128): RsU128 {.importc: "rs_u128_and", nodecl.} | ||
| 123 | +proc `or`*(a, b: RsU128): RsU128 {.importc: "rs_u128_or", nodecl.} | ||
| 124 | +proc `xor`*(a, b: RsU128): RsU128 {.importc: "rs_u128_xor", nodecl.} | ||
| 125 | +proc rsU128Eq(a, b: RsU128): cint {.importc: "rs_u128_eq", nodecl.} | ||
| 126 | +proc `==`*(a, b: RsU128): bool = rsU128Eq(a, b) != 0 | ||
| 127 | +proc rsU128Lt(a, b: RsU128): cint {.importc: "rs_u128_lt", nodecl.} | ||
| 128 | +proc `<`*(a, b: RsU128): bool = rsU128Lt(a, b) != 0 | ||
| 129 | +proc rsU128Le(a, b: RsU128): cint {.importc: "rs_u128_le", nodecl.} | ||
| 130 | +proc `<=`*(a, b: RsU128): bool = rsU128Le(a, b) != 0 | ||
| 131 | +proc `shl`*(a: RsU128, b: uint32): RsU128 {.importc: "rs_u128_shl", nodecl.} | ||
| 132 | +proc `shr`*(a: RsU128, b: uint32): RsU128 {.importc: "rs_u128_shr", nodecl.} | ||
| 133 | +proc `not`*(a: RsU128): RsU128 {.importc: "rs_u128_not", nodecl.} | ||
| 134 | +proc rsU128Lo*(a: RsU128): uint64 {.importc: "rs_u128_lo", nodecl.} | ||
| 135 | +proc `+`*(a, b: RsI128): RsI128 {.importc: "rs_i128_add", nodecl.} | ||
| 136 | +proc `-`*(a, b: RsI128): RsI128 {.importc: "rs_i128_sub", nodecl.} | ||
| 137 | +proc `*`*(a, b: RsI128): RsI128 {.importc: "rs_i128_mul", nodecl.} | ||
| 138 | +proc `div`*(a, b: RsI128): RsI128 {.importc: "rs_i128_div", nodecl.} | ||
| 139 | +proc `mod`*(a, b: RsI128): RsI128 {.importc: "rs_i128_mod", nodecl.} | ||
| 140 | +proc `and`*(a, b: RsI128): RsI128 {.importc: "rs_i128_and", nodecl.} | ||
| 141 | +proc `or`*(a, b: RsI128): RsI128 {.importc: "rs_i128_or", nodecl.} | ||
| 142 | +proc `xor`*(a, b: RsI128): RsI128 {.importc: "rs_i128_xor", nodecl.} | ||
| 143 | +proc rsI128Eq(a, b: RsI128): cint {.importc: "rs_i128_eq", nodecl.} | ||
| 144 | +proc `==`*(a, b: RsI128): bool = rsI128Eq(a, b) != 0 | ||
| 145 | +proc rsI128Lt(a, b: RsI128): cint {.importc: "rs_i128_lt", nodecl.} | ||
| 146 | +proc `<`*(a, b: RsI128): bool = rsI128Lt(a, b) != 0 | ||
| 147 | +proc rsI128Le(a, b: RsI128): cint {.importc: "rs_i128_le", nodecl.} | ||
| 148 | +proc `<=`*(a, b: RsI128): bool = rsI128Le(a, b) != 0 | ||
| 149 | +proc `shl`*(a: RsI128, b: uint32): RsI128 {.importc: "rs_i128_shl", nodecl.} | ||
| 150 | +proc `shr`*(a: RsI128, b: uint32): RsI128 {.importc: "rs_i128_shr", nodecl.} | ||
| 151 | +proc `not`*(a: RsI128): RsI128 {.importc: "rs_i128_not", nodecl.} | ||
| 152 | +proc rsI128Lo*(a: RsI128): uint64 {.importc: "rs_i128_lo", nodecl.} | ||
| 153 | + | ||
| 154 | +proc rsU128FromU*(v: uint64): RsU128 {.importc: "rs_u128_fromu", nodecl.} | ||
| 155 | +proc rsU128FromI*(v: int64): RsU128 {.importc: "rs_u128_fromi", nodecl.} | ||
| 156 | +proc rsI128FromU*(v: uint64): RsI128 {.importc: "rs_i128_fromu", nodecl.} | ||
| 157 | +proc rsI128FromI*(v: int64): RsI128 {.importc: "rs_i128_fromi", nodecl.} | ||
| 158 | +proc rsU128OfI*(v: RsI128): RsU128 {.importc: "rs_u128_ofi", nodecl.} | ||
| 159 | +proc rsU128OfU*(v: RsU128): RsU128 {.importc: "rs_u128_ofu", nodecl.} | ||
| 160 | +proc rsI128OfU*(v: RsU128): RsI128 {.importc: "rs_i128_ofu", nodecl.} | ||
| 161 | +proc rsI128OfI*(v: RsI128): RsI128 {.importc: "rs_i128_ofi", nodecl.} | ||
| 162 | +proc `-`*(v: RsI128): RsI128 {.importc: "rs_i128_neg", nodecl.} | ||
| 163 | +proc `-`*(v: RsU128): RsU128 {.importc: "rs_u128_neg", nodecl.} | ||
| 164 | + | ||
| 165 | +# `u128::MAX` is all ones; `i128::MIN` is the top bit alone, which is also what | ||
| 166 | +# negating `i128::MAX + 1` produces. Both are spelled from their halves for the | ||
| 167 | +# same reason the literals are. | ||
| 168 | +let rsU128max* = rsU128Parts(high(uint64), high(uint64)) | ||
| 169 | +let rsU128min* = rsU128Parts(0'u64, 0'u64) | ||
| 170 | +let rsI128max* = rsI128Parts(cast[uint64](high(int64)), high(uint64)) | ||
| 171 | +let rsI128min* = rsI128Parts(cast[uint64](low(int64)), 0'u64) | ||
| 172 | + | ||
| 173 | +proc `!=`*(a, b: RsU128): bool = not (a == b) | ||
| 174 | +proc `!=`*(a, b: RsI128): bool = not (a == b) | ||
modified
src/ty.rs +23 -7 | @@ -102,11 +102,21 @@ impl Nim { | ||
| 102 | 102 | pub fn is_integer(&self) -> bool { |
| 103 | 103 | matches!(self, Nim::Prim(p) if matches!(p.as_str(), |
| 104 | 104 | "int8"|"int16"|"int32"|"int64"|"int"| |
| 105 | - "uint8"|"uint16"|"uint32"|"uint64"|"uint")) | |
| 105 | + "uint8"|"uint16"|"uint32"|"uint64"|"uint"| | |
| 106 | + "RsI128"|"RsU128")) | |
| 106 | 107 | } |
| 107 | 108 | |
| 108 | 109 | pub fn is_unsigned(&self) -> bool { |
| 109 | - matches!(self, Nim::Prim(p) if p.starts_with("uint")) | |
| 110 | + matches!(self, Nim::Prim(p) if p.starts_with("uint") || p == "RsU128") | |
| 111 | + } | |
| 112 | + | |
| 113 | + /// `Some(signed)` if this is one of the C-backed 128-bit integers. | |
| 114 | + pub fn as_i128(&self) -> Option<bool> { | |
| 115 | + match self { | |
| 116 | + Nim::Prim(p) if p == "RsI128" => Some(true), | |
| 117 | + Nim::Prim(p) if p == "RsU128" => Some(false), | |
| 118 | + _ => None, | |
| 119 | + } | |
| 110 | 120 | } |
| 111 | 121 | } |
| 112 | 122 | |
| @@ -154,6 +164,10 @@ pub fn prim(name: &str) -> Option<Nim> { | ||
| 154 | 164 | "u32" => "uint32", |
| 155 | 165 | "u64" => "uint64", |
| 156 | 166 | "usize" => "uint", |
| 167 | + // Nim has no 128-bit integer, but C does, and it is the same machine | |
| 168 | + // arithmetic Rust itself compiles to. See `src/prelude_i128.nim`. | |
| 169 | + "i128" => "RsI128", | |
| 170 | + "u128" => "RsU128", | |
| 157 | 171 | "f32" => "float32", |
| 158 | 172 | "f64" => "float64", |
| 159 | 173 | "bool" => "bool", |
| @@ -168,11 +182,13 @@ pub fn prim(name: &str) -> Option<Nim> { | ||
| 168 | 182 | } |
| 169 | 183 | |
| 170 | 184 | /// Types we refuse rather than approximate. |
| 171 | -pub fn rejected(name: &str) -> Option<&'static str> { | |
| 172 | - match name { | |
| 173 | - "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"), | |
| 174 | - _ => None, | |
| 175 | - } | |
| 185 | +/// | |
| 186 | +/// Empty at present: `i128`/`u128` were the last entry, and they turned out to | |
| 187 | +/// have an exact equivalent after all -- the C `__int128` that Rust itself | |
| 188 | +/// lowers them to. The hook stays because the rule it serves is the point of | |
| 189 | +/// the project, not because nothing will ever fail again. | |
| 190 | +pub fn rejected(_name: &str) -> Option<&'static str> { | |
| 191 | + None | |
| 176 | 192 | } |
| 177 | 193 | |
| 178 | 194 | /// The Nim type name for a const-qualified C pointer to `t`. |
| @@ -102,11 +102,21 @@ impl Nim { | |||
| 102 | pub fn is_integer(&self) -> bool { | 102 | pub fn is_integer(&self) -> bool { |
| 103 | matches!(self, Nim::Prim(p) if matches!(p.as_str(), | 103 | matches!(self, Nim::Prim(p) if matches!(p.as_str(), |
| 104 | "int8"|"int16"|"int32"|"int64"|"int"| | 104 | "int8"|"int16"|"int32"|"int64"|"int"| |
| 105 | - "uint8"|"uint16"|"uint32"|"uint64"|"uint")) | 105 | + "uint8"|"uint16"|"uint32"|"uint64"|"uint"| |
| 106 | + "RsI128"|"RsU128")) | ||
| 106 | } | 107 | } |
| 107 | 108 | ||
| 108 | pub fn is_unsigned(&self) -> bool { | 109 | pub fn is_unsigned(&self) -> bool { |
| 109 | - matches!(self, Nim::Prim(p) if p.starts_with("uint")) | 110 | + matches!(self, Nim::Prim(p) if p.starts_with("uint") || p == "RsU128") |
| 111 | + } | ||
| 112 | + | ||
| 113 | + /// `Some(signed)` if this is one of the C-backed 128-bit integers. | ||
| 114 | + pub fn as_i128(&self) -> Option<bool> { | ||
| 115 | + match self { | ||
| 116 | + Nim::Prim(p) if p == "RsI128" => Some(true), | ||
| 117 | + Nim::Prim(p) if p == "RsU128" => Some(false), | ||
| 118 | + _ => None, | ||
| 119 | + } | ||
| 110 | } | 120 | } |
| 111 | } | 121 | } |
| 112 | 122 | ||
| @@ -154,6 +164,10 @@ pub fn prim(name: &str) -> Option<Nim> { | |||
| 154 | "u32" => "uint32", | 164 | "u32" => "uint32", |
| 155 | "u64" => "uint64", | 165 | "u64" => "uint64", |
| 156 | "usize" => "uint", | 166 | "usize" => "uint", |
| 167 | + // Nim has no 128-bit integer, but C does, and it is the same machine | ||
| 168 | + // arithmetic Rust itself compiles to. See `src/prelude_i128.nim`. | ||
| 169 | + "i128" => "RsI128", | ||
| 170 | + "u128" => "RsU128", | ||
| 157 | "f32" => "float32", | 171 | "f32" => "float32", |
| 158 | "f64" => "float64", | 172 | "f64" => "float64", |
| 159 | "bool" => "bool", | 173 | "bool" => "bool", |
| @@ -168,11 +182,13 @@ pub fn prim(name: &str) -> Option<Nim> { | |||
| 168 | } | 182 | } |
| 169 | 183 | ||
| 170 | /// Types we refuse rather than approximate. | 184 | /// Types we refuse rather than approximate. |
| 171 | -pub fn rejected(name: &str) -> Option<&'static str> { | 185 | +/// |
| 172 | - match name { | 186 | +/// Empty at present: `i128`/`u128` were the last entry, and they turned out to |
| 173 | - "i128" | "u128" => Some("128-bit integers have no faithful Nim equivalent"), | 187 | +/// have an exact equivalent after all -- the C `__int128` that Rust itself |
| 174 | - _ => None, | 188 | +/// lowers them to. The hook stays because the rule it serves is the point of |
| 175 | - } | 189 | +/// the project, not because nothing will ever fail again. |
| 190 | +pub fn rejected(_name: &str) -> Option<&'static str> { | ||
| 191 | + None | ||
| 176 | } | 192 | } |
| 177 | 193 | ||
| 178 | /// The Nim type name for a const-qualified C pointer to `t`. | 194 | /// The Nim type name for a const-qualified C pointer to `t`. |
added
tests/cases/049-i128.rs +34 -0 | new file mode 100644 | ||
| @@ -0,0 +1,34 @@ | ||
| 1 | +// 128-bit integers. Nim has none; C has `__int128`, which is what rustc | |
| 2 | +// lowers `u128`/`i128` to as well, so this is a binding rather than a model. | |
| 3 | +fn main() { | |
| 4 | + let a: u128 = 18446744073709551615; | |
| 5 | + let b: u128 = 18446744073709551616; | |
| 6 | + println!("{}", a); | |
| 7 | + println!("{}", b); | |
| 8 | + println!("{}", a + b); | |
| 9 | + println!("{}", a < b); | |
| 10 | + println!("{}", a * a); | |
| 11 | + println!("{}", u128::MAX); | |
| 12 | + println!("{}", u128::MIN); | |
| 13 | + println!("{}", i128::MIN); | |
| 14 | + println!("{}", i128::MAX); | |
| 15 | + | |
| 16 | + let c: u128 = 1000; | |
| 17 | + println!("{} {} {}", c / 7, c % 7, c - 1); | |
| 18 | + println!("{} {} {}", c & 12, c | 3, c ^ 5); | |
| 19 | + | |
| 20 | + let d: i128 = -170141183460469231731687303715884105727; | |
| 21 | + println!("{}", d); | |
| 22 | + println!("{}", -d); | |
| 23 | + | |
| 24 | + let e = 5u64; | |
| 25 | + println!("{}", e as u128 * 1000000000000000000000u128); | |
| 26 | + println!("{}", (b + 5) as u64); | |
| 27 | + println!("{}", a as i128); | |
| 28 | + | |
| 29 | + let mut t: u128 = 0; | |
| 30 | + for i in 0..10u64 { | |
| 31 | + t = t + (i as u128) * (i as u128); | |
| 32 | + } | |
| 33 | + println!("{}", t); | |
| 34 | +} | |
| new file mode 100644 | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | +// 128-bit integers. Nim has none; C has `__int128`, which is what rustc | ||
| 2 | +// lowers `u128`/`i128` to as well, so this is a binding rather than a model. | ||
| 3 | +fn main() { | ||
| 4 | + let a: u128 = 18446744073709551615; | ||
| 5 | + let b: u128 = 18446744073709551616; | ||
| 6 | + println!("{}", a); | ||
| 7 | + println!("{}", b); | ||
| 8 | + println!("{}", a + b); | ||
| 9 | + println!("{}", a < b); | ||
| 10 | + println!("{}", a * a); | ||
| 11 | + println!("{}", u128::MAX); | ||
| 12 | + println!("{}", u128::MIN); | ||
| 13 | + println!("{}", i128::MIN); | ||
| 14 | + println!("{}", i128::MAX); | ||
| 15 | + | ||
| 16 | + let c: u128 = 1000; | ||
| 17 | + println!("{} {} {}", c / 7, c % 7, c - 1); | ||
| 18 | + println!("{} {} {}", c & 12, c | 3, c ^ 5); | ||
| 19 | + | ||
| 20 | + let d: i128 = -170141183460469231731687303715884105727; | ||
| 21 | + println!("{}", d); | ||
| 22 | + println!("{}", -d); | ||
| 23 | + | ||
| 24 | + let e = 5u64; | ||
| 25 | + println!("{}", e as u128 * 1000000000000000000000u128); | ||
| 26 | + println!("{}", (b + 5) as u64); | ||
| 27 | + println!("{}", a as i128); | ||
| 28 | + | ||
| 29 | + let mut t: u128 = 0; | ||
| 30 | + for i in 0..10u64 { | ||
| 31 | + t = t + (i as u128) * (i as u128); | ||
| 32 | + } | ||
| 33 | + println!("{}", t); | ||
| 34 | +} | ||
deleted
tests/cases/900-reject-i128.rs +0 -6 | deleted file mode 100644 | ||
| @@ -1,6 +0,0 @@ | ||
| 1 | -//@ reject: 128-bit integers have no faithful Nim equivalent | |
| 2 | -// The rule that this project exists to enforce: no silent approximation. | |
| 3 | -fn main() { | |
| 4 | - let x: i128 = 1; | |
| 5 | - println!("{}", x); | |
| 6 | -} | |
| deleted file mode 100644 | |||
| @@ -1,6 +0,0 @@ | |||
| 1 | -//@ reject: 128-bit integers have no faithful Nim equivalent | ||
| 2 | -// The rule that this project exists to enforce: no silent approximation. | ||
| 3 | -fn main() { | ||
| 4 | - let x: i128 = 1; | ||
| 5 | - println!("{}", x); | ||
| 6 | -} | ||