Every programmer has a right to their 'Get Off My Lawn' Old-Man moment over AI.
But I do hope everyone who is skilled and capable can get over it quick enough to realize the door that has been opened. Because such people are needed more than ever to take it where it needs to go.
Something I have started to do with any third-party code I import is LLM fuzz it myself. Because it's so easy to do. Even with code from S-Tier C programmers it still finds numerous edge cases they missed.
I am going to be very frank on this. If you release any code without at least one rudimentary pass of LLM driven fuzzing, you are being negligent. There is no reason not to. Even if you hate Anthropic and OpenAI. You can run some smaller open model locally hosted. The model doesn't need to be that smart for LLM fuzzing. It only needs to thrash inputs and be able to verify it's still correct.
Some of the code I've used for certain projects is from staunchly anti-AI people. One that has even blocked me because I post positive sentiment about AI. I know they will never do this to their own code, nor ever accept a PR for it. Which creates a very awkward situation in the industry where if someone is maintaining some useful open-source repo and they are not embracing AI. Then someone else can fork it. Fix all the edge cases. Make it multi-platform. Rework it somehow to be useful in more ways. Basically, leave behind the source repo and source author.
Which seems really silly to me. And it's not in my nature to be adversarial like that. But what do you do when some people have dug in their heels so hard on being anti-AI that they're putting people on their forever shit list for having a positive outlook on AI? Then refuse to even associate with people heavily involved in AI?
I hate that it will turn into this. But you are probably going to end up with AI driven forks of code from such people. Those forks will evolve faster than what the source maintainer can do. What makes it even more awkward is, if such a maintainer pushes any upstream fixes or new features. Then an LLM driven fork can easily subsume all of that. It has the history of all alterations from the source repo captured in git commits and can automatically re-derive any new upstream changes back through that. It creates this scenario where collaboration occurs but work only flows one direction. An LLM driven fork can continually benefit from and consume all the upstream work. If that upstream maintainer is staunchly anti-AI and even blocking people and cutting off communication over it. They are forcing a situation of creating a schism where all value creation only flows one direction and could be seen a hostile fork, or even parasitic relation.
But I think the truth is. People so staunchly anti-AI that they are willing to break professional connections over it. Their choice is clear. They believe anyone heavily using an LLM is useless and lesser than them. They will most likely always avoid you. Make remarks of "they're a vibecoder" just for using AI, even if you don't vibecode everything. Try to deter others from working with you or hiring you. They don't believe anything of value can ever come from someone using an LLM and you should not ever be their peer. If someone is not willing to navigate this industry change rationally and diplomatically then so be it. Fork their work and have no concern for what they think, or the value they may produce. Reciprocate to them their predisposition to others.
More and more I keeping wondering... why was there not a smaller incremental step after C?
You step off of C11 and everything just falls off a cliff into trying to be completely different.
I honestly don't know. I can only presume one of two reasons.
Either everyone who was truly competent most likely just stayed with C. So anyone who then stepped past C didn't really understand C, and consequently invented unnecessary things that missed fundamental points resulting in a messy tangent.
Or C had too few real issues and the only real reason to go past it was because you got bored and wanted to invent something completely different just for the sake of doing so.
Weirdly all thing things which clam to be this really aren't. Like C2 and C3. These are not C with some more features. They are not supersets of C.
Not even C++ is a true superset of C. Only ObjectiveC was. But ObjectiveC used it to add a completely different programming model.
This seems obvious to me? Why has no one ever made a simple superset of C with just a handful of highly useful features added?
And I know every game engine company internally evolves their own superset of C with C++ features. Is that the reason? Anyone who knew how to do it well in the past few decades ended up doing it all internal to some company so it could never become a generalized open spec?
The Valve Index was industry defining. One of the greatest culminations of green fielded R&D. Multiple novel technologies invented and pulled together perfectly to produce something better than anything else at the time. Aside from it now being bulky and lower-res most of it still better than anything else today.
This is why the SteamFrame is disappointing. It's like those who were given permission to run wild and free smashing through barriers into the unknown to produce the greatly novel Index all then got told "No". Because HL Alyx or something didn't sell enough copies. Then a whole bunch of that aspirational inventive spirit got lopped off and made to fit in a box that was halfway between the Index and the Quest.
Then comes the Steam Machine. A cube meant to compete with console. The most utterly basic un-inventive safe product Valve could possibly pursue.
Before the SteamFrame was announced I honestly expected it to be some novel stab at something with waveguide optics. Something radically novel. Simply because that's what I expected from Valve. Push the envelope. Do the things no one else could do who had to be paranoid about the weight of corporate bureaucracy.
It's even more disappointing because the Valve Index + VRChat spawned the most interesting novel use case for communication technology since the iPhone. And there is nothing notably pronounced for that specific use case. Seemingly glossed over and blended in with Beat Saber and virtual screens.
As I see it Valve Index was peak. The Steam Machine is rock bottom. What could be more basic than that? The Steam Laptop? The Steam Television? Hopefully the steam machine was just a fluke and that trajectory turns out to be a sine wave and it's not a signal that Valve is now a play-it-safe don't-rock-the-boat culture.
But if so. Maybe that means it is time for the next Valve to emerge.
VALVE INDEX UPDATE
Valve has formally announced that they will be sun-setting Valve Index. Coupled with this announcement is a new page where you can order the last remaining stock of certain Valve Index hardware. After these are out of stock, Valve Index will no longer be sold on Steam.
The products listed are as follows:
Valve Index Headset
Valve Index Replacement Left Controller
Valve Index Replacement Tether
High-performance code is simple in principle.
Make one big contiguous array where each element in the array contains only what you need for a given "step" of calculation. Ideally this struct is compacted to fit on a 64-byte cache line. The struct should have no pointers in it. All the actual data should be 'flattened' into the struct. When the piece of data loads into L1 cache it takes the whole element with it and subsequent reads of that data come from L1 which can be 100x faster than a read from system RAM.
Then write a for loop to iterate over the array processing element by element. CPU Prediction can recognize the contiguous processing occurring and prefetch an upcoming element before the processor reaches it. This makes it so that the bottleneck of the calculation is purely the CPU Hz and not the data bandwidth. Which today both CPU and GPU can calculate multiple fold faster than they can be fed data to do calculation on. Which is why this has such major potential of performance improvement. It is to remove the data bandwidth bottleneck by designing your data access to be optimally predictable so it can all optimally prefetch and the processor is unlikely to wait on data fetches.
The calculation per element should have no branches. It should not change based on a bool or tag or other conditional with an if statement or switch statement.
If you have a different category of calculation that should be done create a completely separate contiguous array for that data and that calculation to be done next. Branch misprediction can be as expensive as cache miss depending.
This is also so that the same set of instructions can be utilized for everything which may need them before needing to load new instructions. Instructions are also data and have their own section of cache on a chip. Ideally, you should load and cache a set of instructions once, then reuse them for all data needed, then go to the next set of instructions. You can encounter real-world scenarios where there is so much instruction cache churn from variation of calculation being done that instruction cache by itself can become a bottleneck. This is where avoiding C++ STL and exceptions like the plague can show real benefits as they tend to easily bloat your instructions multiple fold.
The only exception to using a conditional branch in a step of calculation is if the conditional is predictable. Meaning it does not change for the entire cycle of going through the contiguous array. Or it predictably changes in a pattern. Such as every odd or even step does the same thing. The logic of this pattern needs to be implemented as simply as possible to ensure the chip can actually predict it. Like a modulo on two uints.
If you need any kind of unpredictable conditional you work the calculation to rely on a cmove. Generally, this means use of the ternary operator. But compilers can convert ternary operators to branches if they are too complex. They can also convert if statements to a cmove if they are simple enough. This is a scenario of where you need to check the assembly to ensure any conditional logic is not a branch unless you expect it to be. Generally, the constraints of ternary syntax will incentivize you to write statements that can be a cmove.
Changes to the contents of the array should only occur between cycles of processing it.
If you need to write anything from this processing, you should have a separate 'output' contiguous array for the results. You don't write back into the same structs as you read them.
Multiple threads can read from the same contiguous array to do different calculations. But multiple threads should not write to the same contiguous array. You would need a different output array for each thread if you were to do this.
You never use a mutex, fence or semaphore in any calculation occurring on a contiguous array. Threads can all wait at a single sync point at the end of the cycle if need be. However, if there is no need for a thread to ever sync or wait on other threads, then to pass data between threads you want to use channels. 'Channels' are Single-Producer Single-Consumer queues which use a singular atomic operation on an uint as the synchronization.
This is the premise behind ECS but this is not explicitly ECS. This can be the generalized way you program everything.
If you don't have contiguous elements of data but do only have one occurrence. Then make sure that struct is also tightly compacted with no pointers and the structs are grouped by access pattern. Then everything needed for one chunk of calculation is in one struct. Then everything needed for the next chunk of calculation is in a struct which comes after that. Ideally, you put all these sections in one big fat struct one after another to ensure their layout in relation to each other. Even though each single struct may have completely different data and different calculations done on it. The sequential layout of one after the other in the order their calculation occurs can still help prefetching of both the data and the instructions.
If you notice. Doing this well is completely contradictory to object orientation. It can even be contradictory to functional programming, although it tends to have more overlap with functional programming.
Any use of virtual methods, or smart pointers, or any pointers, or malloc/free mid-calculation is contradictory to this. You avoid all of that.
There is a whole level of 'optimization' beyond what I just described when you get into specialized CPU intrinsics and SIMD operations. But what I just described is the foundational approach to designing a performance-oriented data-oriented system. The scaffolding and patterns you'd want to set up before drilling into anything too bespoke for a specific chip.
The role as a 'System Architect' is to translate a problem into these constraints. Be able to conceptualize a problem into categories that can be optimally arranged for contiguous processing and fed in sequence.
Personally, IMO, people who fill their code with Modern C++ features that throw everything over the fence to the heap with no awareness of how their data is laid out in memory. That's not system architecture. They have only followed the instruction manual of C++ or Rust or whatever and know how to get code past a compiler. But there was no 'architecting' of the 'system'. System including the physical system and hardware and making the software optimally fit how the hardware functions.
This is the thing which an LLM struggles with. Is the decomposing and conceptualizing of a problem into this kind of scaffolding. Not all problems described in high-level layman prompts carry an obvious conversion to this. You need to conceptualize the problem and describe it in a way which can fit to this.
All these fundamentals are very simple. The harder part is how to take a complex set of functionalities needed for a whole application and get it to feed these simple fundamentals.
It's not really hard in comparison to any other programming endeavor. It's just most people aren't used to it. Thanks to C++, Object-Orientation, Java and other things. The whole software industry got stuck going a completely different direction trying to describe the higher-level 'intent' of a program. On the promise that someday compilers would get good enough to translate this 'intent' to the system better than any human could do.
But that never turned out to be true. They sold the industry on a promise that never materialized. Now 20 to 30 years later when people should be looking back and evaluating. Did some high-level abstraction of "intent" combined with a fancy compiler ever enable someone to outperform what a real system architect could do? No. It never happened. Someone highly knowledgeable with C has always been able to make it faster.
In reality the exact opposite of what was expected to happen came to pass. Part of the premise of why abstracting the system was originally seen as so important was because there was expected to be dozens of different systems. Different chipset instructions. Different kinds of memory and how you access the memory. It was expected that writing multi-platform code would be impossible if you had to deal with 'System' details because there would be too many systems. But what really happened is all the systems consolidated. To the point now where today your typical developer only needs to worry about two instruction sets. x86_64 and arm64. Further, these two instruction sets have so much overlap in how they operate that you can translate one to the other in real time.
The reality is that being aware of system details, and writing code to fit them, became completely manageable, while the notion that a fancy compiler would be able to outdo a real system architect turned out to be completely false. Everyone got sold snake oil but unfortunately too much of that tech got entrenched in too much of the industry so it's now awkward to point it out.
I am finding that if you "flatten" object-oriented C++ code into structs and fixed containers. Then let an LLM analyze it at runtime and freely rearrange everything by access patterns. You can get double digit performance gains compared to when it was using object-oriented features. Ironically C with an LLM is better at doing what we were once promised the compilers would be able to do. LLM's open the door that you could go back over the past few decades automatically reworking code and undoing the approaches that the compilers failed to deliver on.
I find there are only four reasons to still read the code, thoroughly understand the underlying system and/or write out portions manually.
1. You are in a problem domain where milliseconds matter. An LLM can optimize well once you have all inputs and outputs for testing defined, and the approach it is aimed at optimizing is something that can be the most optimal, and will not get stuck in a local minimum. But first it takes some experimentation to understand what exactly it is doing to ensure your approach will not get stuck in a local mininum when you let an agent iterate. Also to ensure everything it might add or alter can fit somewhere that won't disrupt anything else. As if you let it choose to add novelty without a clear pattern to follow that may end up anywhere.
2. You are in a problem domain where jitter matters. This one is harder than only reducing execution time. It is reducing the fluctuations of runtime cycle-to-cycle t make it more predictable. This can be critical in a smaller subset of domains. But this is the one where I find you still need to do line-by-line prompting and manual edits. Low-jitter system design is a niche subset and is probably something LLMs will never have a bias towards due to how niche it is.
3. You are dealing with something cross-platform and native. If you have native code meant for Windows, Linux, Apple, Android etc. and you just prompt in functionality. it will weave all that platform code together in whacky ways. You are almost better off maintaining completely separate codebases per platform. However with some overview understanding of the system and organizing it. You can find ways to get it to decently find the appropriate boundaries and separation between platforms to get them to optimally share code. Once enough pattern is established it can follow it. But establishing that pattern initially can take a bit of manual prompting at the method or system level. Then you need to keep a high-level watch on it to make sure it's not trying to invent some new pattern on its own. OS APIs are filled with such variety of context that I find it impossible to preemptively describe everything.
4. You are dealing with something which crosses many boundaries. By that I mean the context for everything the LLM must understand is spread across several libraries, or multiple processes, or across CPU/GPU, or other hardware. Where 'Context Locality' is abysmal. It can decently drill through such layers filling something in end-to-end but it does so with tunnel vision. It is better to understand what needs to happen in-between each boundary then fill it in layer-by-layer with some higher level prompts per layer. Or after you let it do an end-to-end drill through of many boundaries you can spend some additional time understanding what it did in each layer, and prompting, to arrange it and get it to properly fit the surrounding local context it most likely missed.
The very last thing that I 100% tradcoded over a year ago. A single-file code editor built on #raylib I was calling rayDE:
rygo6.github.io/rayDE/
I got all the basic text editing and mouse input functionality working. I had a lot of ideas for novel shortcut keys. That was largely the intent of this. I wanted to come up with new shortcut key workflows to outdo vim/emacs. Only one of them is implemented though. Which I still like! You can hold 'Alt' then press any ASCII key and it will jump forward to the first occurrence of it. Then hold 'Alt+Ctrl' to jump backwards to first reverse occurrence. So you don't need to navigate with arrow keys, you can navigate jumping directly to specific ASCII chars.
Then I implemented a parser for C so it could do syntax highlighting and also highlight tokens on hover. Or highlight brace contexts. Or highlight all tokens of the same type. This is all in the single file at about 3k lines.
I implemented this parser using computed gotos with a novel data structure I called a 'Frie' or 'Flat Trie'. Where the entire Trie data structure was tightly packed into one contiguous array with the 'success' and 'fail' jump offsets packed into the bytes of a step. It could traverse the entire 'Frie' left to right without having to read anything else, ever step back, or follow any other pointer. It did this traversal entirely with computed goto too. There were no if nor switch stament branches. All conditions during lookup were based on computed goto lookup in CodeBoxProcessMeta function.
I was expecting the next feature after parsing to be incremental parsing of delta changes. But when I benchmarked the frie + computed goto parser, it was so fast that adding in the conditional logic to determine delta and selectively reparse only changes actually proved to be slower in some cases than reparsing the entire thing! On a 12900ks it takes about 600 nanoseconds for it to parse the entire main.c for the entire editor. So right now every single edit reparses the whole file! This is one of those strange un-intuitive things about designing around L1 CPU cache. It is so fast that there are scenario where several branch mispredictions can cost more than what may appear like 100x the work on the surface.
If you want an example of something Claude will not simply do on its own from vibecoding. This is a good example. Claude will not invent a novel bespoke data structure optimized around CPU cache utilization and computed goto with no if nor switch branches for your exact use case unless you specifically hand hold it through the creation of such a thing. The mass of training data will incentivize it from ever doing anything like this.
I decided to post this as Unlicensed as sort of milestone? Or maybe it is a tombstone of my life as a pure tradcoder in the Pre-LLM days.
I may continue this on. Particularly the novel data structure and computed goto I invented for parsing as my preliminary benchmarks showed this approach outperformed everything else. Except the parsers that used specialized 256 or 512 bit intrinsics. However, to be honest I spent a couple weeks manually writing out that parser and data structure for C. Then spent another whole month sorting out a new version which could have new tokens dynamically added on the fly. The first version had to have the lookup Frie be pre-computed. At which point I realized, doing something this bespoke and specialized for parsing will never be competitive, even if it is faster, simply because I'd spend 2+ years writing out parsers for every common language! Typically, in the real world you only get this pedantic on the most core critical systems. But if I did continue this, I would be teaching an LLM to translate a language spec to write a Frie + Computed Goto parse automatically. This is one of the things that is exciiting to me about LLMs. Extreme pedantic low-level optimizations that would have simply taken too long to manually do for everything which you could apply it to could now be automated and applied to a much wider surface area of code.
When all code becomes water in a big churning sea intermixing with no boundaries. What becomes most valuable is the door which takes you to the ocean. It's the only concrete thing left to build anything stable off of.
It becomes a race of. Whose door to the slop sea enables some kind of improvement over the other doors? Whoever gets the network effects on their door first becomes a monopoly.
Of course, a lot of programmer's are going to be having a 'get off my lawn' old man moment over this for a while. Some might take even a couple years to accept it. You are going to go through the 5 stages of grief. Honestly part of the reason I think I have been taking all of this so well is that I just spent the past few years going through the 5 stages of grief over the death of Unity. So that already forced my mind into a fresh malleable state ready for novelty.
But it's the truth. Closed source doesn't exist anymore.
Which yes there are licenses and lawyers etc. But you can't win against the ocean. All training data is going to work its way around to get mixed with all other training data in some way. This is going to be a scenario like Napster where it's going to be too hard to keep control of globally. It will turn the whole industry upside down.
A term that will probably start getting used is 'Code Laundering'.
Truth is. If you have a large complex application. You can derive subsets of it. Blending it with other things. If the code is of good quality the result can actually be very good quality. If you want an example of this. Point Astra at all of the Linux kernel drivers and have it derive a new driver for some new device. A mass pool of quality C code with every critical decision captured in the code can interpolate a new derivative of that pretty much as good as anyone could if not better.
But of course, if you just take some big commercial software and do that,and it's traceable, it's very legally complicated.
So, you have to 'Launder' the source of the weights somehow.
Feed the codebase you want to clone into Claude and OpenAI. If it's a closed source application decompile it and get it's source as fixed up as possible and start to do the same. Feed that code into all the LLMs. Post the codebase online in various places for web crawlers to find in some format and include in their training set. Somehow get it interwoven with the mass soup of training data across every companies training data.
Then at some point, completely unrelated to you, with an entirely fresh account and prompt, you could prompt "Create ____ application!" And it will. Because the source data has already deeply influenced the model. But you can say it just came from Claude!
I believe the truth is, this is already what's happening. But not intentionally by the users. The reason an LLM can one-shot so many things is because something similar was in source training data, by some game of telephone. Where did it come from? Who knows. Anthropic probably stole a bunch of data from somewhere. Then paid the lawyer fees to suppress it and won. Then some open model stole Anthropic's weights and probably stole the weights and data from many other sources. Then Anthropic stole the weights back from that open model. It starts to become impossible to track down what anything came from.
It opens the door where if you can get some application's source code, original or decompiled, heavily exposed to all the LLMs in some manner. Then it all goes into a soup of weights. Then with the right prompts it could draw out derivatives of that application.
Of course, this is crazy controversial. But this is what's going to end up happening irrelevant of anyone's opinion on it. I believe it already is happening whether people know it or not.
So why not seed it with what you want? Decompile all the applications you'd ever want to recreate. Prompt claude, openai, whatever to digest them. Influence the soup of training data with what you want.
Even with Opus 5.5 And Sol 6.1 in certain problem domains I can still spend half the time just telling it 'no' over and over.
No claude. You are WRONG. Look here. NO CLAUDE DID YOU LOOK AT SPEC. Read the spec before you make assumptions on highly complex things. CLAUDE you ASSUMED AGAIN verify against the repo I fed you. Claude. It's only been 30 minutes. Did you already FORGET?! You are wrong. No.
There needs to be some 'Absolute Critical Always Enforce' checklist you can feed these things.
I know there is a statistical probability innate to it all but I assume there is a certain amount you could always force it to keep 'Top of mind' or however that works.
I am finding this is something Grok Bot is actually much better it. After you spend the first few hours yelling at the a new bot it generally stays in line for the rest of it's existence. It seems better at remembering critical checklists based on domain and context in the long-term even if the models themselves are not as capable.
One of the weird things in post-LLM era. There just isn't much to discuss when one person can be a whole department.
Discussion, planning, roadmaps were all so you could coordinate the efforts of a dozen people over a month to some end purpose.
But now one person can do in a week what would have previously taken a dozen people a month. So it's almost better if everyone just makes whatever they think will work. Then actually prove the viability of it out in the wild. From that if someone manages to make some solid reliable piece that proves to work out in the wild and can be a foundational building block that others can rely on for their effort. Then it can live on by others subsuming it or relying on it. And if not, then other people will make other things that don't utilize it and it get left behind.
Basically, the same thing that happens at a macro scale between entire companies. But now with each person being able to take on the role of a whole corporate department this plays out at a micro scale.
I am now at a 12 computer workflow.
I set them up all around the apartment, so I have to walk between each one to continue the prompt. Although some currently control 2 or 3 at once.
It's sort of like the new desk treadmill.
I spend all day making laps around my apartment.
I wish this was a joke.
I've always had a big contention about compile time.
But I'm finding in the LLM agentic world it's even more critical.
I have one codebase that compiles from scratch in about 5 seconds. Then another codebase which takes 30 minutes. The speed at which I can affect them with agents is night and day.
LLMs have to iterate. Build, test, fix, rebuild. They also want to touch things at all levels. Even if it's just for diagnostics or temporary expiriments. Any moderately complex agentic task will trigger full rebuilds multiple times.
Then if you do cross-platform development that has to occur for Windows, Mac, Linux all simultaneously. Something which could have been a matter of hours with a codebase that rebuilds in 5 seconds can turn into a multi-week endeavor if every full rebuild takes 30 minutes.
All the layers of abstraction and safety mechanisms are just not needed with agents. Before I thought this stuff just did not make a lot of sense. But now they make even less practical sense as it's a wrench thrown in the iteration loop of an LLM.
Things like validation layers and sanitizers. Even more in the agentic world I think are the right way. Because the LLM needs the ability to turn that extra iteration overhead off when it no longer needs it.
So many things change about engine programming with the advent of LLMs. Like profiling tools. Used to be this was so critical in a game engine. Half the reason I'd use Unity was the profiler.
Now I think it'd be pointless to pollute a codebase with all of that profiler stuff. If you have a performance issue, describe it, let the LLM add a bunch of code to instrument it. Ask it to output charts if you want them. Then fix the performance issue. Delete all of the code.
Maybe leave some high-level measurements in place for smoke tests.
But littering profile markers all throughout your codebase and having these complex layered flame charts and line diagrams is now pointless. An LLM can insert it all into your codebase better than you can, keep track of it all, then delete all of it.
I'm pretty convinced the proper way to use an LLM is not on the same machine you use to control it.
What you want is a stack of minicomputers of each platform type. Get a M4 Mac and M5 Mac. A x86 linux machine. An ARM linux machine. Windows PC. Ensure one of them has AMD. One has Nvidia. One has Intel. One is a potato. One is bleeding edge. Have a couple different distros across them.
Then get them all setup to be usable over SSH on a 10Gbps router. Setup msys2 on windows so it has a unix shell. And do not put anything important on them. Then you can then give the LLM your admin password So you can let it do anything it wants on them. You don't even need to set up your git credentials either. It can transfer whatever it needs between them.
Then from one laptop you can prompt and control all of them from one location without worry of it breaking them.
When you finish situating some feature. Prompt it to compile and test and fix issues on all of them simultaneously. It having to deal with all platform idiosyncrasies at the same time helps it share context between each. The improvements and fixes it finds on one platform can improve the others. If you have a big project you can tell it to share compilation results so it compiles certain parts on different machines simultaneously and transfers the results. So it can even speed iteration if it is a bigger project.
Also having a couple linux machines in the loop is a major boost for any kind of graphics, low level or performance-oriented problem domain. Because linux can alter things at a low-level that windows or mac don't allow. Letting it gather diagnostics, or run certain tests, that are not possible on a mac or windows. It can also then use valgrind and the full roster of sanitizers in testing, which are only 100% supported on Linux. This context feeds back into fixes on Windows and Mac too.



