dzaima

Joined 7 years ago

Karma
1,748
Submissions
973
Member since
Sep 30
https://github.com/dzaima/

https://codeberg.org/dzaima/

View on Hacker News

Recent submissions

Comment · 16 days ago
A C implementation can be fully-compliant without conforming to ISO/IEC 6055, that's optional; question is just whether that optionality extends to fenv.h too. (at least how far I looked, imo it doesn't, and the pragma must exist, and valid fesetround calls (which there may be none of if no FE_* rounding modes are defined) must be represented by FLT_ROUNDS, even if it changes literally no actual float op behavior)
Comment · 16 days ago
...I guess you don't use any C compilers targeting x86-64 then? Cause on all the main configurations in my testing (gcc+linux, clang+linux, clang+windows, msvc+windows) x86-64 uses signed char. As such more programs probably break with forced unsigned char than signed char.
Comment · 16 days ago
That a library exists that claims to do atomics doesn't mean that it actually has working well-defined semantics, and that no standards-compliant C99 compiler can do optimizing transforms that'd break it.

Of course no doubt that (hopefully) C compilers used for compiling plan 9 things won't break it, but...that's then talking about plan9-C, not C99, at which point you're necessarily already in the bucket of "this library relies on extra guarantees that plan9 C makes, instead of it just being a regular C99 library".

> > so you'd need to know how Plan 9's compiler/language specifies atomics.

> It doesn't, as far as I can tell. Kencc is a very "dumb" compiler

...So it effectively quietly makes a bunch of quiet guarantees that C99 doesn't, on account of not making certain valid optimizations. Even if not explicit, that's still semantics outside of those C99 specifies, that code may be relying on.

Fact of the matter is, you cannot make a full atomics library that will necessarily work correctly on C99. As a simple example I came up with on the spot:

    static int foo;
    int main() {
      some_preparation();
      foo = 2;
      fence_probably();
      do_thing(&foo);
    }
A fully-proper standards-compliant C99 compiler could notice that foo isn't used until the `do_thing` call, and as such reorder the `foo = 2;` to after the `fence_probably` call (yay for putting all operations on foo in the same place!).

But, if atomics exist, `do_thing` could do a relaxed store of its given foo address to a global, and another thread could then do a relaxed load of that and dereference that to get foo's value. And if the compiler decides to reorder the store to `foo` to after the fence (as a C99 compiler can), the program now has a data race that it otherwise would not.

(hell, a C99 compiler could even insert `int tmp = something; something = 123; something = tmp;` for any variable or global or field `int something`, in any place in the code (as long as `something` is known to be mutable), which'd break basically every multithreaded thing; this effectively one of the things the "Threads Cannot Be Implemented As a Library" paper mentions)

Comment · 16 days ago
Yeah, that's still an issue of course. Root cause is still just that gcc utterly doesn't support any of fenv anything.

That said, I was surprised that it's mandatory by the standard (and wasn't made optional later like VLAs); feels rather unlike C to require such precise control over something it doesn't even specify any behavior of - imagine if we had mandatory macros for integer overflow behavior and whatnot!

...That "that said" said, C11 does also contain:

> Each of the macros FE_DOWNWARD FE_TONEAREST FE_TOWARDZERO FE_UPWARD is defined if and only if the implementation supports getting and setting the represented rounding direction by means of the fegetround and fesetround functions.

so presumably one could just not define those and get away with not implementing (at least the rounding mode part of) fenv.h, properly allowing the `#define FLT_ROUNDS 1` hack? gcc+glibc does define those, though.

Comment · 17 days ago
> gcc, however, always defines FLT_ROUNDS to 1

Well, considering that gcc doesn't support the FENV_ACCESS pragma, and as such all fesetround calls are always UB, it is valid for it to act as if the rounding mode never changes from the default :)

Comment · 18 days ago
Have to be rather careful with such - e.g. an x86 vaddpd or an ARM fadd aren't commutative because of NaN bit patterns (even worse for fma, esp. if you have it automatically choose the operation from which arg it's destructive over).

