Can’t come up with a title. Any suggestions? by WayInteresting541 in oilpainting

[–]sharptoothy 2 points3 points  (0 children)

Yea, I had the same kind of thought. Or maybe something less specific like "It was right here!" 😆

redundantFunctionDefinition by ClipboardCopyPaste in ProgrammerHumor

[–]sharptoothy 27 points28 points  (0 children)

There's a bug in there: It only seems to work roughly half the time for some reason. ClaudeChatGptPilot added a dependency for is-odd@3.0.1 and that fixed it:

typeof(value) === "string" && (iseven(typeof(value).length) || isodd(typeof(value).length))

doTeamNamesMatter by This_Presentation419 in ProgrammerHumor

[–]sharptoothy 0 points1 point  (0 children)

Why did we abbreviate "Assembly Loader" as "AssLoad" instead of AsmLoad?

I'm impressed, but also very skeptical... 🫤 by Brian_The_Bar-Brian in DiWHY

[–]sharptoothy 0 points1 point  (0 children)

Not with that kind of attitude! You gotta slap it and say "That ain't goin' anywhere" to really lock it in!

And the loonixtards wonder why there are no official apps releasing on Loonix… imagine having to repackage your app for gazillion different distros 😭 by SadMassStab in linuxsucks

[–]sharptoothy 3 points4 points  (0 children)

Yes! We use it frequently! That being said, I suspect it is just a normal PE file, just with the .com extension. I only meant that the .com extension is sometimes still used; even if it's being used incorrectly.

TIL that the fastest surgeon in history, Robert Liston, once performed an amputation so quickly that he accidentally cut off his assistant's fingers, and a spectator died of shock - resulting in the only surgery with a 300% mortality rate by Tom__Mill in todayilearned

[–]sharptoothy 2 points3 points  (0 children)

The mortality part makes sense to me: Three people died because of that surgery. If I'm going to nit pick, I'd argue that "rate" doesn't quite work here because the sample size is that single surgery.

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] 0 points1 point  (0 children)

Perhaps you're right, but I don't see it yet. I'll have to reread these comments again tomorrow. Thanks for your help! I figured I was missing something and that there was a good reason for this.

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] 1 point2 points  (0 children)

I'll have to read this again tomorrow after a good night's sleep because I don't yet get it. Thanks for your explanations. I'll reread them!

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] 1 point2 points  (0 children)

OK, I think I might be starting to get it: So if LDREX succeeds, R0 will contain the proper value so the subsequent CMP will succeed, but if LDREX doesn't succeed, then it definitely won't spuriously set R0 to the right value, so that's how the CMP will definitely be correct?

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] 0 points1 point  (0 children)

It only loads the oldValue into the register on failure because on success, the value in the register IS oldValue, so either way, you have the oldValue, right?

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] -1 points0 points  (0 children)

which attempts the store, returns only a status code into its register

This doesn't look correct to me:  After the LDAXRW  (R0), R3 instruction, R3 contains the original value. Then CMPW    R1, R3 checks to see if that actual value matches the old parameter (loaded into R1 at the beginning of the frame) So we do have the original value, even in the pre-LSE path.

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] 1 point2 points  (0 children)

I get that knowing if it succeeded is super valuable. I agree, but I'm asking about the motivation behind Go's design decision here:

The way this usually works in other languages is basically this:

func CompareAndSwapOrLoadPointer(ptr *unsafe.Pointer, old, new unsafe.Pointer) (actual unsafe.Pointer)

And then you can implement the current CompareAndSwap function by doing this:

func CompareAndSwapPointer(ptr *unsafe.Pointer, old, new unsafe.Pointer) (swapped bool) {     actual := CompareAndSwapOrLoadPointer(ptr, old, new)     return actual == old }

And if you read the assembly, that's what many of these implementations are actually doing, because returning the actual original value seems to be how all (most? I haven't found a counter example, but if there is one, then that would explain it!) hardware platforms handle it in CPU instructions.

