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>
a721ac2 parent: 8587dec modified
DESIGN.md +33 -3 | @@ -878,9 +878,39 @@ Two differences are real and are recorded rather than papered over: | ||
| 878 | 878 | to `quit(101)` rather than unwinding, so there is no holder left to poison |
| 879 | 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, | |
| 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`. | |
| 884 | 914 | |
| 885 | 915 | ### The harness had a hole |
| 886 | 916 | |
| @@ -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 poison | 878 | 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 no | 882 | + |
| 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 hole | 915 | ### The harness had a hole |
| 886 | 916 | ||
modified
src/lower.rs +123 -16 | @@ -412,8 +412,6 @@ impl Lowerer { | ||
| 412 | 412 | // ---------------------------------------------------------------- file |
| 413 | 413 | |
| 414 | 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 | 416 | // Pass 0: type aliases. A signature in one file may use an alias |
| 419 | 417 | // declared in another, and inputs are given in whatever order suits |
| @@ -433,17 +431,6 @@ impl Lowerer { | ||
| 433 | 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 | 434 | // Pass 2: type definitions, which every signature may mention. |
| 448 | 435 | for (m, f) in files { |
| 449 | 436 | self.cur_mod = m.clone(); |
| @@ -506,7 +493,24 @@ impl Lowerer { | ||
| 506 | 493 | self.line(" quit(101)"); |
| 507 | 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 | 516 | fn collect_aliases(&mut self, item: &Item) -> Result<(), String> { |
| @@ -2557,7 +2561,12 @@ impl Lowerer { | ||
| 2557 | 2561 | // `let x = &mut y[..n]` is an immutable binding of a *mutable* borrow: |
| 2558 | 2562 | // Rust may write through it, and Nim only accepts a `var` where a |
| 2559 | 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 | 2570 | let kw = if mutable { "var" } else { "let" }; |
| 2562 | 2571 | // Inside a generic proc the binding's type may mention a parameter Nim |
| 2563 | 2572 | // will infer; naming it in an annotation would not resolve. |
| @@ -3645,6 +3654,25 @@ impl Lowerer { | ||
| 3645 | 3654 | if name == "None" { |
| 3646 | 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 | 3676 | // `log::Level` and `log::LevelFilter` come from the facade |
| 3649 | 3677 | // shim, under names no crate can collide with. |
| 3650 | 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 | 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 | 4828 | (Some("Mutex"), "new", 1) => { |
| 4788 | 4829 | self.uses_sync.set(true); |
| 4789 | 4830 | let inner = args[0].ty.clone(); |
| @@ -5417,6 +5458,72 @@ impl Lowerer { | ||
| 5417 | 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 | 5527 | // `m.lock()` acquires, and the guard releases on Drop -- at scope |
| 5421 | 5528 | // exit and on an early return, which is exactly Nim's `defer`. |
| 5422 | 5529 | // The guard is an *alias* to the protected value, not a copy, so |
| @@ -6005,7 +6112,7 @@ fn module_of(prefix: &[String]) -> String { | ||
| 6005 | 6112 | fn mentions_sync(t: &Nim) -> bool { |
| 6006 | 6113 | match t { |
| 6007 | 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 | 6117 | Nim::Seq(e) | Nim::OpenArray(e) | Nim::Var(e) | Nim::Array(_, e) | Nim::Iter(e) |
| 6011 | 6118 | | Nim::Future(e) | Nim::Ptr(e) | Nim::ConstPtr(e) => mentions_sync(e), |
| @@ -412,8 +412,6 @@ impl Lowerer { | |||
| 412 | // ---------------------------------------------------------------- file | 412 | // ---------------------------------------------------------------- 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 alias | 416 | // Pass 0: type aliases. A signature in one file may use an alias |
| 419 | // declared in another, and inputs are given in whatever order suits | 417 | // 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 a | 2562 | // 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 Nim | 2571 | // 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 facade | 3676 | // `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 scope | 5527 | // `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, so | 5529 | // 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] | |
| 2 | 2 | |
| 3 | 3 | # --------------------------------------------------------------------------- |
| 4 | 4 | # `Arc`/`Rc` and `Mutex`. |
| @@ -24,6 +24,27 @@ import std/locks | ||
| 24 | 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 | 48 | type |
| 28 | 49 | RsArc*[T] = ref object |
| 29 | 50 | v*: T |
| @@ -1,4 +1,4 @@ | |||
| 1 | -import std/locks | 1 | +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 | type | 48 | type |
| 28 | RsArc*[T] = ref object | 49 | RsArc*[T] = ref object |
| 29 | v*: T | 50 | v*: T |
modified
src/ty.rs +18 -0 | @@ -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 | 145 | pub fn prim(name: &str) -> Option<Nim> { |
| 141 | 146 | let mapped = match name { |
| 142 | 147 | "i8" => "int8", |
| @@ -271,6 +276,19 @@ pub fn map(t: &Type) -> Result<Nim, String> { | ||
| 271 | 276 | // The `log` facade's types, under shim names. |
| 272 | 277 | ("Level", 0) => Ok(Nim::Prim("RsLogLevel".into())), |
| 273 | 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 | 292 | // Shared ownership and locking: see `src/prelude_sync.nim`. |
| 275 | 293 | ("Arc", 1) | ("Rc", 1) => Ok(Nim::Named("RsArc".into(), args)), |
| 276 | 294 | ("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 | +} | ||