Lower `static` items, `Self(..)`, and atomics through a shared reference
`colorchoice` compiles: Rust through rustnim to Nim that `nim c` accepts. It is the second of libcosmic's dependencies to go the whole way. A `static` is a module-level binding. Rust const-evaluates its initialiser and Nim runs it at startup, both before any code that can observe the value, so the two agree on every program. `static mut` stays refused: that is unsynchronised shared mutation, and Rust demands `unsafe` for it precisely because it is not safe to reproduce casually. Statics get their own pass, after the forward declarations. They have to come before the procs that read them, and their initialisers routinely call a proc, so neither end of the file will do. `Self(x)` inside an `impl` is the tuple struct's constructor under the name the impl gives it. Rust's atomic `load`/`store` take `&self` -- operating through a shared reference is the whole point of an atomic -- while Nim's want a mutable location, so these go through the address. They are templates rather than procs, and that distinction is the commit: as procs they compiled, ran, and silently dropped every store, because a proc parameter is a copy and `unsafeAddr` inside one takes the address of the copy. `tests/cases/045` caught it -- 6 where rustc says 100. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8038a82 parent: e28de7f modified
src/lower.rs +118 -4 | @@ -704,6 +704,18 @@ impl Lowerer { | ||
| 704 | 704 | self.blank(); |
| 705 | 705 | } |
| 706 | 706 | |
| 707 | + // Pass 3.5: `static` items. Module-level bindings, so Nim wants them | |
| 708 | + // declared before the procs that read them -- and their initialisers | |
| 709 | + // routinely call a proc, so they come after the forward declarations | |
| 710 | + // that make those callable. | |
| 711 | + for (m, f) in files { | |
| 712 | + self.cur_mod = m.clone(); | |
| 713 | + for item in &f.items { | |
| 714 | + let r = self.emit_statics(item); | |
| 715 | + self.note(r)?; | |
| 716 | + } | |
| 717 | + } | |
| 718 | + | |
| 707 | 719 | self.body_start = self.out.len(); |
| 708 | 720 | |
| 709 | 721 | // Pass 4: bodies. |
| @@ -1007,6 +1019,13 @@ impl Lowerer { | ||
| 1007 | 1019 | } |
| 1008 | 1020 | self.impl_generics = outer; |
| 1009 | 1021 | } |
| 1022 | + Item::Static(st) => { | |
| 1023 | + // The name and type, so a body reached before the emission | |
| 1024 | + // pass can still be typed against it. | |
| 1025 | + if let Ok(t) = self.map_ty(&st.ty) { | |
| 1026 | + self.bind(&st.ident.to_string(), t.owned()); | |
| 1027 | + } | |
| 1028 | + } | |
| 1010 | 1029 | Item::Fn(f) => { |
| 1011 | 1030 | // A body may declare its own types, and a signature below may |
| 1012 | 1031 | // name one, so these come first. |
| @@ -2617,6 +2636,33 @@ impl Lowerer { | ||
| 2617 | 2636 | // --------------------------------------------------------------- items |
| 2618 | 2637 | |
| 2619 | 2638 | /// Emit the type definitions only: they must precede every signature. |
| 2639 | + /// Write out the `static` items, after the types and before the procs. | |
| 2640 | + fn emit_statics(&mut self, item: &Item) -> Result<(), String> { | |
| 2641 | + if !self.cfg_keeps(item_attrs(item))? { | |
| 2642 | + return Ok(()); | |
| 2643 | + } | |
| 2644 | + match item { | |
| 2645 | + Item::Static(_) => self.item_inner(item), | |
| 2646 | + Item::Mod(m) => { | |
| 2647 | + if let Some((_, items)) = &m.content { | |
| 2648 | + for i in items { | |
| 2649 | + self.emit_statics(i)?; | |
| 2650 | + } | |
| 2651 | + } | |
| 2652 | + Ok(()) | |
| 2653 | + } | |
| 2654 | + Item::Const(c) => { | |
| 2655 | + if let Some(items) = derive_item_block(c) { | |
| 2656 | + for i in &items { | |
| 2657 | + self.emit_statics(i)?; | |
| 2658 | + } | |
| 2659 | + } | |
| 2660 | + Ok(()) | |
| 2661 | + } | |
| 2662 | + _ => Ok(()), | |
| 2663 | + } | |
| 2664 | + } | |
| 2665 | + | |
| 2620 | 2666 | /// Write out every associated constant, ahead of the types. |
| 2621 | 2667 | fn emit_assoc_consts(&mut self, item: &Item) -> Result<(), String> { |
| 2622 | 2668 | if !self.cfg_keeps(item_attrs(item))? { |
| @@ -2732,8 +2778,11 @@ impl Lowerer { | ||
| 2732 | 2778 | return Ok(()); |
| 2733 | 2779 | } |
| 2734 | 2780 | } |
| 2735 | - // Types were emitted in their own pass. | |
| 2736 | - if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) { | |
| 2781 | + // Types and statics were emitted in their own passes. | |
| 2782 | + if matches!( | |
| 2783 | + item, | |
| 2784 | + Item::Struct(_) | Item::Enum(_) | Item::Const(_) | Item::Static(_) | |
| 2785 | + ) { | |
| 2737 | 2786 | return Ok(()); |
| 2738 | 2787 | } |
| 2739 | 2788 | if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") |
| @@ -2866,6 +2915,40 @@ impl Lowerer { | ||
| 2866 | 2915 | m.ident |
| 2867 | 2916 | )) |
| 2868 | 2917 | } |
| 2918 | + // A `static` is a module-level binding. Rust const-evaluates its | |
| 2919 | + // initialiser and Nim runs it at startup; both happen before any | |
| 2920 | + // code that can observe the value, so the two agree on every | |
| 2921 | + // program. `static mut` stays refused -- that is unsynchronised | |
| 2922 | + // shared mutation, and Rust requires `unsafe` for it precisely | |
| 2923 | + // because it is not safe to reproduce casually. | |
| 2924 | + Item::Static(st) => { | |
| 2925 | + if matches!(st.mutability, syn::StaticMutability::Mut(_)) { | |
| 2926 | + return Err(format!( | |
| 2927 | + "`static mut {}` is unsynchronised shared mutable state; \ | |
| 2928 | + Rust requires `unsafe` to touch it and rustnim has no \ | |
| 2929 | + faithful equivalent", | |
| 2930 | + st.ident | |
| 2931 | + )); | |
| 2932 | + } | |
| 2933 | + let t = self.map_ty(&st.ty)?.owned(); | |
| 2934 | + let v = self.expr_at(&st.expr, Some(&t))?; | |
| 2935 | + let star = if self.indent == 0 { "*" } else { "" }; | |
| 2936 | + // `let`, not `const`: the initialiser is a call more often | |
| 2937 | + // than not, and Nim's `const` demands compile-time evaluation | |
| 2938 | + // where Rust's `static` accepts anything const-*evaluable*. | |
| 2939 | + self.line(&format!( | |
| 2940 | + "let {}{}: {} = {}", | |
| 2941 | + ident(&st.ident.to_string()), | |
| 2942 | + star, | |
| 2943 | + t.render(), | |
| 2944 | + v.code | |
| 2945 | + )); | |
| 2946 | + self.bind(&st.ident.to_string(), t); | |
| 2947 | + if self.indent == 0 { | |
| 2948 | + self.blank(); | |
| 2949 | + } | |
| 2950 | + Ok(()) | |
| 2951 | + } | |
| 2869 | 2952 | other => Err(format!("unsupported item: {}", item_kind(other))), |
| 2870 | 2953 | } |
| 2871 | 2954 | } |
| @@ -7622,6 +7705,30 @@ impl Lowerer { | ||
| 7622 | 7705 | }; |
| 7623 | 7706 | } |
| 7624 | 7707 | |
| 7708 | + // `Self(x)` inside an `impl` is the tuple struct's constructor, under | |
| 7709 | + // the name the impl gives it. | |
| 7710 | + if name == "Self" { | |
| 7711 | + if let Some(t) = self.self_ty.clone() { | |
| 7712 | + let n = type_name(&t); | |
| 7713 | + if let Some(fields) = self.structs.get(&n).cloned() { | |
| 7714 | + let mut parts = Vec::new(); | |
| 7715 | + for (i, a) in c.args.iter().enumerate() { | |
| 7716 | + let want = fields.get(i).map(|(_, t)| t.clone()); | |
| 7717 | + let v = self.expr_at(a, want.as_ref())?; | |
| 7718 | + parts.push(format!( | |
| 7719 | + "{}: {}", | |
| 7720 | + ident(&fields[i].0), | |
| 7721 | + v.code | |
| 7722 | + )); | |
| 7723 | + } | |
| 7724 | + return Ok(Val::new( | |
| 7725 | + format!("{}({})", ident(&n), parts.join(", ")), | |
| 7726 | + Some(t), | |
| 7727 | + )); | |
| 7728 | + } | |
| 7729 | + } | |
| 7730 | + } | |
| 7731 | + | |
| 7625 | 7732 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. |
| 7626 | 7733 | if let Some((def, v)) = self.resolve_variant(&p.path) { |
| 7627 | 7734 | let (ty, _) = self.variant_type(&def, expect)?; |
| @@ -8340,12 +8447,19 @@ impl Lowerer { | ||
| 8340 | 8447 | a.push(self.expr_at(raw, want.as_ref())?.code); |
| 8341 | 8448 | } |
| 8342 | 8449 | let nim = match name.as_str() { |
| 8450 | + // Through the address: Rust's atomics take `&self`, and | |
| 8451 | + // Nim's `load`/`store` want a mutable location. See | |
| 8452 | + // `rsAtomicLoad` in `src/prelude_sync.nim`. | |
| 8343 | 8453 | "load" => return Ok(Val::new( |
| 8344 | - format!("{}.load({})", recv.code, a.first().cloned().unwrap_or_default()), | |
| 8454 | + format!( | |
| 8455 | + "rsAtomicLoad({}, {})", | |
| 8456 | + recv.code, | |
| 8457 | + a.first().cloned().unwrap_or_default() | |
| 8458 | + ), | |
| 8345 | 8459 | Some(inner), |
| 8346 | 8460 | )), |
| 8347 | 8461 | "store" => return Ok(Val::new( |
| 8348 | - format!("{}.store({}, {})", recv.code, a[0], a[1]), | |
| 8462 | + format!("rsAtomicStore({}, {}, {})", recv.code, a[0], a[1]), | |
| 8349 | 8463 | Some(Nim::Unit), |
| 8350 | 8464 | )), |
| 8351 | 8465 | "swap" => "exchange", |
| @@ -704,6 +704,18 @@ impl Lowerer { | |||
| 704 | self.blank(); | 704 | self.blank(); |
| 705 | } | 705 | } |
| 706 | 706 | ||
| 707 | + // Pass 3.5: `static` items. Module-level bindings, so Nim wants them | ||
| 708 | + // declared before the procs that read them -- and their initialisers | ||
| 709 | + // routinely call a proc, so they come after the forward declarations | ||
| 710 | + // that make those callable. | ||
| 711 | + for (m, f) in files { | ||
| 712 | + self.cur_mod = m.clone(); | ||
| 713 | + for item in &f.items { | ||
| 714 | + let r = self.emit_statics(item); | ||
| 715 | + self.note(r)?; | ||
| 716 | + } | ||
| 717 | + } | ||
| 718 | + | ||
| 707 | self.body_start = self.out.len(); | 719 | self.body_start = self.out.len(); |
| 708 | 720 | ||
| 709 | // Pass 4: bodies. | 721 | // Pass 4: bodies. |
| @@ -1007,6 +1019,13 @@ impl Lowerer { | |||
| 1007 | } | 1019 | } |
| 1008 | self.impl_generics = outer; | 1020 | self.impl_generics = outer; |
| 1009 | } | 1021 | } |
| 1022 | + Item::Static(st) => { | ||
| 1023 | + // The name and type, so a body reached before the emission | ||
| 1024 | + // pass can still be typed against it. | ||
| 1025 | + if let Ok(t) = self.map_ty(&st.ty) { | ||
| 1026 | + self.bind(&st.ident.to_string(), t.owned()); | ||
| 1027 | + } | ||
| 1028 | + } | ||
| 1010 | Item::Fn(f) => { | 1029 | Item::Fn(f) => { |
| 1011 | // A body may declare its own types, and a signature below may | 1030 | // A body may declare its own types, and a signature below may |
| 1012 | // name one, so these come first. | 1031 | // name one, so these come first. |
| @@ -2617,6 +2636,33 @@ impl Lowerer { | |||
| 2617 | // --------------------------------------------------------------- items | 2636 | // --------------------------------------------------------------- items |
| 2618 | 2637 | ||
| 2619 | /// Emit the type definitions only: they must precede every signature. | 2638 | /// Emit the type definitions only: they must precede every signature. |
| 2639 | + /// Write out the `static` items, after the types and before the procs. | ||
| 2640 | + fn emit_statics(&mut self, item: &Item) -> Result<(), String> { | ||
| 2641 | + if !self.cfg_keeps(item_attrs(item))? { | ||
| 2642 | + return Ok(()); | ||
| 2643 | + } | ||
| 2644 | + match item { | ||
| 2645 | + Item::Static(_) => self.item_inner(item), | ||
| 2646 | + Item::Mod(m) => { | ||
| 2647 | + if let Some((_, items)) = &m.content { | ||
| 2648 | + for i in items { | ||
| 2649 | + self.emit_statics(i)?; | ||
| 2650 | + } | ||
| 2651 | + } | ||
| 2652 | + Ok(()) | ||
| 2653 | + } | ||
| 2654 | + Item::Const(c) => { | ||
| 2655 | + if let Some(items) = derive_item_block(c) { | ||
| 2656 | + for i in &items { | ||
| 2657 | + self.emit_statics(i)?; | ||
| 2658 | + } | ||
| 2659 | + } | ||
| 2660 | + Ok(()) | ||
| 2661 | + } | ||
| 2662 | + _ => Ok(()), | ||
| 2663 | + } | ||
| 2664 | + } | ||
| 2665 | + | ||
| 2620 | /// Write out every associated constant, ahead of the types. | 2666 | /// Write out every associated constant, ahead of the types. |
| 2621 | fn emit_assoc_consts(&mut self, item: &Item) -> Result<(), String> { | 2667 | fn emit_assoc_consts(&mut self, item: &Item) -> Result<(), String> { |
| 2622 | if !self.cfg_keeps(item_attrs(item))? { | 2668 | if !self.cfg_keeps(item_attrs(item))? { |
| @@ -2732,8 +2778,11 @@ impl Lowerer { | |||
| 2732 | return Ok(()); | 2778 | return Ok(()); |
| 2733 | } | 2779 | } |
| 2734 | } | 2780 | } |
| 2735 | - // Types were emitted in their own pass. | 2781 | + // Types and statics were emitted in their own passes. |
| 2736 | - if matches!(item, Item::Struct(_) | Item::Enum(_) | Item::Const(_)) { | 2782 | + if matches!( |
| 2783 | + item, | ||
| 2784 | + Item::Struct(_) | Item::Enum(_) | Item::Const(_) | Item::Static(_) | ||
| 2785 | + ) { | ||
| 2737 | return Ok(()); | 2786 | return Ok(()); |
| 2738 | } | 2787 | } |
| 2739 | if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") | 2788 | if matches!(item, Item::Macro(m) if path_name(&m.mac.path) == "bitflags") |
| @@ -2866,6 +2915,40 @@ impl Lowerer { | |||
| 2866 | m.ident | 2915 | m.ident |
| 2867 | )) | 2916 | )) |
| 2868 | } | 2917 | } |
| 2918 | + // A `static` is a module-level binding. Rust const-evaluates its | ||
| 2919 | + // initialiser and Nim runs it at startup; both happen before any | ||
| 2920 | + // code that can observe the value, so the two agree on every | ||
| 2921 | + // program. `static mut` stays refused -- that is unsynchronised | ||
| 2922 | + // shared mutation, and Rust requires `unsafe` for it precisely | ||
| 2923 | + // because it is not safe to reproduce casually. | ||
| 2924 | + Item::Static(st) => { | ||
| 2925 | + if matches!(st.mutability, syn::StaticMutability::Mut(_)) { | ||
| 2926 | + return Err(format!( | ||
| 2927 | + "`static mut {}` is unsynchronised shared mutable state; \ | ||
| 2928 | + Rust requires `unsafe` to touch it and rustnim has no \ | ||
| 2929 | + faithful equivalent", | ||
| 2930 | + st.ident | ||
| 2931 | + )); | ||
| 2932 | + } | ||
| 2933 | + let t = self.map_ty(&st.ty)?.owned(); | ||
| 2934 | + let v = self.expr_at(&st.expr, Some(&t))?; | ||
| 2935 | + let star = if self.indent == 0 { "*" } else { "" }; | ||
| 2936 | + // `let`, not `const`: the initialiser is a call more often | ||
| 2937 | + // than not, and Nim's `const` demands compile-time evaluation | ||
| 2938 | + // where Rust's `static` accepts anything const-*evaluable*. | ||
| 2939 | + self.line(&format!( | ||
| 2940 | + "let {}{}: {} = {}", | ||
| 2941 | + ident(&st.ident.to_string()), | ||
| 2942 | + star, | ||
| 2943 | + t.render(), | ||
| 2944 | + v.code | ||
| 2945 | + )); | ||
| 2946 | + self.bind(&st.ident.to_string(), t); | ||
| 2947 | + if self.indent == 0 { | ||
| 2948 | + self.blank(); | ||
| 2949 | + } | ||
| 2950 | + Ok(()) | ||
| 2951 | + } | ||
| 2869 | other => Err(format!("unsupported item: {}", item_kind(other))), | 2952 | other => Err(format!("unsupported item: {}", item_kind(other))), |
| 2870 | } | 2953 | } |
| 2871 | } | 2954 | } |
| @@ -7622,6 +7705,30 @@ impl Lowerer { | |||
| 7622 | }; | 7705 | }; |
| 7623 | } | 7706 | } |
| 7624 | 7707 | ||
| 7708 | + // `Self(x)` inside an `impl` is the tuple struct's constructor, under | ||
| 7709 | + // the name the impl gives it. | ||
| 7710 | + if name == "Self" { | ||
| 7711 | + if let Some(t) = self.self_ty.clone() { | ||
| 7712 | + let n = type_name(&t); | ||
| 7713 | + if let Some(fields) = self.structs.get(&n).cloned() { | ||
| 7714 | + let mut parts = Vec::new(); | ||
| 7715 | + for (i, a) in c.args.iter().enumerate() { | ||
| 7716 | + let want = fields.get(i).map(|(_, t)| t.clone()); | ||
| 7717 | + let v = self.expr_at(a, want.as_ref())?; | ||
| 7718 | + parts.push(format!( | ||
| 7719 | + "{}: {}", | ||
| 7720 | + ident(&fields[i].0), | ||
| 7721 | + v.code | ||
| 7722 | + )); | ||
| 7723 | + } | ||
| 7724 | + return Ok(Val::new( | ||
| 7725 | + format!("{}({})", ident(&n), parts.join(", ")), | ||
| 7726 | + Some(t), | ||
| 7727 | + )); | ||
| 7728 | + } | ||
| 7729 | + } | ||
| 7730 | + } | ||
| 7731 | + | ||
| 7625 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. | 7732 | // A tuple enum variant applied to arguments: `Shape::Circle(1.0)`. |
| 7626 | if let Some((def, v)) = self.resolve_variant(&p.path) { | 7733 | if let Some((def, v)) = self.resolve_variant(&p.path) { |
| 7627 | let (ty, _) = self.variant_type(&def, expect)?; | 7734 | let (ty, _) = self.variant_type(&def, expect)?; |
| @@ -8340,12 +8447,19 @@ impl Lowerer { | |||
| 8340 | a.push(self.expr_at(raw, want.as_ref())?.code); | 8447 | a.push(self.expr_at(raw, want.as_ref())?.code); |
| 8341 | } | 8448 | } |
| 8342 | let nim = match name.as_str() { | 8449 | let nim = match name.as_str() { |
| 8450 | + // Through the address: Rust's atomics take `&self`, and | ||
| 8451 | + // Nim's `load`/`store` want a mutable location. See | ||
| 8452 | + // `rsAtomicLoad` in `src/prelude_sync.nim`. | ||
| 8343 | "load" => return Ok(Val::new( | 8453 | "load" => return Ok(Val::new( |
| 8344 | - format!("{}.load({})", recv.code, a.first().cloned().unwrap_or_default()), | 8454 | + format!( |
| 8455 | + "rsAtomicLoad({}, {})", | ||
| 8456 | + recv.code, | ||
| 8457 | + a.first().cloned().unwrap_or_default() | ||
| 8458 | + ), | ||
| 8345 | Some(inner), | 8459 | Some(inner), |
| 8346 | )), | 8460 | )), |
| 8347 | "store" => return Ok(Val::new( | 8461 | "store" => return Ok(Val::new( |
| 8348 | - format!("{}.store({}, {})", recv.code, a[0], a[1]), | 8462 | + format!("rsAtomicStore({}, {}, {})", recv.code, a[0], a[1]), |
| 8349 | Some(Nim::Unit), | 8463 | Some(Nim::Unit), |
| 8350 | )), | 8464 | )), |
| 8351 | "swap" => "exchange", | 8465 | "swap" => "exchange", |
modified
src/prelude_sync.nim +21 -0 | @@ -97,3 +97,24 @@ proc rsNewJoinHandle*[T](): RsJoinHandle[T] = | ||
| 97 | 97 | proc rsJoin*[T](h: RsJoinHandle[T]): T = |
| 98 | 98 | joinThread(h.th) |
| 99 | 99 | h.res[] |
| 100 | + | |
| 101 | +## Rust's `#[derive(Debug)]` on an atomic prints the value it holds, read with | |
| 102 | +## a relaxed load. Nim's `load` wants a mutable location and a `Debug` does not | |
| 103 | +## have one, so the address is taken -- the read itself is unchanged. | |
| 104 | +template rsDebug*[T](a: Atomic[T]): string = | |
| 105 | + rsDebug(rsAtomicLoad(a, moRelaxed)) | |
| 106 | + | |
| 107 | +template rsDisplay*[T](a: Atomic[T]): string = rsDebug(a) | |
| 108 | + | |
| 109 | +## Rust's atomic operations take `&self`: reading and writing through a shared | |
| 110 | +## reference is the whole point of an atomic. Nim's `load`/`store` want a | |
| 111 | +## mutable location, so these take the address. The operation is unchanged -- | |
| 112 | +## only the way Nim is told where it happens. | |
| 113 | +## Templates, not procs: a proc parameter is a copy, so `unsafeAddr` inside one | |
| 114 | +## takes the address of the copy and a store is written to it and lost. A | |
| 115 | +## template expands where it is written, so the address is the caller's. | |
| 116 | +template rsAtomicLoad*(a, order: untyped): untyped = | |
| 117 | + cast[ptr typeof(a)](unsafeAddr a)[].load(order) | |
| 118 | + | |
| 119 | +template rsAtomicStore*(a, v, order: untyped): untyped = | |
| 120 | + cast[ptr typeof(a)](unsafeAddr a)[].store(v, order) | |
| @@ -97,3 +97,24 @@ proc rsNewJoinHandle*[T](): RsJoinHandle[T] = | |||
| 97 | proc rsJoin*[T](h: RsJoinHandle[T]): T = | 97 | proc rsJoin*[T](h: RsJoinHandle[T]): T = |
| 98 | joinThread(h.th) | 98 | joinThread(h.th) |
| 99 | h.res[] | 99 | h.res[] |
| 100 | + | ||
| 101 | +## Rust's `#[derive(Debug)]` on an atomic prints the value it holds, read with | ||
| 102 | +## a relaxed load. Nim's `load` wants a mutable location and a `Debug` does not | ||
| 103 | +## have one, so the address is taken -- the read itself is unchanged. | ||
| 104 | +template rsDebug*[T](a: Atomic[T]): string = | ||
| 105 | + rsDebug(rsAtomicLoad(a, moRelaxed)) | ||
| 106 | + | ||
| 107 | +template rsDisplay*[T](a: Atomic[T]): string = rsDebug(a) | ||
| 108 | + | ||
| 109 | +## Rust's atomic operations take `&self`: reading and writing through a shared | ||
| 110 | +## reference is the whole point of an atomic. Nim's `load`/`store` want a | ||
| 111 | +## mutable location, so these take the address. The operation is unchanged -- | ||
| 112 | +## only the way Nim is told where it happens. | ||
| 113 | +## Templates, not procs: a proc parameter is a copy, so `unsafeAddr` inside one | ||
| 114 | +## takes the address of the copy and a store is written to it and lost. A | ||
| 115 | +## template expands where it is written, so the address is the caller's. | ||
| 116 | +template rsAtomicLoad*(a, order: untyped): untyped = | ||
| 117 | + cast[ptr typeof(a)](unsafeAddr a)[].load(order) | ||
| 118 | + | ||
| 119 | +template rsAtomicStore*(a, v, order: untyped): untyped = | ||
| 120 | + cast[ptr typeof(a)](unsafeAddr a)[].store(v, order) | ||