nandi/rustnimpublic Fork
⑂ f9ca982
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/rustnim.git
git clone ssh://git@rickub.com/nandi/rustnim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

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>
nandithebull committed 2026-09-19T09:09:45-07:00 Browse files
f9ca982 parent: 8f6401f
modified DESIGN.md +34 -14
@@ -52,8 +52,13 @@ first rule of this project is the one that codebase broke:
5252
5353 > **Never approximate a semantic you cannot represent. Fail loudly instead.**
5454
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.
5762
5863 ## Architecture
5964
@@ -258,8 +263,19 @@ runner) rather than a wrong answer.
258263
259264 ## Mapping decisions made so far
260265
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.
263279 - **Indexing**: both 0-based. Direct.
264280 - **`&T`** → plain value. **`&mut T`** → `var T` parameter.
265281 - **`&[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:
364380 | crate | outcome |
365381 |---|---|
366382 | `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 |
369385 | `hex` 0.4.3 | rejected: `impl Iterator` needs an associated type |
370386 | `crc32fast` 1.5.0 | rejected: directory modules (`specialized/mod.rs`), then SIMD intrinsics |
371387
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
375391 features — associated types, and `mod` directories.
376392
377393 ## How far off is a crate like `libcosmic`?
@@ -400,9 +416,11 @@ rustnim on that took **two** blockers to get through:
400416 1. destructuring parameters — `fn from((column, row): (u16, u16))`, which
401417 `iced` writes several times. Named here and taken apart at the top of the
402418 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.
406424
407425 So the expanded crate clears the language and halts at the project's own
408426 first principle. That is a much more interesting place to stop than "generics
@@ -1126,8 +1144,10 @@ Cases carry directives in leading `//@` comments:
11261144 | `//@ extern: <crate>` | the **oracle** links this crate; rustnim does not get it |
11271145
11281146 `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.)
11311151
11321152 Run one case with `RUSTNIM_CASE=005 cargo test --test differential --
11331153 --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 rather55+`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 ## Architecture63 ## Architecture
59 64
@@ -258,8 +263,19 @@ runner) rather than a wrong answer.
258 263
259 ## Mapping decisions made so far264 ## 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 job388+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 being389+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 missing390+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))`, which416 1. destructuring parameters — `fn from((column, row): (u16, u16))`, which
401 `iced` writes several times. Named here and taken apart at the top of the417 `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, from419+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 the420+ `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 own425 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 "generics426 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 merely1146 `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-library1147+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 root1153 --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 {
300300 /// a type, which happens behind `&self`, and the answer has to be known
301301 /// before the prelude is written.
302302 uses_sync: std::cell::Cell<bool>,
303+ uses_i128: std::cell::Cell<bool>,
303304 /// Module names supplied as separate input files. A `mod x;` naming one
304305 /// of these is satisfied by that file having been passed in.
305306 pub modules: Vec<String>,
@@ -370,6 +371,7 @@ impl Lowerer {
370371 pending_locks: Vec::new(),
371372 uses_async: false,
372373 uses_sync: std::cell::Cell::new(false),
374+ uses_i128: std::cell::Cell::new(false),
373375 modules: Vec::new(),
374376 emitted: 0,
375377 features: Vec::new(),
@@ -534,6 +536,9 @@ impl Lowerer {
534536 out.push_str(include_str!("prelude_async.nim"));
535537 out.push('\n');
536538 }
539+ if self.uses_i128.get() {
540+ out.push_str(include_str!("prelude_i128.nim"));
541+ }
537542 if self.uses_sync.get() {
538543 out.push_str(include_str!("prelude_sync.nim"));
539544 out.push('\n');
@@ -3912,6 +3917,15 @@ impl Lowerer {
39123917 if matches!(name.as_str(), "MAX" | "MIN") {
39133918 if let Some(q) = p.path.segments.iter().rev().nth(1) {
39143919 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+ }
39153929 if t.is_integer() {
39163930 let f = if name == "MAX" { "high" } else { "low" };
39173931 return Ok(Val::new(
@@ -4368,6 +4382,20 @@ impl Lowerer {
43684382 } else {
43694383 ty::prim(suffix).ok_or_else(|| format!("unknown literal suffix `{suffix}`"))?
43704384 };
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+ }
43714399 Ok(Val::new(format!("{}'{}", digits, nim_suffix(&t)?), Some(t)))
43724400 }
43734401 Lit::Float(f) => {
@@ -4537,6 +4565,43 @@ impl Lowerer {
45374565 })?;
45384566
45394567 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+ }
45404605 (f, t) if f.is_integer() && t.is_integer() => {
45414606 // Rust's `as` between integers is a pure bit-width truncation
45424607 // or sign-extension, never a range check. `cast` says exactly
@@ -6468,6 +6533,8 @@ fn default_of(t: &Nim) -> Option<String> {
64686533 Nim::Prim(p) if p == "string" => "\"\"".into(),
64696534 Nim::Prim(p) if p == "bool" => "false".into(),
64706535 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(),
64716538 Nim::Prim(_) if t.is_integer() => format!("{}(0)", t.render()),
64726539 Nim::Seq(e) => format!("newSeq[{}]()", e.render()),
64736540 _ => return None,
@@ -300,6 +300,7 @@ pub struct Lowerer {
300 /// a type, which happens behind `&self`, and the answer has to be known300 /// 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 one304 /// 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 truncation4606 // Rust's `as` between integers is a pure bit-width truncation
4542 // or sign-extension, never a range check. `cast` says exactly4607 // 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 {
102102 pub fn is_integer(&self) -> bool {
103103 matches!(self, Nim::Prim(p) if matches!(p.as_str(),
104104 "int8"|"int16"|"int32"|"int64"|"int"|
105- "uint8"|"uint16"|"uint32"|"uint64"|"uint"))
105+ "uint8"|"uint16"|"uint32"|"uint64"|"uint"|
106+ "RsI128"|"RsU128"))
106107 }
107108
108109 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+ }
110120 }
111121 }
112122
@@ -154,6 +164,10 @@ pub fn prim(name: &str) -> Option<Nim> {
154164 "u32" => "uint32",
155165 "u64" => "uint64",
156166 "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",
157171 "f32" => "float32",
158172 "f64" => "float64",
159173 "bool" => "bool",
@@ -168,11 +182,13 @@ pub fn prim(name: &str) -> Option<Nim> {
168182 }
169183
170184 /// 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
176192 }
177193
178194 /// 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-}