Rendered at 09:24:13 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
nextaccountic 1 days ago [-]
> Principles
> Be extremely portable
> sp.h is written in C99, and it compiles against any compiler and libc imaginable. It works on Linux, on Windows, on macOS. It works under a WASM host. It works in the browser. It works with MSVC, and MinGW, it works with or without libc, or with weird ones like Cosmopolitan. It works with the big compilers and it works with TCC.
> And, best of all, it does all all of that because it’s small, not because it’s big.
vs
> Non-goals
> Obscure architectures and OSes
> I write code for x86_64 and aarch64. WASM is becoming more important, but is still secondary to native targets. I don’t care to bloat the library to support a tiny fraction of use cases.
> That being said, if you’re interested in using the library on an unsupported platform, I’m more than happy to help, and if we can make the patch reasonable, to merge it.
Those are contradictory. Either the code is extremely portable, or it can't support "obscure" platforms, but not both.
bryanlarsen 21 hours ago [-]
It's an odd stance for a C library. In my experience, odd platforms are the main place that C is used in 2026. If you're writing a new Windows or Linux or MacOS or WASM program, it's not likely to be in C. But lots of new microcontroller software is still being written in C.
And he's already hit the hard targets. Many obscure OS's are generally UNIX like and should be easy ports. Many obscure arch's usually are running Linux and should be easy ports.
dboon 14 hours ago [-]
I have no philosophical complaints with supporting odd architectures in general. I agree that most obscure targets are probably not that much code, since the library is factored with this in mind (e.g. basic WASM support took an afternoon).
It's stated as a non-goal simply because it's not the most valuable thing I can do with my time. My fundamental stance is that writing new Windows or Linux or macOS or WASM programs in C is a good idea, and those are the programs that I write, so that's where my focus is. But if someone would like to come along and write the ~30 syscalls needed to port the library to a new platform, or even register any interest in such, I'd be happy to look into it at that point.
yjftsjthsd-h 11 hours ago [-]
That's fine. Just don't call it "ultra portable" while treating it as a non-goal.
rTX5CMRXIfFG 7 hours ago [-]
I'm seeing that people have a big issue with the language, but "ultra" doesn't even necessarily mean "total"
yjftsjthsd-h 2 hours ago [-]
Ultra might be a step below total, but the 3 most common OSs on the 2 most common ISAs is ... several steps below ultra.
nine_k 14 hours ago [-]
Aren't these MCUs predominantly ARM-based, with some RISC-V thrown in?
I see no contradiction in the desire to support x64 (because it would be ridiculous not to), ARM, and likely RISC-V, but not the venerable but now-fringe architectures like MIPS or Sparc or 68040 or even x86.
floxy 9 hours ago [-]
>now-fringe architectures like MIPS
For those not in the know, Microchip still produces MIPS microcontrollers:
The author can do whatever they want. But if all you want to support is x86-64, ARM64 and maybe some version of RISC-V, don't crow about how 'extremely' portable you are. At best, you don't know.
Brian_K_White 9 hours ago [-]
portable is a different word from ported.
magicalhippo 4 hours ago [-]
AfAIK lots of 8051-based microcontrollers that are quite modern, such as Silicon Labs EFM8 line[1] or STC which in addition to 8-bit 8051's also has 32-bit 8051 variants[2].
Portability across compilers is orthogonal to portability across target architectures.
thayne 5 hours ago [-]
This is true, but it would probably be good to be specific what is meant by "portable". There is also the dimension of operating system.
It sounds like it has a goal of being "extremely portable" across compilers, (although I'm curious how many compulers it is actually tested against) but only somewhat portable across architectures and operatings systems, just hitting the most popular ones.
murderfs 11 hours ago [-]
> Those are contradictory. Either the code is extremely portable, or it can't support "obscure" platforms, but not both.
I think it's perfectly valid to call code 'extremely portable' without supporting every special snowflake architecture. There's a spectrum from assumptions that hold on everything that isn't some esoteric joke architecture or archaeology to something that I would probably consider required for 'extremely portable'.
I would personally consider something that failed to support anything on this list above big endian as still being extremely portable: you'll build for any serious modern architecture that isn't a DSP.
- non twos complement integers
- (int) nullptr != 0
- segmented addressing
- non-8 bit char
- big endian
- missing floating point
ARM's done a good job of making it so that you can't assume the traditional x86 assumptions of being able to access any pointer unaligned or having sequentially consistent semantics on memory ordering (with the help of compilers getting better at reordering resulting in you needing to have proper semantics on x86 as well).
kevin_thibedeau 10 hours ago [-]
It makes liberal use of u64 all over the place rather than a more appropriate, machine adaptive unsigned int or unsigned long. It isn't a good fit for anything "exotic" like non-64-bit platforms. I wouldn't consider that in the spirit of portability when it compiles into bloated code with unnecessarily large structs.
murderfs 9 hours ago [-]
I was making a general point about portability, not this library in particular. I wouldn't consider "only x86_64 and aarch64" as being "portable".
kazinator 1 days ago [-]
You can be portable, without supporting obscure platforms.
Supporting obscure platforms is what makes portability "extreme", though.
hnlmorg 1 days ago [-]
“Portable”, in the context of how it was used, generally refers to software using platform agnostic idioms.
If you have to write extensive patches to actually port the software, then it’s only “portable” in the same sense that any software can be ported with enough effort. Ie “Foo is portable. You just have to write a write a whole new kernel to port it”
kazinator 18 hours ago [-]
Is the Foo kernel 75% of the code base, 5% or 0.01%?
hnlmorg 12 hours ago [-]
I’m not getting dragged into a strawman argument about meaningless hypothetical percentages of a vague and arbitrary illustration. And particularly not when the point being made was pretty clear:
> “Portable”, in the context of how it was used, generally refers to software using platform agnostic idioms.
kazinator 10 hours ago [-]
Portable has two meanings: a construct is portable if we can rely on it to work everywhere. A portable codebase is one that supports moving to a new platform, with some nonzero effort which is not large compared to rewriting the code. It is able to be ported. E.g, "Johnson's Portable C Compiler (PCC)".
If only the Foo kernel must be rewritten in order to port Foo, but that kernel is 75% of Foo, then I would say Foo is not portable. If the kernel is 0.1% of Foo, then I would say that it is: 99.9% of the code base depends on the abstractions in the Foo kernel rather than platform features.
hnlmorg 1 hours ago [-]
> “Portable”, in the context of how it was used, generally refers to software using platform agnostic idioms.
“in the context of how it was used,”
This isn’t some meta conversation about how to write portable software. This is a conversation specific to what the author had written about their project.
ktpsns 1 days ago [-]
Exactly. This shows that "extremely portable" is actually marketing for "It supports a number of platforms. In my opinion, this number is big".
noosphr 1 days ago [-]
We support extreme portability for sufficiently large values of two.
egl2020 12 hours ago [-]
When I was a wee lad, a conference speaker announced that "portability" meant "runs on anything that supports OS/360".
lelanthran 1 days ago [-]
> This shows that "extremely portable" is actually marketing for "It supports a number of platforms. In my opinion, this number is big".
The number might just be zero - did anyone check if this compiles? I am trying to track down where the function `sp_mem_allocator_alloc_type` is defined (used in 3x places) but it doesn't appear in the GH search results.
I'm not going to clone and build this (too dangerous).
flohofwoe 1 days ago [-]
> I am trying to track down where the function `sp_mem_allocator_alloc_type` is defined
`sp_mem_allocator_alloc_type ` is going through a couple of macro resolutions which ends up at `sp_mem_allocator_alloc`
> I'm not going to clone and build this (too dangerous).
Your computer won't explode just from downloading and compiling some C code, don't worry ;)
The github repo builds and the examples run just fine on macOS by just running `make` in the project directory, although with one warning:
warning: 'posix_spawn_file_actions_addchdir_np' is deprecated: first deprecated in macOS 26.0
dboon 1 days ago [-]
It looks like I need to update my macOS machine! Thanks for the sanity, and thanks for reading.
rswail 23 hours ago [-]
Only if you want to move from MacOS Sequoia to Tahoe. Tahoe has the Liquid Glass stuff that people don't like as well as other UX changes that have been controversial.
Apple still do security updates on Sequoia.
locknitpicker 1 days ago [-]
> Your computer won't explode just from downloading and compiling some C code, don't worry ;)
This is the first time I ever saw anyone dismissing the risk of downloading and running stuff off the internet.
"Don't worry".
jamespo 1 days ago [-]
Compiling
pjmlp 1 days ago [-]
Depends how compiling actually happens in practice, what executables and scripts are called.
lmz 22 hours ago [-]
"I have no idea how that rm -rf $HOME ended up in the Makefile"
1 days ago [-]
flohofwoe 1 days ago [-]
Did you never install anything on your computing device? It's the same risk, or even worse if it was closed source software installed via an app store
lelanthran 1 days ago [-]
> Your computer won't explode just from downloading and compiling some C code, don't worry ;)
I have no idea what's in the Makefile, and I'm not going to review it just so to try and figure out where a function is defined :-/
flohofwoe 1 days ago [-]
I found the function body in about 30 seconds by browsing the code on github via plain old text search. Is that also to dangerous? ;)
rigonkulous 20 hours ago [-]
Use better tooling, then. Cscope is your friend.
forgotpwd16 24 hours ago [-]
> I'm not going to clone and build this (too dangerous).
Just create a disposable isolated environment, like VM or container, and do it inside? And, yes, does compile.
rigonkulous 20 hours ago [-]
For C-based projects, use cscope. It found it pretty fast.
dboon 1 days ago [-]
There are very few C libraries which compile, stock, against the matrix of toolchains, ABIs, and operating systems that this library does. For the subset of machines which run, I don't know, 99.9% of all instructions (i.e. x86_64 + aarch64, Linux + Darwin + Windows), the library just works. This is a definition of portability. Why would portability be a binary of supporting every possible system or being hard tied to a single one?
AlotOfReading 1 days ago [-]
The natural comparisons are libraries like glibc and newlib, which do support lots of architectures and more importantly make porting to new architectures or taking advantage of platform features pretty straightforward.
trueno 5 hours ago [-]
people also greatly underestimate the performance of glibc and the insane work and literal decades it's taken to achieve. people keep relearning this lesson the hard way when they link musl and suffer perf issues. one needs not look further than the libc mailing lists and if they were so dedicated could probably find a discussion on every performance issue they face with the glibc alternatives over the decades.
for embedded defs not against portable alternatives like this tho.
however ops post sure gets off on the wrong foot by saying this is "fixing C". the hubris of mankind on full display, yet again
scott01 1 days ago [-]
I’m not as experienced as some people here, but in ~10 years, I’ve never needed to write code for anything other than x86 or arm. So I agree with the author on their priorities.
hnlmorg 1 days ago [-]
Not all ARM systems are created equally. For example many of the 32bit ARM processor didn’t even support floating point ops. So they’d have to be calculated in software.
Aside from various different variations of ARM, I’ve worked several variations of x86 and AMD64, SPARC, PowerPC, MIPS and others I’ve likely forgotten. Not to mention a few 8-bit CPUs too, but those there more than 20 years ago and not really fair to discuss here.
sitzkrieg 15 hours ago [-]
in just the past ten years i’ve had to write asm for x86, arm, mips, riscv, 8051 and something else i can’t even remember. generalizing rules
taneq 20 hours ago [-]
This debate reminds me of way back in the day when Java first became popular. I excitedly started writing things in Java because it would be portable! I quickly learned, of course, that Java was not portable at all, for values of ‘portable’ equal to ‘my friends can run it without installing the JRE and using the command line’, which is the actual definition in practice. Those friends all ran Wintel boxes. A Win32 i386 binary was (and probably still is) the most universally runnable native code.
1 days ago [-]
imtringued 1 days ago [-]
Yeah he doesn't even try to support major platforms like RISC-V. I know there is fragmentation, but best effort attempts at portability would show some sign of goodwill.
forrestthewoods 1 days ago [-]
That’s a lot of text to say “well ackshually”.
riedel 1 days ago [-]
I could not even find a mention what platform it supports. There is a Linux example on the bottom. Have never seem a libc implementation that does not even mention for which platforms it is meant.
dboon 1 days ago [-]
It...is not a libc implementation. That's an impressive level of misunderstanding!
forrestthewoods 1 days ago [-]
> sp.h is written in C99, and it compiles against any compiler and libc imaginable. It works on Linux, on Windows, on macOS. It works under a WASM host. It works in the browser. It works with MSVC, and MinGW, it works with or without libc, or with weird ones like Cosmopolitan. It works with the big compilers and it works with TCC.
bsder 1 days ago [-]
You could, of course, spend 30 seconds look at the code on Github which you would have to do if you were interested in using it anyway?
Or you could actually try the compliance suite on an architecture and report back to us if it works?
imtringued 1 days ago [-]
You've rejected a user. You can't complain that he has no interest in your project at that point. The bridge is burned.
bsder 24 hours ago [-]
I don't know how the author would feel. But, honestly, for a libc replacement, I'd personally be okay with that ...
If you can't be bothered to look at a Makefile (or ask an AI to look at the Makefile), you are almost certain to be more trouble than any possible benefit you will bring.
Especially in the realm of open source, I'm becoming increasingly comfortable with "If you can't be bothered to jump through even the most minimal of hoops, please get lost."
dboon 14 hours ago [-]
People are very silly and very entitled. I'd bend over backward to help anyone contribute to or use the library in any way. In response, all I ask is for some common courtesy and friendliness. Spending more than exactly zero seconds on people who won't give you that is a waste of time.
In other words, you hit the nail on the head. Anyone who acts this way can get fucked! We'll be having a good time and making friends without them
lifthrasiir 1 days ago [-]
Zig, one of the giants upon whose shoulders this library stands, coined a name for this
almost-but-not-quite-UTF encoding: WTF-8 and WTF-16. These encodings mean, simply, the
same as their UTF counterpart but allowing unpaired surrogates to pass through.
To give credit where credit is due, both WTF-8 and WTF-16 were devised by Simon Sapin [1] and Zig simply picked them up.
Wait, is a compound literal an l-value in that sense (as opposed to, just being able to take its reference)?! Take a look at the C99 standard Oh my, it indeed is (C99 §6.5.2.5 p5). Good to know!
abcd_f 23 hours ago [-]
The WTF name really lies on the surface, there's no authoritative source of its origin.
I have a wtf.c from 10+ years ago when I was re-implementing Windows-style Unicode handling for some project. You keep running into various quirks, which accumulate and you inevitably arrive at your WTF moment. So WTF as name comes up naturally, no special wit required.
lifthrasiir 23 hours ago [-]
That might be possible, but Simon Sapin was who tried to specify what exactly are WTF encodings so that should mark sort of milestone.
dboon 19 hours ago [-]
Thanks! I didn’t know this.
saidnooneever 27 minutes ago [-]
provide performance benchmarks + specs of the machine that ran them. provide similar code between for e.g. libc and this lib and then show how its compiled / disassembled to show what is more optimal.
i doubt many people will go through the whole code themselves or do this stuff to determine if claims are true and/or its worth to port something to this or start learning it.
people spend a lot of time getting familiar with libraries in order to be able to use them properly.. If you help them a long that path more it will be more inviting to try your code.
(that being said i think people talking about fixing C likely dont realise that people just quietly roll their own libs like this..not to fix C but because that is what C programming is.)
A comment from a legend. Thanks for reading and thanks for the response! I agree; the dynamic array is typed as a T* for ergonomics sake but is similarly a pointer and a length (and an allocator).
Could I pick your brain a little more on the design? I'm spader at spader.zone; if you have time, drop me an email. I promise not to take too much of your time and I'd love to hear from you.
Strings (and arrays) being length/ptr is a freaking enormous win, in simplicity, performance, and overflow bug elimination.
One of D's secret features is that string literals still have a 0 appended to them, even though the length of the string does not include the 0. This makes it super slick to call C functions, like printf, using a string literal for the format string.
I'm baffled why C spends its energy doing things like normalized Unicode identifiers (an abomination) instead of something incredibly useful like length/ptr arrays.
gritzko 5 hours ago [-]
I use a C dialect with (ptr,ptr) slices and (ptr,ptr,ptr,ptr) buffers. Effectively a different programming language, still uses standard C compiler.
In a cleared codebase though all the usual C memory bugs are virtually non-existant. When did I see core dump last time? I do not remember. Thus feel no urge to use Zig or Rust.
WalterBright 3 hours ago [-]
Congrats! I had many years of chasing memory bugs. It certainly influenced the design of D a lot.
Interestingly, I rarely make a memory bug these days. Too much experience, I've just learned not to make them.
But I still prefer to use language features that make it easier to not make such errors.
x0re4x 13 hours ago [-]
I suggest using a slightly different array operator syntax for fat pointer arrays: "char a[|..|]" instead of just "char a[..]" to make them visually distinct and indicate that element access has additional bounds check. (syntax inspired by ocaml)
WalterBright 13 hours ago [-]
Thank you for the idea! I have no experience with Ocaml, so have no opinion on it. My experience is the simpler the syntax, the more people will use it.
secondcoming 13 hours ago [-]
But won't all those posix functions that take only `const char*` parameters need to be changed to be pointer/length?
WalterBright 11 hours ago [-]
No. For string literals, they already have a 0 appended, so no problem. For others, you'll need to malloc/copy/free.
It hasn't been much of an issue with decades of D code.
p4bl0 1 days ago [-]
First, thanks for sharing this link, it was an interesting read! A few remarks below.
I had a hard time reading the wc code in the article. First I had to go to the GitHub to understand that "da" stands for dynamic array, and then understand that what the author calls wc is not at all the wc linux commands, which by default gives you the number of lines, words, and characters in a file, not the count of occurrences of each word in the file, which is what the proposed code does.
Also, since I had to read the GitHub README, another remark: it says that sp_io uses pthreads rather than fork and exec. Both of those approach (but especially pthreads) are contradictory to the explicit goals of programming against lowest level interfaces. I believe the lowest level syscall is clone3 [1], which gives you more fine grained control on what is shared between the parent and child processes, allowing to implement fork or threads.
To be portable it's probably best to use the pthreads API for everything, make no additional assumptions, and rely on the user to provide the implementation. Consider what happens when someone is working with OpenMP, CUDA, or similar and attempts to make use of a dependency that in turn makes use of your library. The easier it is to understand the assumptions made by your library the better.
p4bl0 3 hours ago [-]
I agree with that. I'm just stating that it's contradictory with the project's own principles.
> Program directly against syscalls
It's the very first one of the listed principles. In the paragraph after this title it even says it "must" be the case in italic to insist on it, and there's a footnote to define what they mean, which is very clear in that pthreads should be out according to this principle.
eqvinox 1 days ago [-]
By the time you know enough to reasonably use clone3, you have also learned that doing so is an exceptionally bad idea save for very rare circumstances.
zzo38computer 1 days ago [-]
I agree with most of the criticisms they make.
I agree that pointer and length is better than null-terminated strings (although it is difficult in C, and as they mention you will have to use a macro (or some additional functions) to work this in C).
Making the C standard library directly against syscalls is also a good idea, although in some cases you might have an implementation that needs to not do this for some reason, generally it is better for the standard library directly against syscalls.
FILE object is sometimes useful especially if you have functions such as fopencookie and open_memstream; but it might be useful (although probably not with C) to be able to optimize parts of a program that only use a single implementation of the FILE interface (or a subset of its functions, e.g. that does not use seeking).
alfiedotwtf 1 days ago [-]
Making every C call a system call is not a good idea at all - think about malloc() etc - the OS shouldn’t care about individual allocations and only worry about providing brk() etc. otherwise, performance will die if you’re doing a thousand system calls per second!
HexDecOctBin 1 days ago [-]
No modern libc uses (or should use) brk() as the heap. Allocate virtual memory using mmap, VirtualAlloc, etc., and manage your set of heaps.
zzo38computer 16 hours ago [-]
It is not what I meant and also seems to me not what is meant by sp.h either.
fithisux 1 days ago [-]
Null terminated strings have some merits but they should be a completely different data type like in Freebasic.
Sankozi 1 days ago [-]
Are there other merits than availability of literals in C?
It seems like one of the worst data structures ever - lookup complexity of a linked list with a expansion complexity of an array list with security problems added as a bonus.
af78 1 days ago [-]
One I can think of is simplicity. No need to worry about what the type of the string should be (size_t?) or where it should be stored. Just pass around a pointer. Pointers fit the size of a CPU register most of the time. Though in my opinion the drawbacks (O(N) performance, NUL forbidden etc.) outweigh this benefit we are stuck. Many kernel interfaces like open, getdents etc. assume NUL-terminated strings, therefore any low-level language or library has to support them.
nine_k 14 hours ago [-]
But (i32 length, byte[] data) is as complex as (byte[] data, '\0'), its two-parts anyway. Of course it allows potentially for very long strings at the cost of just a single byte spent as a terminator. Beside the rarity of such a case, the "space savings" might play a role on a PDP11, or on a Z80, but not on any of the modern architectures that need structures aligned to 32 or even 64 bit boundary. The efficiency and security costs far outweigh any savings is space or simplicity (heh) of processing.
Null-terminated strings are the other billion-dollar mistake, along with the original NULL.
fc417fc802 10 hours ago [-]
Arrays as glorified pointers were the mistake. Null terminated strings are a natural result of that design choice.
Null pointers however were not a mistake, despite how popular slandering them has become. A reasonable case can be made that any modern language should enforce null checks (and bound checks, and ...) or at the least provide them by default but that is neither here nor there as far as C is concerned.
nine_k 9 hours ago [-]
Tony Hoare himself called NULL a mistake. But the problem is not in the ability to set a pointer to a null value, of course. The problem is that all pointers are nullable, and there's no way to statically enforce their being non-null. I wonder how feasible data flow analysis would be in 1969 though.
boricj 1 days ago [-]
It's fine as a serialization/deserialization primitive for on-disk files, as long as the NULL character is invalid.
String tables in most object file formats work like that, a concatenated series of ASCIIZ strings. One byte of overhead (NUL), requires only an offset into one to address a string and you can share strings with common suffixes. It's a very compact layout.
diffuse_l 1 days ago [-]
Nothing prevents you from using a shared pool of strings that don't have null terminator. It can even be more efficient, since you don't have the null byte to handle at string end. Depending on the maximum string length you want to support, it doesn't even have to take more space.
boricj 24 hours ago [-]
How do you represent that pool of strings on-disk?
If we concatenate the raw strings together without the null terminator, either all string references will require a length on top of the offset (25% size penalty for a Elf32_Sym), or we'll need a separate descriptor table that stores string offsets and lengths to index into.
If we prepend strings with a length (let's say LEB128), we'll be at best tied with null-terminated strings because we'd have a byte for the length vs. a byte for the terminator. At worst, we'll have a longer string table because we'd need more than one byte to encode a long string length and we would lose the ability to share string suffixes.
Out of all the jank from a.out and COFF that was eliminated with ELF, that representation for the string table was kept (in fact, the only change was mandating a null byte at the beginning to have the offset 0 indicate a null string). It works fine since the 1970s and doesn't cause undue problems, as nothing prevents a parser to spit out std::string_view instead of const char* for the application code.
norir 9 hours ago [-]
When using null terminated strings, parsing can be branchless because you don't need bounds checks and can use a jump table indexed by the byte.
10 hours ago [-]
10 hours ago [-]
tdeck 1 days ago [-]
Hearing someone mention FreeBASIC really brings me back. It was the first language I ever used pointers in.
Retr0id 1 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.
Retr0id 1 days ago [-]
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.
AlotOfReading 1 days ago [-]
That seems to be a pretty consistent quality level for the entire library. Look at the implementations in sp_math, yikes.
12_throw_away 1 days ago [-]
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.
lifthrasiir 1 days ago [-]
As noted in my other comment though, some interesting decisions and interfaces do point to some degree of human intervention. I have recently written a similarly sized WebAssembly runner in C using agents (feel free to review: [1]) so I'm pretty certain that agents simply don't do that kind of things themselves...
feel free to pay me before asking me to review slop
lifthrasiir 15 hours ago [-]
Feel free to ignore if you don't feel so.
jcranmer 1 days ago [-]
"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.
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.
AlotOfReading 16 hours ago [-]
> 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.
I saw the note and ignored it because it's not actually a repackaging of HHM. It simply happens to define a few vaguely similar functions. It doesn't reuse the naming conventions (MulVec3f vs vec3_scale), it doesn't reuse the interface (see SP_MATH_IMPLEMENTATION), and it's missing genuinely useful bits like the matrix functions.
Moreover, the quality of what's been added is significantly worse. Look at sp_sys_expf:
f32 sp_sys_expf(f32 x) {
f32 result = 1.0f;
f32 term = 1.0f;
for (int i = 0; i < 20; i++) {
term *= x / (f32)(i + 1);
result += term;
}
return result;
}
I can't imagine a good reason why anyone (even an LLM) would ever write a 20th order taylor series for expf. A single FMA can improve on this and have capped relative error to boot, and that's not even a good way to do it. See what happens with your function at +-10 for comparison. At the f32 limit of 88, you achieve an honestly impressive 100% relative error.
Also, because sp_math doesn't use FMAs, your library isn't reproducible. Different compilers will produce different values. Reproducibility is a pretty nice property in games.
lifthrasiir 1 days ago [-]
I don't like that slopcode angle either, but unfortunately I have to say that you do have picked a wrong library to bundle. For example, it's almost likely that there is a correct (not just accurate enough, but correct) implementation of sqrt in your CPU because IEEE 754 mandates that. Unless you're doing softfloat you simply want to wire it via asm.
locknitpicker 1 days ago [-]
> 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"
monocasa 1 days ago [-]
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.
dboon 1 days ago [-]
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.
NetMageSCW 15 hours ago [-]
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?
dboon 14 hours ago [-]
The library provides a few basic allocators.
feelamee 23 hours ago [-]
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?
voakbasda 19 hours ago [-]
A buffered file may not be fully written to disk if the program exits suddenly. That’s generally a highly undesirable trait for a log file.
Retr0id 17 hours ago [-]
Line buffering solves this
gabriela_c 1 days ago [-]
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.
lelanthran 1 days ago [-]
> Jesus! Claude could've told this guy all these things.
Claude probably wrote it.
dboon 1 days ago [-]
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.
zamadatix 1 days ago [-]
> 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).
DeathArrow 1 days ago [-]
What about BSDs?
dboon 1 days ago [-]
I don't support non-macOS BSDs explicitly yet. Not for any reason of design, just hasn't been a priority.
whateverboat 1 days ago [-]
syscalls
yjftsjthsd-h 1 days ago [-]
That might work on FreeBSD but is pretty well guaranteed to break on OpenBSD. (Dunno about Net and Dragonfly) (I'd caution that treating the BSDs as a monolith is likely to end in errors; they're quite diverse.)
quuxplusone 1 days ago [-]
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)."
teo_zero 1 days ago [-]
Interesting project! I'm eagerly reading through it.
Probably I would have made different choices. For example, I'd rather have many modules that can be individually included, than one giant file.
Also from a purely aesthetic point of view, I would have opted for more readable function and type names: no sp_ prefix, recognizable names like dict istead of ht, vec instead of da, etc.
And I know there are compilers out there still stuck in the 90s, but I would have targeted C23, these days.
But that would be my highly opinionated library!
P.S. be aware that word frequency is not what the standard 'wc' does.
tialaramex 1 days ago [-]
> I’ve been working on fixing C by giving it a high quality, ultra portable standard library
If the only problem with C was that the stdlib is terrible that would be a very different situation.
There are much more fundamental problems with the language. Problems that are entirely understandable in K&R C but aren't acceptable half a century later. A "high quality" standard library can't fix these problems. In some cases it can paper over them though not others, and even then the actual problem wasn't fixed it's just not obvious with superficial examination any more.
First, the type system is crap. The array types don't work across function boundaries, there's no Empty type at all, you are provided with a user defined product type with names, but not one without names etc. There is no fat pointer type, slice reference, nothing like that.
Second, naming is also crap. There's no namespacing feature provided so you're left with the convention of picking a few letters as a prefix and hoping it doesn't overlap and yet is succinct enough to not be annoying.
Third, everything coerces, all the coercions you could want if you like coercions, and then ten times that many on top. Some people really like coercions, C will see them learn that actually they don't like them that much.
flohofwoe 1 days ago [-]
These are all just your personal preferences. Just use another language instead which better matches your taste, nobody forces you to use C and there are plenty of more opinionated alternatives.
FWIW, the standard library being stuck in the K&R era is an actual problem since it doesn't make use of more modern language features and some functions are downright footgun magnets, but nobody quite agrees what a modern stdlib should look like, so a stdlib2 probably will never happen.
rixed 6 hours ago [-]
HN is for technical discussions.
Even if the parent were only judging by his own personal preferences that would be relevant here.
Note: I've written a lot of C by profession and passion, yet I find parent's criticisms mostly valid. At least he did not mention rust ;)
Yesterday I would have agreed that C is a nice and simple language, today I believe it is a cursed one that we just happen to make work somehow.
tialaramex 20 hours ago [-]
Nah, I spent many years writing C for a living. That library isn't from the K&R era, it's from C89, the problems I'm talking about are much more fundamental.
Of course nobody forces me to use C, which is why I stopped writing C a few years ago.
cv5005 9 hours ago [-]
>There's no namespacing feature provided so you're left with the convention of picking a few letters as a prefix and hoping it doesn't overlap and yet is succinct enough to not be annoying.
I've been using C on a daily basis for 30+ years and name collisions has just never been a problem.
Granted, it might be due to lack of a package manager so micro dependencies ala import is_even is not a thing here, but still, in practice, no name collions occurs.
rixed 6 hours ago [-]
I've spent enough time wondering where a given short name was pulled from that i came to see lack of namespace as a quasi feature.
IshKebab 1 days ago [-]
Sure but it's definitely true that a significant part of the problem with C is that it's standard library is crap. So if you are forced to use C for some reason this could help.
Luker88 13 hours ago [-]
> C is valuable because it’s simple
This is funny to me because just today some friends gave me a link to a C quiz:
From which I gathered that it is a much more cursed language than I remembered. Maybe we all just got used to C and just happen to use a minimal subset.
The problems with C are not mainly with the standard library, but any effort to improve things should be lauded.
uecker 12 hours ago [-]
The quiz is not entirely accurate though. For example, pointers to the same address must compare equal. Other stuff is ancient and some of it was already removed in C23. Again other things are entirely irrelevant in practice and kept only for backwards compatibility or for specific technical reasons. In practice, C is very simple, at least compared to the alternatives.
Luker88 12 minutes ago [-]
There is an explanation for every one of them, the pointer equality one has code, too.
try it with `gcc -O1` and you will get a different result than with `gcc -O0`
skydhash 12 hours ago [-]
There's C the standard, and there's C the implementation (compiler & platform). The standard is nice to have on a bookshelf, but your main reference should be the implementation.
ndesaulniers 8 hours ago [-]
I spent a bit of time implementing parts of a C runtime for the brief tenure I had maintaining LLVM's libc. I quickly came the realization that a lot of the interfaces that made it all the way to standardization were trash. How the language is standardized is still comical to me. I agree with TFA that C style strings are trash.
gritzko 5 hours ago [-]
In this day and age, the value of C standard library is approaching zero. Is is basically an unfirm looping trajectory of CS thought left over 50 years. That amount of code can be recreated pretty quick today. So why bother. Unless it is a syscall or somehow specially treated by the compiler (memcpy etc), there is no special value in it.
skybrian 1 days ago [-]
My impression of the sample programs is that they're unreadably noisy, but maybe this would be a good compiler target if you're writing your own language?
dboon 1 days ago [-]
How would you write https://github.com/tspader/sp/blob/main/example/ls.c in your statically typed language of choice? To be fair, this is definitely the kindest example to my library, but one reason I felt this project was worth pursuing was that that example reads basically like a slightly worse TypeScript to me. In other words, quite nice for how low level the code really is.
skybrian 11 hours ago [-]
In a higher-level language, you wouldn't need to write code like this:
const sp_fs_entry_t* a = (const sp_fs_entry_t*)pa;
const sp_fs_entry_t* b = (const sp_fs_entry_t*)pb;
return sp_str_compare_alphabetical(a->name, b->name);
With the correct types declared for the parameter types instead of void pointers.
Or if you do need a cast, not having to write "sp_fs_entry_t*" twice in the same line because the local variable's type is inferred.
Maybe after reading C for a while, you don't see all the noise anymore?
tom_ 10 hours ago [-]
Because it's const void *pa, you don't need the cast. A void * pointer will convert to any other kind of pointer. Now you only need to mention the type once. (I forget the const-related rules, but since the consts match in this case I don't think it'll be relevant.)
504118318 1 days ago [-]
Just taking a quick look at the atomics section:
First, (on unix) it's wrapping pthread mutex. That's part of libc! (Technically it might not be libc.so, but it's still the standard library.)
Also, none of the atomics talk about the memory model. You don't _have_ to use the C11 memory model (Linux, for example, doesn't). But if you're not using the C11 memory model and letting the compiler insert fences for you, you definitely need to have fence instructions, yourself.
While C11 atomics do rely on libgcc, so do the __sync* functions that this library uses (see https://godbolt.org/z/bW1f7xGas) for an example.
Oops... apparently this is vibecoded. Welp, I just wasted ten minutes of my life reviewing slop that I'm not going to get back.
dboon 1 days ago [-]
Yes, unfortunately the threading primitives require libc. Ditto subprocesses. It's on my list.
But regarding: "Oops... apparently this is vibecoded. Welp, I just wasted ten minutes of my life reviewing slop that I'm not going to get back."
Do not talk to people like this. I don't care if you don't like the library, or if you found a flaw in it. I am a regular person who wrote this code for no other reason than I thought it would be good to exist. It's unbelievably rude to call it vibecoded slop, or a waste of your life, and it makes me sad that someone who would write an otherwise thoughtful comment would say something like that.
504118318 19 hours ago [-]
You're absolutely right. I should have expressed my late-night frustration more kindly.
spxtr 12 hours ago [-]
I thought this was a hilarious comment, for what it's worth.
dboon 18 hours ago [-]
No problem and thanks for the apology. Happens to the best of us. Regardless, thanks for the comment — I definitely didn’t mean to slip by the pthread stuff on a “well technically this isn’t libc.so”. It’s just code that’s pretty hard to get right and I haven’t had a chance to rewrite it!
9 hours ago [-]
10 hours ago [-]
rixed 5 hours ago [-]
Many of us are forced to spend time reviewing automatically generated code for work, so there is an understandable reluctance to be asked to do the same here as well.
9 hours ago [-]
RedComet 7 hours ago [-]
You didn't write it, though. And it is slop. If you had actually wrote it, you might learn from some of the criticism or be able to engage with it on an objective level.
12_throw_away 15 hours ago [-]
> It's unbelievably rude to call it vibecoded slop
Could you clarify how much of this code and blog post was written by an LLM?
feelamee 23 hours ago [-]
> Oops... apparently this is vibecoded. Welp, I just wasted ten minutes of my life reviewing slop that I'm not going to get back.
you interested in project and spent some time researching it, but stop when understand that it is vibe-coded (be it or not)?
Why care if it is interesting to you?
weregiraffe 4 hours ago [-]
"but stop when understand"
English language as she is spoke
xorvoid 10 hours ago [-]
They're just inventing another language here. That's fine if you want to do that. Just do THAT.
C is horribly and unfixably broken. We've known that for many decades. Just let it die already!
Let's move on.
schaefer 6 hours ago [-]
> C is horribly and unfixably broken. ... Let's move on.
I love that you are both making this argument and that you have a link to a boutique C compiler written in assembly on your home page.
While I'm commenting on your home page - I recognize that photo as being Red Rock. Possibly pine creek? but can I ask which route specifically?
Panzerschrek 1 days ago [-]
It's a disadvantage, that it's header-only. It needs to include <windows.h> and a bunch of other stuff, which slow-downs compilation. Splitting it into a couple of files (a header and an implementation) would be much better.
flohofwoe 1 days ago [-]
This normally isn't a problem since windows.h and other big system headers are usually only needed in the implementation part, not in the declaration part of the header (this is an STB-style header where the implementation is isolated in an `#ifdef IMPL` section).
Unfortunately though this particular header seems to include the system headers up in the declaration part of the header.
pjmlp 1 days ago [-]
We should have left C in the 90's already, but then FOSS happened,
"Using a language other than C is like using a non-standard feature: it will cause trouble for users. Even if GCC supports the other language, users may find it inconvenient to have to install the compiler for that other language in order to build your program. So please write in C."
C is the only language I found where it is possible to isolate yourself from the "AMAZING" ideas of programming language creators.
There is no language other than C and C++ that is mature enough that you can actually discard the implicit runtime stuff and still be able to code in the language. C++ is too complex in my opinion so I only get to use C as a minimal language.
Even if you look at a language like Zig. You have implicit error trace printing stuff that is inaccurate when using optimized builds, you get a very bad fuzz implementation that doesn't work properly, you get comptime reflection which will be insane in the hands of the people that are writing rust now. Also a bunch of features you would want to use to discard the runtime are not documented/stable.
You can't even use Odin without libc as far as I can understand.
Hare doesn't even have inline asm.
Contrast with using C with clang/gcc where you can do '-nostdinc' '-nostdlib', then implement memcpy etc. and you can do w/e you want after that.
Rust as a nother example is trash for doing low level projects without pulling the 10billion lines of code that comes with using rust like libc/stdlib/binding libaries etc. etc.
You can use libraries that other people built in Rust but doing it yourself takes much more time than doing it in a language like C or Zig.
Another thing is, C is easy to implement. Implementing Rust/C++/Zig or any of the other languages is basically impossible in comparison.
Also I found that C is the only language that you can go into a very big project and open a random file and roughly understand what is going on. This is not possible in any of these other laguages other than Zig and I suspect it will get very bad in Zig when(if) the lower skill level people that are currently writing Rust start moving to writing Zig.
Plenty to chose from since 1958 with JOVIAL, naturally there is this urban myth of C being the very first systems language.
mjevans 1 days ago [-]
For reference, Dialup Internet (E.G. ~2-3KByte/sec transfer) was NOT uncommon even into the early 2000s.
In 1994 even dialup internet connections were rare and most software distribution occurred by floppy disk (encased in hardshell plastic). _storage_ space was also at a major premium with internal hard disk size indexed in CHS rather than LBA and new (rarely seen by most end consumers) models barely passing 1GB in capacity. https://en.wikipedia.org/wiki/Seagate_Barracuda
Even in the early 'dot com' era as DSL and early cable modem became common downloading software updates could still be painful, though far less so than hours or days on dialup.
troad 4 hours ago [-]
It's been interesting seeing the retrospective flattening of computer history. I suppose it's inevitable over time. I wonder how bad it will get.
"In the 1990s, 'hackers' would 'dial up' their flip phones to local BBSes (called 'phreaking'), where they played and exchanged small Flash games (the 'demo scene')." /s
yjftsjthsd-h 1 days ago [-]
That sounds like GNU reacted to the problem rather than causing it.
I'm inclined to agree with you that paths can be arbitrarily long as a matter of principle. However in practice I believe PATH_MAX on most distros defaults to 4096 so if you're going to insist on a hard upper limit it's at least within reason.
dboon 1 days ago [-]
Can you show me a realistic case with a longer path?
magicalhippo 3 hours ago [-]
I guess the c8 means you don't plan on supporting Unicode on Windows? If you do, there's the 32768 "character" max path which since Win10 is opt-in without jumping through hoops.
I have email backups from a provider where filenames are extremely long.
Panzerschrek 1 days ago [-]
How does this library work in programs with parts still requiring libc?
How does it deal with code executing before main? Libc does a bunch of necessary stuff, like calling initializers for global variables.
dboon 1 days ago [-]
If your code depends on a bunch of initialization from libc, then you should continue to link to and use libc. sp.h can coexist with libc just fine; if you link to it, the library makes sure to conform where it needs to (e.g. not stomping on the register that holds the TLS base pointer).
What sp.h does not do is reimplement all of libc's initialization code. If you want to build a freestanding binary, there are a few utilities in there for defining a _start so the loader can actually jump to your code. But it's not, and isn't meant to be, a libc replacement in this sense.
1 days ago [-]
WalterBright 13 hours ago [-]
Whenever I review C code, I always look for the string functions. About 90% of the time, I find a bug in it. The bug is always about forgetting to account for the terminating 0 byte.
The functions strncpy, snprintf, strncat, are fountains of bugs.
WalterBright 8 hours ago [-]
P.S. For these reasons I have abandoned the "n" functions, such as strncpy, strncat, etc.
I still use snprintf, though, because it is so darned useful. But I wrap it up in another function after carefully ensuring it is called correctly.
Kab1r 1 days ago [-]
Best library name.
dboon 1 days ago [-]
Thank you, but why's that?
ta8903 21 hours ago [-]
"SPH" stands for small penis humiliation in certain corners of the internet.
JSR_FDED 1 days ago [-]
I love how hyper-opinionated this is.
dboon 1 days ago [-]
Thank you!
smitty1e 1 days ago [-]
Family saying: "It ain't bragging if you can do it."
When one is competent to work at this level, strong opinions are in order.
Their correctness is something I cannot gage. I'm barely competent to follow the conversation.
rmunn 1 days ago [-]
Considering the first thing I saw in the thread was https://news.ycombinator.com/item?id=48244891 where the values returned from sp's sine function was compared to the correct values, I'm going to take any such opinions with a few grains of salt. Because the correct sine for the number they tested (31337 radians) is 0.3772 (0.3771522646 according to my calculator), sp's implementation returned 0.4385. That's not even close to right.
CyberDildonics 14 hours ago [-]
I was promised by the title that it is "high quality"
JSR_FDED 1 days ago [-]
It’s still alpha
feelamee 23 hours ago [-]
How do they all know that it is vibe-coded?
I missed the meeting where they were handing out vibe-code-detectors?
Please, describe..
P.S. sad to see that HN becomes a witch hunting place
dboon 18 hours ago [-]
Yeah, AI has done a number to this place
nektro 1 days ago [-]
not one mention of Zig on the whole page?
dboon 1 days ago [-]
I had half of a manifesto about how C programmers should be embarrassed on account of Zig but I ended up paring it down to be more focused on what the library is plainly.
Zig is obviously incredible and this library would not exist without it being the standard bearer for systems programming in many ways
Thanks for reading and thanks for the link. I'll read anything DJB wrote.
Onavo 15 hours ago [-]
Bun and Anthropic wants to know your location.
KnuthIsGod 1 days ago [-]
"The library’s stance, to put it simply, that the juice ain’t worth the squeeze when it comes to low level, compute-bound performance.
Designing software and data structures for performance against unknown use cases on unknown hardware is extremely difficult and the resulting code is much more complicated. Even then, it’s often better to use code written against your actual use case and hardware when performance is that critical.
Things that are off the table might be:
SIMD
A highly optimized hash table rewrite
Figuring out where inlining or LIKELY causes the compiler to produce better code."
LOL...
Classic vibe coder.
TZubiri 1 days ago [-]
> Every language that depends on third party libraries, like js and python, is getting massively infected with supply chain worms
> Only couple of languages not affected are those that don't have a culture of downloading third party code, like C and C++
> Ex js and python developer publishes a 'library'
> Library is vibe coded
> Published on github amidst GitHub being hit by supply chain attacks, had their source code leaked.
The timing is terrible for starters, and I don't trust the vibe coded code at all. Imagine a pandemic and the cities are on fire, and you arrive to a rural town asking to kiss people.
redlewel 1 days ago [-]
Thanks for this comment, I was about to bookmark the repo for later you saved me the time.
dboon 1 days ago [-]
Man, this place has become strange. This person has no idea who I am. But thanks for commenting on my 'library' nonetheless!
dzhar11 14 hours ago [-]
[flagged]
NelsonMinar 12 hours ago [-]
[dead]
KnuthIsGod 1 days ago [-]
Wonderful !
Yet another slop coded library.
What could possibly go wrong...
charcircuit 1 days ago [-]
I do not want to include and compile a standard library for every file that includes it.
Why do standard library headers always have to be insane?
dboon 1 days ago [-]
Have you considered compiling it into a binary of your choice? It works perfectly well as a traditional library. The only cost you pay is re-parsing the header part once per TU. Because C is so simple, this is virtually free. In any case, calling it insane makes me feel disrespected and I would prefer if you didn't do that.
charcircuit 12 hours ago [-]
>The only cost you pay is re-parsing the header part once per TU.
You are not just parsing it. The header includes the implementation too which will get compiled. Then the linker has to do extra work in order to deduplicate all of this extra code that was made.
>Because C is so simple, this is virtually free.
This is not virtually free needing to compile a standard library for every file in one's project.
>In any case, calling it insane makes me feel disrespected
I would recommend you not take it personally. Fortunately, for better or worse software ends up being pretty robust so it can tolerate a lot. Even if you have to recompile the same standard library hundreds of times, it will eventually compile.
> Be extremely portable
> sp.h is written in C99, and it compiles against any compiler and libc imaginable. It works on Linux, on Windows, on macOS. It works under a WASM host. It works in the browser. It works with MSVC, and MinGW, it works with or without libc, or with weird ones like Cosmopolitan. It works with the big compilers and it works with TCC.
> And, best of all, it does all all of that because it’s small, not because it’s big.
vs
> Non-goals
> Obscure architectures and OSes
> I write code for x86_64 and aarch64. WASM is becoming more important, but is still secondary to native targets. I don’t care to bloat the library to support a tiny fraction of use cases.
> That being said, if you’re interested in using the library on an unsupported platform, I’m more than happy to help, and if we can make the patch reasonable, to merge it.
Those are contradictory. Either the code is extremely portable, or it can't support "obscure" platforms, but not both.
And he's already hit the hard targets. Many obscure OS's are generally UNIX like and should be easy ports. Many obscure arch's usually are running Linux and should be easy ports.
It's stated as a non-goal simply because it's not the most valuable thing I can do with my time. My fundamental stance is that writing new Windows or Linux or macOS or WASM programs in C is a good idea, and those are the programs that I write, so that's where my focus is. But if someone would like to come along and write the ~30 syscalls needed to port the library to a new platform, or even register any interest in such, I'd be happy to look into it at that point.
I see no contradiction in the desire to support x64 (because it would be ridiculous not to), ARM, and likely RISC-V, but not the venerable but now-fringe architectures like MIPS or Sparc or 68040 or even x86.
For those not in the know, Microchip still produces MIPS microcontrollers:
https://www.microchip.com/en-us/products/microcontrollers/32...
[1]: https://www.silabs.com/mcu/8-bit-microcontrollers
[2]: https://www.stcmicro.com/stc/stc32g.html
It sounds like it has a goal of being "extremely portable" across compilers, (although I'm curious how many compulers it is actually tested against) but only somewhat portable across architectures and operatings systems, just hitting the most popular ones.
I think it's perfectly valid to call code 'extremely portable' without supporting every special snowflake architecture. There's a spectrum from assumptions that hold on everything that isn't some esoteric joke architecture or archaeology to something that I would probably consider required for 'extremely portable'.
I would personally consider something that failed to support anything on this list above big endian as still being extremely portable: you'll build for any serious modern architecture that isn't a DSP.
ARM's done a good job of making it so that you can't assume the traditional x86 assumptions of being able to access any pointer unaligned or having sequentially consistent semantics on memory ordering (with the help of compilers getting better at reordering resulting in you needing to have proper semantics on x86 as well).Supporting obscure platforms is what makes portability "extreme", though.
If you have to write extensive patches to actually port the software, then it’s only “portable” in the same sense that any software can be ported with enough effort. Ie “Foo is portable. You just have to write a write a whole new kernel to port it”
> “Portable”, in the context of how it was used, generally refers to software using platform agnostic idioms.
If only the Foo kernel must be rewritten in order to port Foo, but that kernel is 75% of Foo, then I would say Foo is not portable. If the kernel is 0.1% of Foo, then I would say that it is: 99.9% of the code base depends on the abstractions in the Foo kernel rather than platform features.
“in the context of how it was used,”
This isn’t some meta conversation about how to write portable software. This is a conversation specific to what the author had written about their project.
The number might just be zero - did anyone check if this compiles? I am trying to track down where the function `sp_mem_allocator_alloc_type` is defined (used in 3x places) but it doesn't appear in the GH search results.
I'm not going to clone and build this (too dangerous).
A quick glance at the source on github and here you go: https://github.com/tspader/sp/blob/e64697aa649907ce3357a7dd0...
`sp_mem_allocator_alloc_type ` is going through a couple of macro resolutions which ends up at `sp_mem_allocator_alloc`
> I'm not going to clone and build this (too dangerous).
Your computer won't explode just from downloading and compiling some C code, don't worry ;)
The github repo builds and the examples run just fine on macOS by just running `make` in the project directory, although with one warning:
Apple still do security updates on Sequoia.
This is the first time I ever saw anyone dismissing the risk of downloading and running stuff off the internet.
"Don't worry".
I have no idea what's in the Makefile, and I'm not going to review it just so to try and figure out where a function is defined :-/
Just create a disposable isolated environment, like VM or container, and do it inside? And, yes, does compile.
for embedded defs not against portable alternatives like this tho.
however ops post sure gets off on the wrong foot by saying this is "fixing C". the hubris of mankind on full display, yet again
Aside from various different variations of ARM, I’ve worked several variations of x86 and AMD64, SPARC, PowerPC, MIPS and others I’ve likely forgotten. Not to mention a few 8-bit CPUs too, but those there more than 20 years ago and not really fair to discuss here.
If you can't be bothered to look at a Makefile (or ask an AI to look at the Makefile), you are almost certain to be more trouble than any possible benefit you will bring.
Especially in the realm of open source, I'm becoming increasingly comfortable with "If you can't be bothered to jump through even the most minimal of hoops, please get lost."
In other words, you hit the nail on the head. Anyone who acts this way can get fucked! We'll be having a good time and making friends without them
[1] https://wtf-8.codeberg.page/
Wait, is a compound literal an l-value in that sense (as opposed to, just being able to take its reference)?! Take a look at the C99 standard Oh my, it indeed is (C99 §6.5.2.5 p5). Good to know!I have a wtf.c from 10+ years ago when I was re-implementing Windows-style Unicode handling for some project. You keep running into various quirks, which accumulate and you inevitably arrive at your WTF moment. So WTF as name comes up naturally, no special wit required.
i doubt many people will go through the whole code themselves or do this stuff to determine if claims are true and/or its worth to port something to this or start learning it.
people spend a lot of time getting familiar with libraries in order to be able to use them properly.. If you help them a long that path more it will be more inviting to try your code.
(that being said i think people talking about fixing C likely dont realise that people just quietly roll their own libs like this..not to fix C but because that is what C programming is.)
Pointer/length is not just for strings - but for all arrays.
See my proposal:
https://www.digitalmars.com/articles/C-biggest-mistake.html
Could I pick your brain a little more on the design? I'm spader at spader.zone; if you have time, drop me an email. I promise not to take too much of your time and I'd love to hear from you.
This can get you started:
https://dlang.org/spec/arrays.html
Strings (and arrays) being length/ptr is a freaking enormous win, in simplicity, performance, and overflow bug elimination.
One of D's secret features is that string literals still have a 0 appended to them, even though the length of the string does not include the 0. This makes it super slick to call C functions, like printf, using a string literal for the format string.
I'm baffled why C spends its energy doing things like normalized Unicode identifiers (an abomination) instead of something incredibly useful like length/ptr arrays.
https://github.com/gritzko/libabc
In this day and age, the top problem is Claude bringing lots and lots of bad C into the code base. Takes a weekend to clear the week's mess.
https://github.com/gritzko/beagle
In a cleared codebase though all the usual C memory bugs are virtually non-existant. When did I see core dump last time? I do not remember. Thus feel no urge to use Zig or Rust.
Interestingly, I rarely make a memory bug these days. Too much experience, I've just learned not to make them.
But I still prefer to use language features that make it easier to not make such errors.
It hasn't been much of an issue with decades of D code.
I had a hard time reading the wc code in the article. First I had to go to the GitHub to understand that "da" stands for dynamic array, and then understand that what the author calls wc is not at all the wc linux commands, which by default gives you the number of lines, words, and characters in a file, not the count of occurrences of each word in the file, which is what the proposed code does.
Also, since I had to read the GitHub README, another remark: it says that sp_io uses pthreads rather than fork and exec. Both of those approach (but especially pthreads) are contradictory to the explicit goals of programming against lowest level interfaces. I believe the lowest level syscall is clone3 [1], which gives you more fine grained control on what is shared between the parent and child processes, allowing to implement fork or threads.
[1] https://manpages.debian.org/trixie/manpages-dev/clone3.2.en....
> Program directly against syscalls
It's the very first one of the listed principles. In the paragraph after this title it even says it "must" be the case in italic to insist on it, and there's a footnote to define what they mean, which is very clear in that pthreads should be out according to this principle.
I agree that pointer and length is better than null-terminated strings (although it is difficult in C, and as they mention you will have to use a macro (or some additional functions) to work this in C).
Making the C standard library directly against syscalls is also a good idea, although in some cases you might have an implementation that needs to not do this for some reason, generally it is better for the standard library directly against syscalls.
FILE object is sometimes useful especially if you have functions such as fopencookie and open_memstream; but it might be useful (although probably not with C) to be able to optimize parts of a program that only use a single implementation of the FILE interface (or a subset of its functions, e.g. that does not use seeking).
It seems like one of the worst data structures ever - lookup complexity of a linked list with a expansion complexity of an array list with security problems added as a bonus.
Null-terminated strings are the other billion-dollar mistake, along with the original NULL.
Null pointers however were not a mistake, despite how popular slandering them has become. A reasonable case can be made that any modern language should enforce null checks (and bound checks, and ...) or at the least provide them by default but that is neither here nor there as far as C is concerned.
String tables in most object file formats work like that, a concatenated series of ASCIIZ strings. One byte of overhead (NUL), requires only an offset into one to address a string and you can share strings with common suffixes. It's a very compact layout.
If we concatenate the raw strings together without the null terminator, either all string references will require a length on top of the offset (25% size penalty for a Elf32_Sym), or we'll need a separate descriptor table that stores string offsets and lengths to index into.
If we prepend strings with a length (let's say LEB128), we'll be at best tied with null-terminated strings because we'd have a byte for the length vs. a byte for the terminator. At worst, we'll have a longer string table because we'd need more than one byte to encode a long string length and we would lose the ability to share string suffixes.
Out of all the jank from a.out and COFF that was eliminated with ELF, that representation for the string table was kept (in fact, the only change was mandating a null byte at the beginning to have the offset 0 indicate a null string). It works fine since the 1970s and doesn't cause undue problems, as nothing prevents a parser to spit out std::string_view instead of const char* for the application code.
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.
It looks like sp_log's string formatting is entirely unbuffered which results in lots of tiny write syscalls.
[1] https://github.com/lifthrasiir/wah/
feel free to pay me before asking me to review slop
... ... ... 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.
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.
I saw the note and ignored it because it's not actually a repackaging of HHM. It simply happens to define a few vaguely similar functions. It doesn't reuse the naming conventions (MulVec3f vs vec3_scale), it doesn't reuse the interface (see SP_MATH_IMPLEMENTATION), and it's missing genuinely useful bits like the matrix functions.
Moreover, the quality of what's been added is significantly worse. Look at sp_sys_expf:
I can't imagine a good reason why anyone (even an LLM) would ever write a 20th order taylor series for expf. A single FMA can improve on this and have capped relative error to boot, and that's not even a good way to do it. See what happens with your function at +-10 for comparison. At the f32 limit of 88, you achieve an honestly impressive 100% relative error.Also, because sp_math doesn't use FMAs, your library isn't reproducible. Different compilers will produce different values. Reproducibility is a pretty nice property in games.
That does spin the meaning of "Sp.h is the standard library that C deserves"
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.
Why is the unbuffered default? Is there any thoughts on this?
Claude probably wrote it.
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.
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).
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)."
Probably I would have made different choices. For example, I'd rather have many modules that can be individually included, than one giant file.
Also from a purely aesthetic point of view, I would have opted for more readable function and type names: no sp_ prefix, recognizable names like dict istead of ht, vec instead of da, etc.
And I know there are compilers out there still stuck in the 90s, but I would have targeted C23, these days.
But that would be my highly opinionated library!
P.S. be aware that word frequency is not what the standard 'wc' does.
If the only problem with C was that the stdlib is terrible that would be a very different situation.
There are much more fundamental problems with the language. Problems that are entirely understandable in K&R C but aren't acceptable half a century later. A "high quality" standard library can't fix these problems. In some cases it can paper over them though not others, and even then the actual problem wasn't fixed it's just not obvious with superficial examination any more.
First, the type system is crap. The array types don't work across function boundaries, there's no Empty type at all, you are provided with a user defined product type with names, but not one without names etc. There is no fat pointer type, slice reference, nothing like that.
Second, naming is also crap. There's no namespacing feature provided so you're left with the convention of picking a few letters as a prefix and hoping it doesn't overlap and yet is succinct enough to not be annoying.
Third, everything coerces, all the coercions you could want if you like coercions, and then ten times that many on top. Some people really like coercions, C will see them learn that actually they don't like them that much.
FWIW, the standard library being stuck in the K&R era is an actual problem since it doesn't make use of more modern language features and some functions are downright footgun magnets, but nobody quite agrees what a modern stdlib should look like, so a stdlib2 probably will never happen.
Note: I've written a lot of C by profession and passion, yet I find parent's criticisms mostly valid. At least he did not mention rust ;)
Yesterday I would have agreed that C is a nice and simple language, today I believe it is a cursed one that we just happen to make work somehow.
Of course nobody forces me to use C, which is why I stopped writing C a few years ago.
I've been using C on a daily basis for 30+ years and name collisions has just never been a problem.
Granted, it might be due to lack of a package manager so micro dependencies ala import is_even is not a thing here, but still, in practice, no name collions occurs.
This is funny to me because just today some friends gave me a link to a C quiz:
https://stefansf.de/c-quiz/
From which I gathered that it is a much more cursed language than I remembered. Maybe we all just got used to C and just happen to use a minimal subset.
The problems with C are not mainly with the standard library, but any effort to improve things should be lauded.
try it with `gcc -O1` and you will get a different result than with `gcc -O0`
Or if you do need a cast, not having to write "sp_fs_entry_t*" twice in the same line because the local variable's type is inferred.
Maybe after reading C for a while, you don't see all the noise anymore?
First, (on unix) it's wrapping pthread mutex. That's part of libc! (Technically it might not be libc.so, but it's still the standard library.)
Also, none of the atomics talk about the memory model. You don't _have_ to use the C11 memory model (Linux, for example, doesn't). But if you're not using the C11 memory model and letting the compiler insert fences for you, you definitely need to have fence instructions, yourself.
While C11 atomics do rely on libgcc, so do the __sync* functions that this library uses (see https://godbolt.org/z/bW1f7xGas) for an example.
Oops... apparently this is vibecoded. Welp, I just wasted ten minutes of my life reviewing slop that I'm not going to get back.
But regarding: "Oops... apparently this is vibecoded. Welp, I just wasted ten minutes of my life reviewing slop that I'm not going to get back."
Do not talk to people like this. I don't care if you don't like the library, or if you found a flaw in it. I am a regular person who wrote this code for no other reason than I thought it would be good to exist. It's unbelievably rude to call it vibecoded slop, or a waste of your life, and it makes me sad that someone who would write an otherwise thoughtful comment would say something like that.
Could you clarify how much of this code and blog post was written by an LLM?
you interested in project and spent some time researching it, but stop when understand that it is vibe-coded (be it or not)?
Why care if it is interesting to you?
English language as she is spoke
C is horribly and unfixably broken. We've known that for many decades. Just let it die already!
Let's move on.
I love that you are both making this argument and that you have a link to a boutique C compiler written in assembly on your home page.
While I'm commenting on your home page - I recognize that photo as being Red Rock. Possibly pine creek? but can I ask which route specifically?
Unfortunately though this particular header seems to include the system headers up in the declaration part of the header.
"Using a language other than C is like using a non-standard feature: it will cause trouble for users. Even if GCC supports the other language, users may find it inconvenient to have to install the compiler for that other language in order to build your program. So please write in C."
The GNU Coding Standard in 1994, http://web.mit.edu/gnu/doc/html/standards_7.html#SEC12
There is no language other than C and C++ that is mature enough that you can actually discard the implicit runtime stuff and still be able to code in the language. C++ is too complex in my opinion so I only get to use C as a minimal language.
Even if you look at a language like Zig. You have implicit error trace printing stuff that is inaccurate when using optimized builds, you get a very bad fuzz implementation that doesn't work properly, you get comptime reflection which will be insane in the hands of the people that are writing rust now. Also a bunch of features you would want to use to discard the runtime are not documented/stable.
You can't even use Odin without libc as far as I can understand.
Hare doesn't even have inline asm.
Contrast with using C with clang/gcc where you can do '-nostdinc' '-nostdlib', then implement memcpy etc. and you can do w/e you want after that.
Rust as a nother example is trash for doing low level projects without pulling the 10billion lines of code that comes with using rust like libc/stdlib/binding libaries etc. etc.
You can use libraries that other people built in Rust but doing it yourself takes much more time than doing it in a language like C or Zig.
Another thing is, C is easy to implement. Implementing Rust/C++/Zig or any of the other languages is basically impossible in comparison.
Also I found that C is the only language that you can go into a very big project and open a random file and roughly understand what is going on. This is not possible in any of these other laguages other than Zig and I suspect it will get very bad in Zig when(if) the lower skill level people that are currently writing Rust start moving to writing Zig.
https://stefansf.de/c-quiz/
In 1994 even dialup internet connections were rare and most software distribution occurred by floppy disk (encased in hardshell plastic). _storage_ space was also at a major premium with internal hard disk size indexed in CHS rather than LBA and new (rarely seen by most end consumers) models barely passing 1GB in capacity. https://en.wikipedia.org/wiki/Seagate_Barracuda
Even in the early 'dot com' era as DSL and early cable modem became common downloading software updates could still be painful, though far less so than hours or days on dialup.
"In the 1990s, 'hackers' would 'dial up' their flip phones to local BBSes (called 'phreaking'), where they played and exchanged small Flash games (the 'demo scene')." /s
https://learn.microsoft.com/en-us/windows/win32/fileio/maxim...
How does it deal with code executing before main? Libc does a bunch of necessary stuff, like calling initializers for global variables.
What sp.h does not do is reimplement all of libc's initialization code. If you want to build a freestanding binary, there are a few utilities in there for defining a _start so the loader can actually jump to your code. But it's not, and isn't meant to be, a libc replacement in this sense.
The functions strncpy, snprintf, strncat, are fountains of bugs.
I still use snprintf, though, because it is so darned useful. But I wrap it up in another function after carefully ensuring it is called correctly.
When one is competent to work at this level, strong opinions are in order.
Their correctness is something I cannot gage. I'm barely competent to follow the conversation.
Please, describe..
P.S. sad to see that HN becomes a witch hunting place
Zig is obviously incredible and this library would not exist without it being the standard bearer for systems programming in many ways
Designing software and data structures for performance against unknown use cases on unknown hardware is extremely difficult and the resulting code is much more complicated. Even then, it’s often better to use code written against your actual use case and hardware when performance is that critical.
Things that are off the table might be:
SIMD A highly optimized hash table rewrite Figuring out where inlining or LIKELY causes the compiler to produce better code."
LOL...
Classic vibe coder.
> Only couple of languages not affected are those that don't have a culture of downloading third party code, like C and C++
> Ex js and python developer publishes a 'library'
> Library is vibe coded
> Published on github amidst GitHub being hit by supply chain attacks, had their source code leaked.
The timing is terrible for starters, and I don't trust the vibe coded code at all. Imagine a pandemic and the cities are on fire, and you arrive to a rural town asking to kiss people.
Yet another slop coded library.
What could possibly go wrong...
Why do standard library headers always have to be insane?
You are not just parsing it. The header includes the implementation too which will get compiled. Then the linker has to do extra work in order to deduplicate all of this extra code that was made.
>Because C is so simple, this is virtually free.
This is not virtually free needing to compile a standard library for every file in one's project.
>In any case, calling it insane makes me feel disrespected
I would recommend you not take it personally. Fortunately, for better or worse software ends up being pretty robust so it can tolerate a lot. Even if you have to recompile the same standard library hundreds of times, it will eventually compile.