Live data from Hacker News

There's No Such Thing as “Implicitly Atomic”

belkadan.com

31–40 of 47 posts

Re: There's No Such Thing as “Implicitly Atomic”

#31
post #22

I assume Rust concurrency guarantees will prevent races and have this covered. Except of course if you use unsafe. Right?

Yes.

It is actually fairly tricky to get a value across threads like this. The simplest I could come up with is this:

    struct UnsafeSync(T);
    
    unsafe impl Sync for UnsafeSync {}
    
    fn main() {
        let i = std::sync::Arc::new(UnsafeSync(std::cell::Cell::new(0)));
        
        let thread_i = i.clone();
        std::thread::spawn(move || {
            thread_i.0.set(1);
        });
        
        eprintln!("i is {}", i.0.get());
    }
https://play.rust-lang.org/?version=stable&mode=debug&editio...

Re: There's No Such Thing as “Implicitly Atomic”

#32
post #15

> Even that isn’t guaranteed. If you don’t say “atomic”, the compiler might decide to split up a store to optimize for speed or code size! This isn’t hypothetical; Greg Parker ran into this with libobjc. Maybe I'm tired and lack imagination, but please enlighten me. How can splitting up a store speed something up or reduce the code size? Assuming it is aligned and on a 32bit native CPU.

16 bit CPUs for one. They often lack the instructions.

Re: There's No Such Thing as “Implicitly Atomic”

#33
post #22

I assume Rust concurrency guarantees will prevent races and have this covered. Except of course if you use unsafe. Right?

Yes. It is actually fairly tricky to get a value across threads like this. The simplest I could come up with is this: struct UnsafeSync (T); unsafe impl Sync for UnsafeSync {} fn main() { let i = std::sync::Arc::new(UnsafeSync(std::cell::Cell::new(0))); let thread_i = i.clone(); std::thread::spawn(move || { thread_i.0.set(1); }); eprintln!("i is {}", i.0.get()); } https://play.rust-lang.org/?version=stable&mode=debug…

Why didn't you use a Mutex?

Re: There's No Such Thing as “Implicitly Atomic”

#34
post #33

Earlier quoted context omitted.

Yes. It is actually fairly tricky to get a value across threads like this. The simplest I could come up with is this: struct UnsafeSync (T); unsafe impl Sync for UnsafeSync {} fn main() { let i = std::sync::Arc::new(UnsafeSync(std::cell::Cell::new(0))); let thread_i = i.clone(); std::thread::spawn(move || { thread_i.0.set(1); }); eprintln!("i is {}", i.0.get()); } https://play.rust-lang.org/?version=stable&mode=debug…

Why didn't you use a Mutex?

What are you thinking?

I could have used a Mutex then cast the `&Mutex` to `&mut Mutex` so that I could call `.get_mut()`. But this didn't seem simpler.

Re: There's No Such Thing as “Implicitly Atomic”

#35
post #15

> Even that isn’t guaranteed. If you don’t say “atomic”, the compiler might decide to split up a store to optimize for speed or code size! This isn’t hypothetical; Greg Parker ran into this with libobjc. Maybe I'm tired and lack imagination, but please enlighten me. How can splitting up a store speed something up or reduce the code size? Assuming it is aligned and on a 32bit native CPU.

You don't even need unusual architectures - e.g. it's pretty easy to find a constant that might be fast to encode a value as 2 immediate and store the value in memory as two halfword stores on ARM - see https://alisdair.mcdiarmid.org/arm-immediate-value-encoding/ for the immediate encoding. It might be quicker than loading those immediates into a register, shifting and or-ing them as necessary, and then doing a single store. Especially if your focus is on code size (for small platforms or cache utilization reasons).

An I wouldn't be surprised if there's some weird x86 encoding that in some cases helps code size similarly, with different encoded instruction lengths allowing for different immediate sizes.

Re: There's No Such Thing as “Implicitly Atomic”

#36
post #33

Earlier quoted context omitted.

Why didn't you use a Mutex?

What are you thinking? I could have used a Mutex then cast the `&Mutex` to `&mut Mutex` so that I could call `.get_mut()`. But this didn't seem simpler.

I wanted to avoid unsafe.

Because unsafe introduces conceptual complications and it seemed to be simpler to avoid that in an example.

Re: There's No Such Thing as “Implicitly Atomic”

#37
post #36

Earlier quoted context omitted.

What are you thinking? I could have used a Mutex then cast the `&Mutex` to `&mut Mutex` so that I could call `.get_mut()`. But this didn't seem simpler.

I wanted to avoid unsafe. Because unsafe introduces conceptual complications and it seemed to be simpler to avoid that in an example.

Well you can't have a memory race without unsafe. The goal of the example was to show undefined behaviour.

Re: There's No Such Thing as “Implicitly Atomic”

#38
post #9
post #4

The FreeBSD kernel only runs on platforms where 32-bit sized and aligned ordinary loads and stores are atomic; this is a requirement that it demands of the hardware. This may not be (extremely) portable, or may not work for Swift, or userspace under some very weird runtime environments, but it is true of the vast majority of CPU hardware out there.

So FreeBSD can run only on simple single 32-bit CPUs with no cache (or no registers)? I would argue that outside of a few select embedded controllers there's almost no hardware out there that meets that requirement.

No, you’re making an unstated bogus assumption somewhere but I’m not sure which one.

Re: There's No Such Thing as “Implicitly Atomic”

#39
post #4

The FreeBSD kernel only runs on platforms where 32-bit sized and aligned ordinary loads and stores are atomic; this is a requirement that it demands of the hardware. This may not be (extremely) portable, or may not work for Swift, or userspace under some very weird runtime environments, but it is true of the vast majority of CPU hardware out there.

A problem identified in the article is that if you access a word-size object, your compiler may use a load or store of some different size, violating your assumptions about atomicity. This is not just about ISAs. The example cited was in a library written in C.

Yeah. FreeBSD requires its C compiler not to do that, essentially.

Re: There's No Such Thing as “Implicitly Atomic”

#40
post #4

The FreeBSD kernel only runs on platforms where 32-bit sized and aligned ordinary loads and stores are atomic; this is a requirement that it demands of the hardware. This may not be (extremely) portable, or may not work for Swift, or userspace under some very weird runtime environments, but it is true of the vast majority of CPU hardware out there.

It is important to separate the hardware and the language. The article is about Swift and it doesn't matter what the hardware guarantees exist for 32-bit loads and stores. The is the compiler's job. If the language doesn't guarantee that a particular action will product an atomic 32-bit load or store than it may be compiled to something different. This is why it is important to tell the language what you mean. Even i…

“Platform” in my comment refers to the combination of hardware and language/compiler targeting that hardware. FreeBSD does not target the C abstract machine, only a handful of very specific platforms. I agree it would probably be better to explicitly state atomic requirements using the primitives provided by the language.
Post reply on HN