← Back to context

Comment by Retr0id

2 days ago

> Program directly against syscalls

Works nicely on Linux where the syscall interface is explicitly stable, but on many (most?) other platforms this is not the case.

> There Is No Heap

I don't understand what this means, when it's followed by the definition of a heap allocation interface. The paragraph after the code block conveys no useful information.

> Null-terminated strings are the devil’s work

Agreed! I also find the stance regarding perf optimization agreeable.

Looks like the default allocator uses mmap(2) for every single allocation, which is horribly inefficient - you map a whole PAGE_SIZE worth of memory for every tiny string. Aside from just wasting memory this will make the TLB very unhappy.

It looks like sp_log's string formatting is entirely unbuffered which results in lots of tiny write syscalls.

  • That seems to be a pretty consistent quality level for the entire library. Look at the implementations in sp_math, yikes.

    • Oh man. Oof. I'm sure there must be some repository out there that has an AGENTS.md but isn't pure slopcode, but I haven't seen it yet. The number of people who can be trusted to vibe code "responsibly" is probably about the same as the number of people who can be trusted to write memory safe C.

      3 replies →

    • "How bad can it be, I mean I know that numerics are not many people's strong suit, but..."

      ... ... ... oh wow, the math functions are really bad implementations. The range reduction on the sin/cos functions are yikes-level. Like the wrong input gives you an infinite loop level of yikes.

      2 replies →

    • sp_math.h is, as noted at the top of the file, a repackaging of https://github.com/HandmadeMath/HandmadeMath

      It is not part of the core library. It is certainly not meant as a reference-level implementation of math functions. It's there so you can write an easing function for a game without pulling in libc. It seems like its existence has offended you. If that's the case...I'm sorry? At every possible point, I note as loudly as possible exactly what that library is. I found your tone extremely dismissive and disrespectful and I don't care to engage with that any more than I already have.

      2 replies →

    • > That seems to be a pretty consistent quality level for the entire library. Look at the implementations in sp_math, yikes.

      That does spin the meaning of "Sp.h is the standard library that C deserves"

  • Not just the TLB, but the L1 D$ will be very unhappy as well. All heap objects being page aligned on most microarchs ends up making every object start at cache set 0 because the set determination ends up being indexed off of the offest within a page so that the TLB lookup can happen in parallel with the set load.

  • The point of the library is that you do not call the low level allocation primitive to allocate a single string. Of course, in simple programs which exit immediately, there is no difference between using a page allocator and a heap allocator. In real programs, I use an appropriate allocator for the allocation rather than making arbitrary calls to malloc(). In the sp.h examples, I use the page allocator to keep freestanding Linux simple. I could swap out a single line to be backed by an arena, but it misses the forest for the trees.

    sp_log() writes directly to an IO writer. An IO writer can be buffered or unbuffered, but is unbuffered by default. This is a feature, not a bug. Have a look through the IO code!

    Cheers and thanks for reading.

    • So every program using sp has to re-invent malloc or multiple copies of their own bespoke allocator and this is supposed to be a good idea?

      1 reply →

    • q> sp_log() writes directly to an IO writer. An IO writer can be buffered or unbuffered, but is unbuffered by default. This is a feature, not a bug. Have a look through the IO code!

      Why is the unbuffered default? Is there any thoughts on this?

      2 replies →

  • Jesus! Claude could've told this guy all these things. People underestimate how much the average malloc implementation does and how many considerations it makes. Or how much IO sucks.

Thanks for reading. "There is no heap" is meant to say that your mental model of memory shouldn't be one heap from which all memory is pulled. It should be many heaps, owned by many different allocators and providing different semantics. Hence the opinionated stance of the library; there is no allocation function that does not force you to specify the specific heap you want to allocate from. I'm sorry if I didn't explain that well.

As far as the syscall thing, it's actually quite interesting. NT is also extremely stable. Likewise for the stock Darwin syscalls on macOS. In practice, though, Windows loads kernel32.dll automatically, so there's no drawback in using it when appropriate. I still call directly into NT sometimes (mostly to skip complex userspace path translations that aren't useful). On macOS, you are likewise forced to link to libc (libSystem.dylib), and so I usually just end up using the syscall-wrapper libc functions there.

> Works nicely on Linux where the syscall interface is explicitly stable, but on many (most?) other platforms this is not the case.

There is a footnote on this saying as much:

> 3. Where “syscall” means “the lowest level primitive available”. On Linux, it’s always actual syscalls. On Windows, that’s usually NT. On macOS, it’s usually the syscall-wrapper subset of libc because you’re forced to link libc and it’s not quite as open as Linux (although there is a rich “undocumented” set of APIs and syscalls that are very interesting).

The "definition of a heap allocation interface" indicates that there is no standard heap. Instead, there's a standard interface for the use to define their own heaps. Any standard library function that needs to allocate will take a sp_allocator_t parameter, and use that to allocate. As opposed to e.g. strdup, which hard-codes a call to malloc internally. Sp.h's strdup-alike would take an sp_allocator_t as input and call into that to get the memory it needs.

A C++ programmer might describe this as "PMR, but not default-constructible. And std::stable_sort takes a PMR allocator parameter. And PMR is the default, and there's no implementation of std::allocator (or new or delete)."