The code size overhead of a varargs function is much more than I had
expected. Since GCC 5 didn't inline the function anyway and it is only
used in debug mode, there is no point keeping multiple copies of that
code around.
These two logging blocks are massive enough to disturb the reading flow
of the remaining code.
Even without these two blocks, ApplyModifiers is still 250 lines long,
which is quite much.
Just because these file descriptors have to be in an array when they are
created is not reason enough to keep this array and a few access macros
in the Job struct. It's better to have separate fields, as they can be
documented independently.
The comment above a struct is supposed to give a high-level overview.
The previous comment didn't do that but instead listed the struct fields
in declaration order in a numbered list, which was not helpful.
Ideally the condition for allocating more memory would have been
(old_len + 2 > bp->cap) since that's the actually intended wording. But
GCC 5 neglected to generate good code for that on x86_64, so be it.
The tricky detail here is that the current node from the iteration is
removed if it is no longer needed.
The Lst_FindDatum that has been removed was both inefficient and
misleading since it could never return null, yet there was a null check
for it. The callback API from Lst_ForEachUntil would have required to
define a custom struct for passing this parameter to the callback
function, in addition to the parent node.
Inlining the whole Lst_ForEach and passing the list node as a parameter
is much more obvious.
This protects against very simple memory allocation bugs such as
migrating Lst_ForEachUntil to Lst_ForEach without remembering that
Lst_ForEachUntil can handle the situation where the current list node is
removed from the list, but Lst_ForEach cannot. This happens in
Make_ExpandUse, for example.
Before August 2020, the Lst library passed null pointers through. This
was a confusing design pattern that has been removed since. Now the Lst
functions fail fast on null pointers.
The 'targets' list is one of the few places where there is indeed an
optional list that may sometimes be null. Back then, there was not
enough inline documentation to understand when the targets list was null
and when it wasn't.
Now that the documentation is there, the redundant and thereby
misleading null checks are no longer useful.
Printing a node does not modify the structure of the node, therefore the
additional housekeeping of Lst_ForEachUntil is not needed here.
Inlining the callback function also removes a lot of pointer stuff that
is more difficult to read than necessary.
This avoids the extra local function and a few conversions to void
pointers, to gain additional type safety.
The code in Compat_RunCommand does not modify gn->commands structurally,
therefore it does not need the extra complexity of Lst_ForEachUntil. It
does have access to a list node to exactly this list. This list node is
only used to set the command to NULL after processing it, not for
removing the node from the list.
There is a crucial difference between these functions, in that
Lst_ForEachUntil can cope with a few concurrent modifications while
iterating over the list. This is something that Lst_ForEach doesn't do.
This difference led to a crash very early in NetBSD's build.sh.
These functions made the code larger than necessary. The prev and next
fields are published intentionally since navigating in a doubly-linked
list is simple to do and there is no need to wrap this in a layer of
function calls, not even syntactically. (On the execution level, the
function calls had been inlined anyway.)