From Christopher Wellons to ~skeeto/public-inbox
You've almost got it. You just need to fix three mistakes in two lines of newthread. The first two you can reason about by dimensional analysis. 1. "count" should be the number of stack_head objects that fit in the stack, not thread_stack objects. I suspect this is a typo. 2. Since "count" is a number of stack_head objects, pointer arithmetic involving it should be in terms of "stack_head *" pointers, not raw bytes ("void *"). The stack high end is the last element of a "stack_head array" that forms the tack. (Note: And *not* "one past the end." You did not make this mistake, but I want to highlight it. The last stack_head element is just beyond the stack as seen by the thread, serving as a kind of metadata much like argc+argv+envp+aux on the main thread stack.)
From Christopher Wellons to ~skeeto/public-inbox
I'm glad to see that you've given this a shot! You're quite close, but there area a few subtle details to sort out. First, unlike clone(), clone3() takes a pointer to the low end of the stack, which is mentioned on the man page. The original clone() does not know the stack size, so it takes a pointer to the high end, as otherwise the kernel could not find it. That simplified my demo, too. So you must modify newstack() to return both ends of the stack, the high end for you to populate and the low end to give to the kernel. I defined this to be returned from newthread(): typedef struct { void *tail; stack_head *head;
From Christopher Wellons to ~skeeto/public-inbox
Thanks for the links, Ljubomir. I knew about Arabesque of course, but the Alan Zintz tutorial is new to me. When choosing a recommendation I look for a text that begins with the fundamentals followed by an opinionated (with good taste) guide of how to apply them. This old tutorial does not quite fit, but my usual recommendation, Practical Vim (Drew Neil), does exactly that. In another direction is the Vim manual, which is thorough and covers every last detail, but lacks an opinion on how to use all that power.
From Christopher Wellons to ~skeeto/public-inbox
> How did you figure this out? (I just remembered I forgot to respond to this.) I wrote my first attempt in C++ using operator overloading in order to have nice ergonomics while I got the hang of relative pointers, and GCC's optimization punished me for misuse of char* pointer arithmetic: https://lists.sr.ht/~skeeto/public-inbox/%3C20230902232504.hcihm5fdw6k7kejq%40gen2.localdomain%3E#%3C20230911223809.2rvs357olqfzt4d2@nullprogram.com%3E While that was mostly a result of being cheeky in my test, using local variables rather than a proper arena, I expect it's necessary even with arenas when also using the "malloc" function attribute. It specifically declares that returned pointers are unrelated, and therefore cannot be compared using char*. So uintptr_t is really the only safe way to do it.
From Christopher Wellons to ~skeeto/public-inbox
Great question, Angel! That part isn't so simple and pretty, which is why I had suggested an intrusive linked list. It turns it a hash trie into a "linked hash map" where the list order is independent of the hash trie, and iteration is as simple as traversing that list. It could track, say, insertion order, or you could sort it — mergesort goes well with linked lists — all without disrupting the underlying hash trie. If you'd like to see hash trie iteration, I wrote an example iterator over an integer hash set (iter, newiter, iternext): https://gist.github.com/skeeto/e416b3b07bf63792db91a5139526612e
From Christopher Wellons to ~skeeto/public-inbox
Thanks for the video links, Flo. I hadn't yet come across the first, and
there's a shocking amount of overlap with my own thoughts and writing the
past year — even down to signed sizes. The slide "API Design: Modern C" at
41:20 is a good summary, which emphasizes that "modern C" is more about
style (esp. ZII, allocator aware) and attitude than new language features
(emphasized _too_ much in the second video). That is, these techniques
have all been possible for a quarter century. I'll reference this video
myself in the future.
> What do you think of this addition and do you think it is useful?
At least for me, not useful, and the ugly syntax keeps me from using it
even casually. With one exception, I just don't have trouble passing null
pointers to functions that don't accept them. As Lucas Sas notes in the
From Christopher Wellons to ~skeeto/public-inbox
> not entirely clear on the entire "alternating" business Yeah, that's one of the major downsides. If, of all people, _you're_ not clear on it, then the typical person using it, or even just reading the code, will probably be unable to reason whether or not its use is correct. (Difficulty reasoning about code that should be straightforward and simple is one of my chief complaints about modern C++ after all.) > The "fast realloc" trick Dennis shared on the list also wouldn't work > for the same reason. Yup, definitely another cost. Tangent: In preparation for a future article, I've revisited an old Go
From Christopher Wellons to ~skeeto/public-inbox
The other day I had a brief discussion with u/treefidgety, who did not like the idea of passing two arenas down the call stack any time there was a function that would need both. It got me thinking about how this might be avoided, and I came up with an idea of using one arena for both roles. The core concept: An arena has two "ends" and we could use the other end for scratch allocations. Since callees/callers alternate arenas, which end is which depends on the position in the call stack. When passing an arena, the ends usually swap, and we can do this while keeping the two-pointer arena format. I call the ends "persist" and "scratch". Allocations that survive local scope are made from the former, and scoped allocations from the latter. typedef struct {
From Christopher Wellons to ~skeeto/public-inbox
I don't know the history, but C and C++ generally regard null pointers as super special — very much not a valid pointer to a zero-sized object. I hadn't thought about it with qsort before, but other cases include fwrite and fread. C++ takes it even further: When the standard says a function accepts an "array" (e.g. std::ostream::write) you're not even allowed to use zero as a count/nmemb with a non-null pointer because arrays cannot be zero length. It's nuts, like they were trying to make gotchas. Though whatever historical circumstance established this should not matter today, GCC uses it as an optimization hint. If a pointer is passed to one of these functions, then it's assumed not null, and that hint propagates forward potentially removing null pointer checks and such. A half workaround is to write your own copy/move/set functions with your
From Christopher Wellons to ~skeeto/public-inbox
Thanks, Dmitrii! Yeah, I've used CSS Reset myself in the past. A cool, useful project.