Perhaps not significant for writing code (though maybe someone does want the NaN bit pattern to be taken from a specific arg!), but is one aspect that can easily go missing in a higher-level assembler (or esp. disassembler).

Comment · 23 days ago
Except gcc doesn't actually support it to any sane extent (it ignores and warns on "#pragma STDC FENV_ACCESS ON", which C requires for fenv.h to actually function; and as such gcc makes a bunch invalid optimizations); clang supports it, but only as of somewhat-recently.
Comment · 1 month ago
Stop the presses! 5 instructions!

    vpor        xmm2, xmm0, 0x80000000
    vpcmpeqd    xmm2, xmm1, xmm2
    vcmpunordps xmm2, xmm2, xmm0
    vminps      xmm0, xmm1, xmm0
    vblendvps   xmm0, xmm0, xmm1, xmm2
yay for superoptimizers
Comment · 1 month ago
That vminpd+vminpd+vorpd actually does handle signed zero properly! Screws up NaN payloads to the max though. Can be easily extended to canonicalize the NaN with 2 instrs + constant though of course. (which ends up at the same number of instrs as your proper impl (albeit with worse port distribution and latency), but you get to have a canonical NaN!)

Hit upon https://github.com/llvm/llvm-project/issues/217376 while playing around with proper minimumnum, failing to SMT-verify whatever version of LLVM I had; did find a funky working 6-instr (+ constant) version though:

    vpandn      ymm2, ymm1, ymm0
    vpcmpeqd    ymm2, ymm2, 0x80000000 # whether ymm0 is -0 and ymm1 is +0 (or other cases that magically don't cause issues)
    vcmpltpd    ymm3, ymm0, ymm1
    vcmpunordpd ymm3, ymm3, ymm1 # regular NaN-is-larger ymm0<ymm1
    vpor        ymm2, ymm2, ymm3
    vpblendvb   ymm0, ymm1, ymm0, ymm2
Comment · 1 month ago
Comment · 1 month ago
Except the ALU isn't actually 64-bit, it's 67-bit as per article, extra bits for rounding. I'd imagine it was just taken for "prettiness", with 15 bits for exponent being basically reasonable. (maybe some algorithms which double precision per iteration would like it being a power of two? but any such probably vary significantly on initial estimate precision anyway)
Comment · 1 month ago
Or "--ignore-other-worktrees" for "git switch".

This leaves things in a weird state though, as modifying the branch from one of the worktrees will effectively change the ref the other is pointing to, but won't update the other's working tree, making it look like it's preparing to revert all the changes that the first worktree made.

(obligatory mention of jj, whose workspaces track what state they were snapshotted at, thus never losing what the true diff of a workspace should be, allowing a "jj workspace update-stale" to safely update the working copy with whatever the other workspaces changed (if you change the working copy and in parallel from another workspace change what that workspace points to, you'll get a saved commit of the working copy changes before being updated, which you'll need to squash/rebase wherever those changes were needed))

Comment · 1 month ago
How so? It's replacing multiple enum variants, but just one enum, "enum Value".

(also; if anything, the title is implying the exact opposite of "Rust compiler was able to optimize ...", "Replacing a Rust [...] with [...]" is clearly moving away from Rust-magic to something else)

Comment · 1 month ago
Unless the highly-optimized parts are wrapped by an interface that looks similar to the non-optimized version.

In the case of a tagged object in Rust, depending on how well the compiler can wrangle through it, you might even be able to add a `.unpack()` method that returns a pretty enum from a packed value, that you can pattern-match on or whatever, and let the compiler remove all the code of unpacking unused cases.

(using that directly for the addition example would end up less efficient of course, but still most likely beneficial. It's after this when there's a potential true readability vs performance tradeoff)

Live from the official Hacker News API

A modern reader for Hacker News · SvelteKit + Skeleton