nandi/rustnimpublic Fork
⑂ a721ac2
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

Lower atomics; the correspondence is exact, not approximate

I had left atomics out on the grounds that they were semantically risky.
That was wrong, and probing rather than assuming showed why: both languages
compile these to the same C11 primitives, and Nim's MemoryOrder is a total
match for Rust's Ordering -- Relaxed/Acquire/Release/AcqRel/SeqCst against
moRelaxed/moAcquire/moRelease/moAcquireRelease/moSequentiallyConsistent --
with the operations corresponding one for one. The "indistinguishable from a
plain variable without threads" worry was beside the point; the mapping is
exact either way.

Two things needed care. Rust's atomics mutate through &self, so `let a =
AtomicUsize::new(0)` is still writable; Nim needs var for that, and the
lowering emits var for an atomic however Rust spelled the binding.
compare_exchange reports differently -- Rust gives Ok(previous) or
Err(actual) where Nim returns a bool and writes the actual value back through
its expected argument -- so the prelude bridges them and the test pins both
outcomes.

Also here, and more generally useful: the prelude is now composed after the
body rather than before it. Whether a program needs the async dispatcher or
the locking runtime is discovered while lowering -- an AtomicUsize::new in a
function body says so and nothing in the signatures does -- so emitting the
prelude first meant predicting it, and the prediction was wrong. Rendering
the body into a buffer and composing around it removes the ordering
constraint instead of adding another pre-scan. Verified that 001-hello and
the base16ct crate import neither runtime, the async case imports only
asyncdispatch, and the atomics and mutex cases only locks/atomics.

Accepted crates unchanged at 18 of 400.

51 differential cases, all green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-19T07:08:24-07:00 Browse files
a721ac2 parent: 8587dec
modified DESIGN.md +33 -3
@@ -878,9 +878,39 @@ Two differences are real and are recorded rather than papered over:
878878 to `quit(101)` rather than unwinding, so there is no holder left to poison
879879 it — but a program that depends on *observing* poisoning would differ.
880880
881-`AtomicUsize` is still unmapped, and deliberately: it needs memory orderings,
882-and would be indistinguishable from a plain variable in a program with no
883-threads — which cannot be verified here.
881+### Atomics
882+
883+I had left these out on the grounds that they were semantically risky. That
884+was wrong, and checking rather than assuming showed why: **both languages
885+compile atomics to the same C11 primitives**, and Nim's `MemoryOrder` is a
886+*total* match for Rust's `Ordering` —
887+`Relaxed`/`Acquire`/`Release`/`AcqRel`/`SeqCst` against `moRelaxed`/
888+`moAcquire`/`moRelease`/`moAcquireRelease`/`moSequentiallyConsistent`. The
889+operations correspond one for one. This is not an approximation, and the
890+"indistinguishable from a plain variable without threads" worry is beside the
891+point — the mapping is exact either way.
892+
893+Two things needed care:
894+
895+- **Rust's atomics mutate through `&self`** — interior mutability — so `let a
896+ = AtomicUsize::new(0)` is still writable. Nim needs `var` for that, so the
897+ lowering emits `var` for an atomic regardless of how Rust spelled the
898+ binding. `tests/cases/045` opens with exactly that case.
899+- **`compare_exchange` reports differently.** Rust returns `Ok(previous)` or
900+ `Err(actual)`; Nim returns a `bool` and writes the actual value back through
901+ its `expected` argument. The prelude bridges them, and the test pins both
902+ outcomes.
903+
904+### The prelude is composed last
905+
906+Whether a program needs the async dispatcher or the locking runtime is
907+discovered *while* lowering — an `AtomicUsize::new` in a function body says
908+so, and nothing in the signatures does. Emitting the prelude first meant
909+predicting that, and the prediction was wrong. The body is now rendered into a
910+buffer and the prelude composed around it afterwards, which removes the
911+ordering constraint rather than adding another pre-scan. Verified: `001-hello`
912+and the `base16ct` crate import neither runtime, the async case imports only
913+`asyncdispatch`, the atomics and mutex cases only `locks`/`atomics`.
884914
885915 ### The harness had a hole
886916
@@ -878,9 +878,39 @@ Two differences are real and are recorded rather than papered over:
878 to `quit(101)` rather than unwinding, so there is no holder left to poison878 to `quit(101)` rather than unwinding, so there is no holder left to poison
879 it — but a program that depends on *observing* poisoning would differ.879 it — but a program that depends on *observing* poisoning would differ.
880 880
881-`AtomicUsize` is still unmapped, and deliberately: it needs memory orderings,881+### Atomics
882-and would be indistinguishable from a plain variable in a program with no882+
883-threads — which cannot be verified here.883+I had left these out on the grounds that they were semantically risky. That
884+was wrong, and checking rather than assuming showed why: **both languages
885+compile atomics to the same C11 primitives**, and Nim's `MemoryOrder` is a
886+*total* match for Rust's `Ordering` —
887+`Relaxed`/`Acquire`/`Release`/`AcqRel`/`SeqCst` against `moRelaxed`/
888+`moAcquire`/`moRelease`/`moAcquireRelease`/`moSequentiallyConsistent`. The
889+operations correspond one for one. This is not an approximation, and the
890+"indistinguishable from a plain variable without threads" worry is beside the
891+point — the mapping is exact either way.
892+
893+Two things needed care:
894+
895+- **Rust's atomics mutate through `&self`** — interior mutability — so `let a
896+ = AtomicUsize::new(0)` is still writable. Nim needs `var` for that, so the
897+ lowering emits `var` for an atomic regardless of how Rust spelled the
898+ binding. `tests/cases/045` opens with exactly that case.
899+- **`compare_exchange` reports differently.** Rust returns `Ok(previous)` or
900+ `Err(actual)`; Nim returns a `bool` and writes the actual value back through
901+ its `expected` argument. The prelude bridges them, and the test pins both
902+ outcomes.
903+
904+### The prelude is composed last
905+
906+Whether a program needs the async dispatcher or the locking runtime is
907+discovered *while* lowering — an `AtomicUsize::new` in a function body says
908+so, and nothing in the signatures does. Emitting the prelude first meant
909+predicting that, and the prediction was wrong. The body is now rendered into a
910+buffer and the prelude composed around it afterwards, which removes the
911+ordering constraint rather than adding another pre-scan. Verified: `001-hello`
912+and the `base16ct` crate import neither runtime, the async case imports only
913+`asyncdispatch`, the atomics and mutex cases only `locks`/`atomics`.
884 914
885 ### The harness had a hole915 ### The harness had a hole
886 916
modified src/lower.rs +123 -16
@@ -412,8 +412,6 @@ impl Lowerer {
412412 // ---------------------------------------------------------------- file
413413
414414 pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {
415- self.out.push_str(include_str!("prelude.nim"));
416- self.blank();
417415
418416 // Pass 0: type aliases. A signature in one file may use an alias
419417 // declared in another, and inputs are given in whatever order suits
@@ -433,17 +431,6 @@ impl Lowerer {
433431 self.collect(item)?;
434432 }
435433 }
436- // The async runtime is only pulled in when something needs it: it
437- // costs compile time, and most programs have no futures at all.
438- if self.uses_async {
439- self.out.push_str(include_str!("prelude_async.nim"));
440- self.blank();
441- }
442- if self.uses_sync.get() {
443- self.out.push_str(include_str!("prelude_sync.nim"));
444- self.blank();
445- }
446-
447434 // Pass 2: type definitions, which every signature may mention.
448435 for (m, f) in files {
449436 self.cur_mod = m.clone();
@@ -506,7 +493,24 @@ impl Lowerer {
506493 self.line(" quit(101)");
507494 self.indent -= 1;
508495 }
509- Ok(std::mem::take(&mut self.out))
496+
497+ // The prelude is composed *after* the body, so that whether a runtime
498+ // is needed can be discovered while lowering rather than predicted
499+ // before it. The async dispatcher and the locking runtime each cost
500+ // compile time, and most programs need neither.
501+ let body = std::mem::take(&mut self.out);
502+ let mut out = String::from(include_str!("prelude.nim"));
503+ out.push('\n');
504+ if self.uses_async {
505+ out.push_str(include_str!("prelude_async.nim"));
506+ out.push('\n');
507+ }
508+ if self.uses_sync.get() {
509+ out.push_str(include_str!("prelude_sync.nim"));
510+ out.push('\n');
511+ }
512+ out.push_str(&body);
513+ Ok(out)
510514 }
511515
512516 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
@@ -2557,7 +2561,12 @@ impl Lowerer {
25572561 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
25582562 // Rust may write through it, and Nim only accepts a `var` where a
25592563 // `var` parameter is wanted, so the binding has to be one.
2560- let mutable = mutable || is_mut_borrow(&init.expr);
2564+ // Rust's atomics mutate through `&self` -- interior mutability -- so
2565+ // an immutable `let` still writes. Nim needs `var` for that, and
2566+ // emitting it is what makes the binding behave as Rust's does.
2567+ let mutable = mutable
2568+ || is_mut_borrow(&init.expr)
2569+ || matches!(&t, Nim::Named(n, _) if n == "Atomic");
25612570 let kw = if mutable { "var" } else { "let" };
25622571 // Inside a generic proc the binding's type may mention a parameter Nim
25632572 // will infer; naming it in an annotation would not resolve.
@@ -3645,6 +3654,25 @@ impl Lowerer {
36453654 if name == "None" {
36463655 return Ok(Val::new(self.none_of(expect), expect.cloned()));
36473656 }
3657+ // `Ordering::SeqCst` and friends. Nim's names differ but the
3658+ // set is the same, and both mean the C11 ordering.
3659+ if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3660+ if q == "Ordering" && !self.enums.contains_key(&q) {
3661+ let mo = match name.as_str() {
3662+ "Relaxed" => Some("moRelaxed"),
3663+ "Acquire" => Some("moAcquire"),
3664+ "Release" => Some("moRelease"),
3665+ "AcqRel" => Some("moAcquireRelease"),
3666+ "SeqCst" => Some("moSequentiallyConsistent"),
3667+ _ => None,
3668+ };
3669+ if let Some(mo) = mo {
3670+ self.uses_sync.set(true);
3671+ return Ok(Val::new(mo, Some(Nim::Prim("MemoryOrder".into()))));
3672+ }
3673+ }
3674+ }
3675+
36483676 // `log::Level` and `log::LevelFilter` come from the facade
36493677 // shim, under names no crate can collide with.
36503678 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
@@ -4784,6 +4812,19 @@ impl Lowerer {
47844812 inner.map(|t| Nim::Named("RsArc".into(), vec![t])),
47854813 ));
47864814 }
4815+ (Some(q), "new", 1) if q.starts_with("Atomic") => {
4816+ self.uses_sync.set(true);
4817+ let t = ty::map(&syn::parse_str::<syn::Type>(q).map_err(|e| e.to_string())?)?;
4818+ // The atomic's own element type decides the literal's width;
4819+ // `AtomicUsize::new(5)` holds a `uint`, not the `i32` an
4820+ // unconstrained literal would default to.
4821+ let inner = match &t {
4822+ Nim::Named(_, a) => a.first().cloned(),
4823+ _ => None,
4824+ };
4825+ let v = self.expr_at(&c.args[0], inner.as_ref())?;
4826+ return Ok(Val::new(format!("rsAtomicNew({})", v.code), Some(t)));
4827+ }
47874828 (Some("Mutex"), "new", 1) => {
47884829 self.uses_sync.set(true);
47894830 let inner = args[0].ty.clone();
@@ -5417,6 +5458,72 @@ impl Lowerer {
54175458 Some(Nim::Ptr(Box::new(elem))),
54185459 )
54195460 }
5461+ // Atomic operations. Each is the same C11 primitive on both
5462+ // sides, with the ordering carried through unchanged.
5463+ "load" | "store" | "swap" | "fetch_add" | "fetch_sub" | "fetch_and"
5464+ | "fetch_or" | "fetch_xor" | "compare_exchange" | "compare_exchange_weak"
5465+ if matches!(&rt, Some(Nim::Named(n, _)) if n == "Atomic") =>
5466+ {
5467+ let Some(Nim::Named(_, ta)) = &rt else { unreachable!() };
5468+ let inner = ta
5469+ .first()
5470+ .cloned()
5471+ .ok_or("an atomic with no known value type")?;
5472+ // The atomic's element type decides the operand's width, the
5473+ // same way the constructor's does.
5474+ let mut a: Vec<String> = Vec::new();
5475+ for (i, raw) in m.args.iter().enumerate() {
5476+ let want = if i == 0 && name != "load" { Some(inner.clone()) } else { None };
5477+ a.push(self.expr_at(raw, want.as_ref())?.code);
5478+ }
5479+ let nim = match name.as_str() {
5480+ "load" => return Ok(Val::new(
5481+ format!("{}.load({})", recv.code, a.first().cloned().unwrap_or_default()),
5482+ Some(inner),
5483+ )),
5484+ "store" => return Ok(Val::new(
5485+ format!("{}.store({}, {})", recv.code, a[0], a[1]),
5486+ Some(Nim::Unit),
5487+ )),
5488+ "swap" => "exchange",
5489+ "fetch_add" => "fetchAdd",
5490+ "fetch_sub" => "fetchSub",
5491+ "fetch_and" => "fetchAnd",
5492+ "fetch_or" => "fetchOr",
5493+ "fetch_xor" => "fetchXor",
5494+ _ => {
5495+ // Both the current and the new value are of the
5496+ // atomic's type.
5497+ let mut a = a.clone();
5498+ if m.args.len() == 4 {
5499+ a[1] = self.expr_at(&m.args[1], Some(&inner))?.code;
5500+ }
5501+ // `compare_exchange` returns `Ok(previous)` or
5502+ // `Err(actual)`; Nim returns a bool and writes the
5503+ // actual value back, so the prelude bridges them.
5504+ if a.len() != 4 {
5505+ return Err(
5506+ "`compare_exchange` takes current, new, and two orderings"
5507+ .into(),
5508+ );
5509+ }
5510+ return Ok(Val::new(
5511+ format!(
5512+ "rsCompareExchange({}, {}, {}, {}, {})",
5513+ recv.code, a[0], a[1], a[2], a[3]
5514+ ),
5515+ Some(Nim::Named(
5516+ "Result".into(),
5517+ vec![inner.clone(), inner],
5518+ )),
5519+ ));
5520+ }
5521+ };
5522+ (
5523+ format!("{}.{}({}, {})", recv.code, nim, a[0], a[1]),
5524+ Some(inner),
5525+ )
5526+ }
54205527 // `m.lock()` acquires, and the guard releases on Drop -- at scope
54215528 // exit and on an early return, which is exactly Nim's `defer`.
54225529 // The guard is an *alias* to the protected value, not a copy, so
@@ -6005,7 +6112,7 @@ fn module_of(prefix: &[String]) -> String {
60056112 fn mentions_sync(t: &Nim) -> bool {
60066113 match t {
60076114 Nim::Named(n, a) => {
6008- n == "RsArc" || n == "RsMutex" || a.iter().any(mentions_sync)
6115+ n == "RsArc" || n == "RsMutex" || n == "Atomic" || a.iter().any(mentions_sync)
60096116 }
60106117 Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) | Nim::Iter(e)
60116118 | Nim::Future(e) | Nim::Ptr(e) | Nim::ConstPtr(e) => mentions_sync(e),
@@ -412,8 +412,6 @@ impl Lowerer {
412 // ---------------------------------------------------------------- file412 // ---------------------------------------------------------------- file
413 413
414 pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {414 pub fn lower_file(&mut self, files: &[(String, syn::File)]) -> Result<String, String> {
415- self.out.push_str(include_str!("prelude.nim"));
416- self.blank();
417 415
418 // Pass 0: type aliases. A signature in one file may use an alias416 // Pass 0: type aliases. A signature in one file may use an alias
419 // declared in another, and inputs are given in whatever order suits417 // declared in another, and inputs are given in whatever order suits
@@ -433,17 +431,6 @@ impl Lowerer {
433 self.collect(item)?;431 self.collect(item)?;
434 }432 }
435 }433 }
436- // The async runtime is only pulled in when something needs it: it
437- // costs compile time, and most programs have no futures at all.
438- if self.uses_async {
439- self.out.push_str(include_str!("prelude_async.nim"));
440- self.blank();
441- }
442- if self.uses_sync.get() {
443- self.out.push_str(include_str!("prelude_sync.nim"));
444- self.blank();
445- }
446-
447 // Pass 2: type definitions, which every signature may mention.434 // Pass 2: type definitions, which every signature may mention.
448 for (m, f) in files {435 for (m, f) in files {
449 self.cur_mod = m.clone();436 self.cur_mod = m.clone();
@@ -506,7 +493,24 @@ impl Lowerer {
506 self.line(" quit(101)");493 self.line(" quit(101)");
507 self.indent -= 1;494 self.indent -= 1;
508 }495 }
509- Ok(std::mem::take(&mut self.out))496+
497+ // The prelude is composed *after* the body, so that whether a runtime
498+ // is needed can be discovered while lowering rather than predicted
499+ // before it. The async dispatcher and the locking runtime each cost
500+ // compile time, and most programs need neither.
501+ let body = std::mem::take(&mut self.out);
502+ let mut out = String::from(include_str!("prelude.nim"));
503+ out.push('\n');
504+ if self.uses_async {
505+ out.push_str(include_str!("prelude_async.nim"));
506+ out.push('\n');
507+ }
508+ if self.uses_sync.get() {
509+ out.push_str(include_str!("prelude_sync.nim"));
510+ out.push('\n');
511+ }
512+ out.push_str(&body);
513+ Ok(out)
510 }514 }
511 515
512 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {516 fn collect_aliases(&mut self, item: &Item) -> Result<(), String> {
@@ -2557,7 +2561,12 @@ impl Lowerer {
2557 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:2561 // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow:
2558 // Rust may write through it, and Nim only accepts a `var` where a2562 // Rust may write through it, and Nim only accepts a `var` where a
2559 // `var` parameter is wanted, so the binding has to be one.2563 // `var` parameter is wanted, so the binding has to be one.
2560- let mutable = mutable || is_mut_borrow(&init.expr);2564+ // Rust's atomics mutate through `&self` -- interior mutability -- so
2565+ // an immutable `let` still writes. Nim needs `var` for that, and
2566+ // emitting it is what makes the binding behave as Rust's does.
2567+ let mutable = mutable
2568+ || is_mut_borrow(&init.expr)
2569+ || matches!(&t, Nim::Named(n, _) if n == "Atomic");
2561 let kw = if mutable { "var" } else { "let" };2570 let kw = if mutable { "var" } else { "let" };
2562 // Inside a generic proc the binding's type may mention a parameter Nim2571 // Inside a generic proc the binding's type may mention a parameter Nim
2563 // will infer; naming it in an annotation would not resolve.2572 // will infer; naming it in an annotation would not resolve.
@@ -3645,6 +3654,25 @@ impl Lowerer {
3645 if name == "None" {3654 if name == "None" {
3646 return Ok(Val::new(self.none_of(expect), expect.cloned()));3655 return Ok(Val::new(self.none_of(expect), expect.cloned()));
3647 }3656 }
3657+ // `Ordering::SeqCst` and friends. Nim's names differ but the
3658+ // set is the same, and both mean the C11 ordering.
3659+ if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
3660+ if q == "Ordering" && !self.enums.contains_key(&q) {
3661+ let mo = match name.as_str() {
3662+ "Relaxed" => Some("moRelaxed"),
3663+ "Acquire" => Some("moAcquire"),
3664+ "Release" => Some("moRelease"),
3665+ "AcqRel" => Some("moAcquireRelease"),
3666+ "SeqCst" => Some("moSequentiallyConsistent"),
3667+ _ => None,
3668+ };
3669+ if let Some(mo) = mo {
3670+ self.uses_sync.set(true);
3671+ return Ok(Val::new(mo, Some(Nim::Prim("MemoryOrder".into()))));
3672+ }
3673+ }
3674+ }
3675+
3648 // `log::Level` and `log::LevelFilter` come from the facade3676 // `log::Level` and `log::LevelFilter` come from the facade
3649 // shim, under names no crate can collide with.3677 // shim, under names no crate can collide with.
3650 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {3678 if let Some(q) = p.path.segments.iter().rev().nth(1).map(|s| s.ident.to_string()) {
@@ -4784,6 +4812,19 @@ impl Lowerer {
4784 inner.map(|t| Nim::Named("RsArc".into(), vec![t])),4812 inner.map(|t| Nim::Named("RsArc".into(), vec![t])),
4785 ));4813 ));
4786 }4814 }
4815+ (Some(q), "new", 1) if q.starts_with("Atomic") => {
4816+ self.uses_sync.set(true);
4817+ let t = ty::map(&syn::parse_str::<syn::Type>(q).map_err(|e| e.to_string())?)?;
4818+ // The atomic's own element type decides the literal's width;
4819+ // `AtomicUsize::new(5)` holds a `uint`, not the `i32` an
4820+ // unconstrained literal would default to.
4821+ let inner = match &t {
4822+ Nim::Named(_, a) => a.first().cloned(),
4823+ _ => None,
4824+ };
4825+ let v = self.expr_at(&c.args[0], inner.as_ref())?;
4826+ return Ok(Val::new(format!("rsAtomicNew({})", v.code), Some(t)));
4827+ }
4787 (Some("Mutex"), "new", 1) => {4828 (Some("Mutex"), "new", 1) => {
4788 self.uses_sync.set(true);4829 self.uses_sync.set(true);
4789 let inner = args[0].ty.clone();4830 let inner = args[0].ty.clone();
@@ -5417,6 +5458,72 @@ impl Lowerer {
5417 Some(Nim::Ptr(Box::new(elem))),5458 Some(Nim::Ptr(Box::new(elem))),
5418 )5459 )
5419 }5460 }
5461+ // Atomic operations. Each is the same C11 primitive on both
5462+ // sides, with the ordering carried through unchanged.
5463+ "load" | "store" | "swap" | "fetch_add" | "fetch_sub" | "fetch_and"
5464+ | "fetch_or" | "fetch_xor" | "compare_exchange" | "compare_exchange_weak"
5465+ if matches!(&rt, Some(Nim::Named(n, _)) if n == "Atomic") =>
5466+ {
5467+ let Some(Nim::Named(_, ta)) = &rt else { unreachable!() };
5468+ let inner = ta
5469+ .first()
5470+ .cloned()
5471+ .ok_or("an atomic with no known value type")?;
5472+ // The atomic's element type decides the operand's width, the
5473+ // same way the constructor's does.
5474+ let mut a: Vec<String> = Vec::new();
5475+ for (i, raw) in m.args.iter().enumerate() {
5476+ let want = if i == 0 && name != "load" { Some(inner.clone()) } else { None };
5477+ a.push(self.expr_at(raw, want.as_ref())?.code);
5478+ }
5479+ let nim = match name.as_str() {
5480+ "load" => return Ok(Val::new(
5481+ format!("{}.load({})", recv.code, a.first().cloned().unwrap_or_default()),
5482+ Some(inner),
5483+ )),
5484+ "store" => return Ok(Val::new(
5485+ format!("{}.store({}, {})", recv.code, a[0], a[1]),
5486+ Some(Nim::Unit),
5487+ )),
5488+ "swap" => "exchange",
5489+ "fetch_add" => "fetchAdd",
5490+ "fetch_sub" => "fetchSub",
5491+ "fetch_and" => "fetchAnd",
5492+ "fetch_or" => "fetchOr",
5493+ "fetch_xor" => "fetchXor",
5494+ _ => {
5495+ // Both the current and the new value are of the
5496+ // atomic's type.
5497+ let mut a = a.clone();
5498+ if m.args.len() == 4 {
5499+ a[1] = self.expr_at(&m.args[1], Some(&inner))?.code;
5500+ }
5501+ // `compare_exchange` returns `Ok(previous)` or
5502+ // `Err(actual)`; Nim returns a bool and writes the
5503+ // actual value back, so the prelude bridges them.
5504+ if a.len() != 4 {
5505+ return Err(
5506+ "`compare_exchange` takes current, new, and two orderings"
5507+ .into(),
5508+ );
5509+ }
5510+ return Ok(Val::new(
5511+ format!(
5512+ "rsCompareExchange({}, {}, {}, {}, {})",
5513+ recv.code, a[0], a[1], a[2], a[3]
5514+ ),
5515+ Some(Nim::Named(
5516+ "Result".into(),
5517+ vec![inner.clone(), inner],
5518+ )),
5519+ ));
5520+ }
5521+ };
5522+ (
5523+ format!("{}.{}({}, {})", recv.code, nim, a[0], a[1]),
5524+ Some(inner),
5525+ )
5526+ }
5420 // `m.lock()` acquires, and the guard releases on Drop -- at scope5527 // `m.lock()` acquires, and the guard releases on Drop -- at scope
5421 // exit and on an early return, which is exactly Nim's `defer`.5528 // exit and on an early return, which is exactly Nim's `defer`.
5422 // The guard is an *alias* to the protected value, not a copy, so5529 // The guard is an *alias* to the protected value, not a copy, so
@@ -6005,7 +6112,7 @@ fn module_of(prefix: &[String]) -> String {
6005 fn mentions_sync(t: &Nim) -> bool {6112 fn mentions_sync(t: &Nim) -> bool {
6006 match t {6113 match t {
6007 Nim::Named(n, a) => {6114 Nim::Named(n, a) => {
6008- n == "RsArc" || n == "RsMutex" || a.iter().any(mentions_sync)6115+ n == "RsArc" || n == "RsMutex" || n == "Atomic" || a.iter().any(mentions_sync)
6009 }6116 }
6010 Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) | Nim::Iter(e)6117 Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) | Nim::Iter(e)
6011 | Nim::Future(e) | Nim::Ptr(e) | Nim::ConstPtr(e) => mentions_sync(e),6118 | Nim::Future(e) | Nim::Ptr(e) | Nim::ConstPtr(e) => mentions_sync(e),
modified src/prelude_sync.nim +22 -1
@@ -1,4 +1,4 @@
1-import std/locks
1+import std/[locks, atomics]
22
33 # ---------------------------------------------------------------------------
44 # `Arc`/`Rc` and `Mutex`.
@@ -24,6 +24,27 @@ import std/locks
2424 # to poison it.
2525 # ---------------------------------------------------------------------------
2626
27+# Atomics are *not* an approximation: both languages sit on C11 atomics, and
28+# Nim's `MemoryOrder` is a total match for Rust's `Ordering` --
29+# Relaxed/Acquire/Release/AcqRel/SeqCst against
30+# moRelaxed/moAcquire/moRelease/moAcquireRelease/moSequentiallyConsistent.
31+# The operations correspond one for one too.
32+#
33+# Rust's atomics mutate through `&self` -- interior mutability -- so a `let`
34+# binding is still writable. Nim needs `var` for that, which is what the
35+# lowering emits for an atomic regardless of how Rust spelled the binding.
36+
37+proc rsAtomicNew*[T](v: T): Atomic[T] =
38+ result.store(v, moRelaxed)
39+
40+proc rsCompareExchange*[T](a: var Atomic[T], expected, desired: T,
41+ succ, fail: MemoryOrder): Result[T, T] =
42+ ## Rust returns `Ok(previous)` on success and `Err(actual)` on failure; Nim
43+ ## returns a bool and writes the actual value back through `expected`.
44+ var e = expected
45+ if a.compareExchange(e, desired, succ, fail): rsOk[T, T](e)
46+ else: rsErr[T, T](e)
47+
2748 type
2849 RsArc*[T] = ref object
2950 v*: T
@@ -1,4 +1,4 @@
1-import std/locks1+import std/[locks, atomics]
2 2
3 # ---------------------------------------------------------------------------3 # ---------------------------------------------------------------------------
4 # `Arc`/`Rc` and `Mutex`.4 # `Arc`/`Rc` and `Mutex`.
@@ -24,6 +24,27 @@ import std/locks
24 # to poison it.24 # to poison it.
25 # ---------------------------------------------------------------------------25 # ---------------------------------------------------------------------------
26 26
27+# Atomics are *not* an approximation: both languages sit on C11 atomics, and
28+# Nim's `MemoryOrder` is a total match for Rust's `Ordering` --
29+# Relaxed/Acquire/Release/AcqRel/SeqCst against
30+# moRelaxed/moAcquire/moRelease/moAcquireRelease/moSequentiallyConsistent.
31+# The operations correspond one for one too.
32+#
33+# Rust's atomics mutate through `&self` -- interior mutability -- so a `let`
34+# binding is still writable. Nim needs `var` for that, which is what the
35+# lowering emits for an atomic regardless of how Rust spelled the binding.
36+
37+proc rsAtomicNew*[T](v: T): Atomic[T] =
38+ result.store(v, moRelaxed)
39+
40+proc rsCompareExchange*[T](a: var Atomic[T], expected, desired: T,
41+ succ, fail: MemoryOrder): Result[T, T] =
42+ ## Rust returns `Ok(previous)` on success and `Err(actual)` on failure; Nim
43+ ## returns a bool and writes the actual value back through `expected`.
44+ var e = expected
45+ if a.compareExchange(e, desired, succ, fail): rsOk[T, T](e)
46+ else: rsErr[T, T](e)
47+
27 type48 type
28 RsArc*[T] = ref object49 RsArc*[T] = ref object
29 v*: T50 v*: T
modified src/ty.rs +18 -0
@@ -137,6 +137,11 @@ pub fn c_type(name: &str) -> Option<&'static str> {
137137 })
138138 }
139139
140+/// `Atomic[T]` for a primitive `T`.
141+fn atomic_of(t: &str) -> Nim {
142+ Nim::Named("Atomic".into(), vec![Nim::Prim(t.into())])
143+}
144+
140145 pub fn prim(name: &str) -> Option<Nim> {
141146 let mapped = match name {
142147 "i8" => "int8",
@@ -271,6 +276,19 @@ pub fn map(t: &Type) -> Result<Nim, String> {
271276 // The `log` facade's types, under shim names.
272277 ("Level", 0) => Ok(Nim::Prim("RsLogLevel".into())),
273278 ("LevelFilter", 0) => Ok(Nim::Prim("RsLogFilter".into())),
279+ // Atomics. Both languages compile these to the same C11
280+ // primitives, so the correspondence is exact.
281+ ("AtomicBool", 0) => Ok(atomic_of("bool")),
282+ ("AtomicUsize", 0) => Ok(atomic_of("uint")),
283+ ("AtomicIsize", 0) => Ok(atomic_of("int")),
284+ ("AtomicU8", 0) => Ok(atomic_of("uint8")),
285+ ("AtomicU16", 0) => Ok(atomic_of("uint16")),
286+ ("AtomicU32", 0) => Ok(atomic_of("uint32")),
287+ ("AtomicU64", 0) => Ok(atomic_of("uint64")),
288+ ("AtomicI8", 0) => Ok(atomic_of("int8")),
289+ ("AtomicI16", 0) => Ok(atomic_of("int16")),
290+ ("AtomicI32", 0) => Ok(atomic_of("int32")),
291+ ("AtomicI64", 0) => Ok(atomic_of("int64")),
274292 // Shared ownership and locking: see `src/prelude_sync.nim`.
275293 ("Arc", 1) | ("Rc", 1) => Ok(Nim::Named("RsArc".into(), args)),
276294 ("Mutex", 1) => Ok(Nim::Named("RsMutex".into(), args)),
@@ -137,6 +137,11 @@ pub fn c_type(name: &str) -> Option<&'static str> {
137 })137 })
138 }138 }
139 139
140+/// `Atomic[T]` for a primitive `T`.
141+fn atomic_of(t: &str) -> Nim {
142+ Nim::Named("Atomic".into(), vec![Nim::Prim(t.into())])
143+}
144+
140 pub fn prim(name: &str) -> Option<Nim> {145 pub fn prim(name: &str) -> Option<Nim> {
141 let mapped = match name {146 let mapped = match name {
142 "i8" => "int8",147 "i8" => "int8",
@@ -271,6 +276,19 @@ pub fn map(t: &Type) -> Result<Nim, String> {
271 // The `log` facade's types, under shim names.276 // The `log` facade's types, under shim names.
272 ("Level", 0) => Ok(Nim::Prim("RsLogLevel".into())),277 ("Level", 0) => Ok(Nim::Prim("RsLogLevel".into())),
273 ("LevelFilter", 0) => Ok(Nim::Prim("RsLogFilter".into())),278 ("LevelFilter", 0) => Ok(Nim::Prim("RsLogFilter".into())),
279+ // Atomics. Both languages compile these to the same C11
280+ // primitives, so the correspondence is exact.
281+ ("AtomicBool", 0) => Ok(atomic_of("bool")),
282+ ("AtomicUsize", 0) => Ok(atomic_of("uint")),
283+ ("AtomicIsize", 0) => Ok(atomic_of("int")),
284+ ("AtomicU8", 0) => Ok(atomic_of("uint8")),
285+ ("AtomicU16", 0) => Ok(atomic_of("uint16")),
286+ ("AtomicU32", 0) => Ok(atomic_of("uint32")),
287+ ("AtomicU64", 0) => Ok(atomic_of("uint64")),
288+ ("AtomicI8", 0) => Ok(atomic_of("int8")),
289+ ("AtomicI16", 0) => Ok(atomic_of("int16")),
290+ ("AtomicI32", 0) => Ok(atomic_of("int32")),
291+ ("AtomicI64", 0) => Ok(atomic_of("int64")),
274 // Shared ownership and locking: see `src/prelude_sync.nim`.292 // Shared ownership and locking: see `src/prelude_sync.nim`.
275 ("Arc", 1) | ("Rc", 1) => Ok(Nim::Named("RsArc".into(), args)),293 ("Arc", 1) | ("Rc", 1) => Ok(Nim::Named("RsArc".into(), args)),
276 ("Mutex", 1) => Ok(Nim::Named("RsMutex".into(), args)),294 ("Mutex", 1) => Ok(Nim::Named("RsMutex".into(), args)),
added tests/cases/045-atomics.rs +49 -0
new file mode 100644
@@ -0,0 +1,49 @@
1+// Atomics are *not* an approximation. Both languages compile these to the same
2+// C11 primitives, and Nim's `MemoryOrder` is a total match for Rust's
3+// `Ordering`: Relaxed/Acquire/Release/AcqRel/SeqCst against
4+// moRelaxed/moAcquire/moRelease/moAcquireRelease/moSequentiallyConsistent.
5+//
6+// Rust's atomics mutate through `&self` -- interior mutability -- so `let a`
7+// is still writable. Nim needs `var` for that, which the lowering emits
8+// regardless of how Rust spelled the binding; the first case below is what
9+// checks it.
10+//
11+// Single-threaded, because `std::thread` is not modelled. What is verified is
12+// the operations and their return values, which are defined without threads.
13+
14+use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicUsize, Ordering};
15+
16+fn main() {
17+ // An immutable binding that is nonetheless written through.
18+ let a = AtomicUsize::new(5);
19+ println!("{}", a.load(Ordering::SeqCst));
20+ println!("{}", a.fetch_add(3, Ordering::Relaxed));
21+ println!("{}", a.load(Ordering::Relaxed));
22+ println!("{}", a.fetch_sub(2, Ordering::AcqRel));
23+ println!("{}", a.load(Ordering::Acquire));
24+ a.store(100, Ordering::Release);
25+ println!("{}", a.swap(7, Ordering::SeqCst));
26+ println!("{}", a.load(Ordering::SeqCst));
27+
28+ let b = AtomicBool::new(false);
29+ println!("{}", b.load(Ordering::Acquire));
30+ b.store(true, Ordering::Release);
31+ println!("{} {}", b.load(Ordering::Acquire), b.swap(false, Ordering::SeqCst));
32+
33+ let m = AtomicU32::new(0b1010);
34+ println!("{}", m.fetch_and(0b0110, Ordering::Relaxed));
35+ println!("{}", m.fetch_or(0b0001, Ordering::Relaxed));
36+ println!("{}", m.fetch_xor(0b1111, Ordering::Relaxed));
37+ println!("{}", m.load(Ordering::Relaxed));
38+
39+ // `compare_exchange` is `Ok(previous)` or `Err(actual)`.
40+ let c = AtomicI32::new(10);
41+ println!("{:?}", c.compare_exchange(10, 20, Ordering::SeqCst, Ordering::SeqCst));
42+ println!("{:?}", c.compare_exchange(99, 30, Ordering::SeqCst, Ordering::SeqCst));
43+ println!("{}", c.load(Ordering::SeqCst));
44+
45+ // Negative values, to check signedness survives.
46+ let d = AtomicI32::new(-5);
47+ println!("{}", d.fetch_sub(10, Ordering::Relaxed));
48+ println!("{}", d.load(Ordering::Relaxed));
49+}
new file mode 100644
@@ -0,0 +1,49 @@
1+// Atomics are *not* an approximation. Both languages compile these to the same
2+// C11 primitives, and Nim's `MemoryOrder` is a total match for Rust's
3+// `Ordering`: Relaxed/Acquire/Release/AcqRel/SeqCst against
4+// moRelaxed/moAcquire/moRelease/moAcquireRelease/moSequentiallyConsistent.
5+//
6+// Rust's atomics mutate through `&self` -- interior mutability -- so `let a`
7+// is still writable. Nim needs `var` for that, which the lowering emits
8+// regardless of how Rust spelled the binding; the first case below is what
9+// checks it.
10+//
11+// Single-threaded, because `std::thread` is not modelled. What is verified is
12+// the operations and their return values, which are defined without threads.
13+
14+use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, AtomicUsize, Ordering};
15+
16+fn main() {
17+ // An immutable binding that is nonetheless written through.
18+ let a = AtomicUsize::new(5);
19+ println!("{}", a.load(Ordering::SeqCst));
20+ println!("{}", a.fetch_add(3, Ordering::Relaxed));
21+ println!("{}", a.load(Ordering::Relaxed));
22+ println!("{}", a.fetch_sub(2, Ordering::AcqRel));
23+ println!("{}", a.load(Ordering::Acquire));
24+ a.store(100, Ordering::Release);
25+ println!("{}", a.swap(7, Ordering::SeqCst));
26+ println!("{}", a.load(Ordering::SeqCst));
27+
28+ let b = AtomicBool::new(false);
29+ println!("{}", b.load(Ordering::Acquire));
30+ b.store(true, Ordering::Release);
31+ println!("{} {}", b.load(Ordering::Acquire), b.swap(false, Ordering::SeqCst));
32+
33+ let m = AtomicU32::new(0b1010);
34+ println!("{}", m.fetch_and(0b0110, Ordering::Relaxed));
35+ println!("{}", m.fetch_or(0b0001, Ordering::Relaxed));
36+ println!("{}", m.fetch_xor(0b1111, Ordering::Relaxed));
37+ println!("{}", m.load(Ordering::Relaxed));
38+
39+ // `compare_exchange` is `Ok(previous)` or `Err(actual)`.
40+ let c = AtomicI32::new(10);
41+ println!("{:?}", c.compare_exchange(10, 20, Ordering::SeqCst, Ordering::SeqCst));
42+ println!("{:?}", c.compare_exchange(99, 30, Ordering::SeqCst, Ordering::SeqCst));
43+ println!("{}", c.load(Ordering::SeqCst));
44+
45+ // Negative values, to check signedness survives.
46+ let d = AtomicI32::new(-5);
47+ println!("{}", d.fetch_sub(10, Ordering::Relaxed));
48+ println!("{}", d.load(Ordering::Relaxed));
49+}