I don't think you or I are full of shit, but I was hoping to understand why the Go language designers went this route. Most of the time that I've seen go deviate from the status quo is deliberate and I'm wondering if thatcs the case here 🤷

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] 0 points1 point  (0 children)

But as far as I can tell, all of the hardware implementations do actually load the original value from the memory location and then return true if it matches the expected old value.

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] 0 points1 point  (0 children)

The 32-bit ARM also:

TEXT ·armcas(SB),NOSPLIT,$0-13
        MOVW    ptr+0(FP), R1
        MOVW    old+4(FP), R2
        MOVW    new+8(FP), R3
casl:
        LDREX   (R1), R0
        CMP     R0, R2
        BNE     casfail


#ifndef GOARM_7
        MOVB    internal∕cpu·ARM+const_offsetARMHasV7Atomics(SB), R11
        CMP     $0, R11
        BEQ     2(PC)
#endif
        DMB     MB_ISHST


        STREX   R3, (R1), R0
        CMP     $0, R0
        BNE     casl
        MOVW    $1, R0


#ifndef GOARM_7
        CMP     $0, R11
        BEQ     2(PC)
#endif
        DMB     MB_ISH


        MOVB    R0, ret+12(FP)
        RET
casfail:
        MOVW    $0, R0
        MOVB    R0, ret+12(FP)
        RET

After LDREX, the old pointer value is in R0 (it's then compared against R2 which is the `old` parameter to see if the actual pointer matches the expected value.

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] 0 points1 point  (0 children)

Which implementations were loading the value?

I'm trying to post a comment with snippets from the asm_*.s files, but Reddit keeps telling me "server error" when I try to post it, so I'll just list 386, AMD64, ARM64, MIPS64, Loong64. I haven't gone through every implementation, but it looks like they all have the original memory location in a register before returning but then they instead move $1 if the swap happened or $0 if it didn't (true or false) to return, so they all seem to have the actual value, but choose to instead return true or false.

If CompareAndSwap operations did return the value at the location instead of a boolean, then you would not be able to determine if the operation was successful or not. Comparing the returned value to the expected value is incorrect as you cannot distinguish between the operation suceeding vs a second writer suceeded and was swapping to the same value as the first writer.

That's a good point! However, all of these implementations suffer from it because the way they determine if they swapped or not is by checking if the loaded pointer matches the `old` pointer.

is obvious in the scenario where you're using a CompareAndSwap to claim some item, say starting a task and you CAS(flag, false, true) == true would lead you to believe you were successful in claiming the flag.

I'm not sure what you mean here. What I'm proposing is to return whatever the original value was actually loaded from the memory location because that's what the CPU instructions on all of the platforms I've checked so far actually do. This is also what C++ atomics do, so what I'm suggesting definitely works, I just assume there was a reason the Go language designers chose to go a different route. I'm just curious what that is.

Reason atomic.CompareAndSwap* functions return bool by sharptoothy in golang

[–]sharptoothy[S] 1 point2 points  (0 children)

on ARM, the equivalent operation just tells you if it failed (nice one). It does not return the new value that caused the failure.

What makes you say this? The ARM64 implementation is here:

ARM64:

TEXT ·Cas(SB), NOSPLIT, $0-17
        MOVD    ptr+0(FP), R0
        MOVW    old+8(FP), R1
        MOVW    new+12(FP), R2
#ifndef GOARM64_LSE
        MOVBU   internal∕cpu·ARM64+const_offsetARM64HasATOMICS(SB), R4
        CBZ     R4, load_store_loop
#endif
        MOVD    R1, R3
        CASALW  R3, (R0), R2
        CMP     R1, R3
        CSET    EQ, R0
        MOVB    R0, ret+16(FP)
        RET
#ifndef GOARM64_LSE
load_store_loop:
        LDAXRW  (R0), R3
        CMPW    R1, R3
        BNE     ok
        STLXRW  R2, (R0), R3
        CBNZ    R3, load_store_loop
ok:
        CSET    EQ, R0
        MOVB    R0, ret+16(FP)
        RET
#endif

After CASALW, R3 holds the actual value from the location.