I think stacking windows make more sense in the context of the pre-OS X Macintosh UI. The Mac was built entirely around the concept of spatial manipulation. When you opened a folder, it would remember the exact position and size of its window, where all of the icons were, where your scroll bar was, whether the folder was set for icon, list, or detail view, etc. Every window was permanently and unambiguously associated with a single folder. This made navigating the computer possible entirely through muscle memory. Just like you know where all the light switches and doorknobs are in your house after a few months, you would gain the ability to navigate through files on your computer extremely quickly because when you double click on a folder you already knew where it would open and what would show up in the window. Instead of remembering a file path, you would remember a series of mouse motions to get where you wanted with very little conscious thought. Obviously this workflow isn't suitable for everyone, but a lot of people enjoyed it and I think it's a shame that Apple decided to throw it out for Mac OS X.
Another feature that gets a lot of flack from some Linux users is desktop icons. This is something else that a lot of UIs screwed up (maybe stemming from Windows 95? I'm not sure). The classic Mac UI let you drag whatever files you were currently working on to the desktop and do whatever you needed to do. Then when you were done with the files, you could highlight them and select "Put Away" and they'd all get moved back to their original locations. The desktop was a temporary space for what you were actively working on, not a giant catch-all storage location like how modern UIs treat it.
Here's a contrarian-squared take: in reality, the question "Is macOS a BSD" is malformed. It makes some sens, but is more confusing than it's worth.
Yes, NeXT was built on Mach, which was itself basically an evolution of the Accent microkernel married with BSD, when BSD was a proper noun.
In fact, NextStep 0.8, the first public "pre-release", has left support in for A.OUT executables. The included ex and vi binaries are indeed A.OUT executables taken straight from BSD! In the very next release, support for A.OUT was removed, leaving only the Mach-O loader.
XNU is not derived from the Mach that NeXT was, though, but from the OSF Mach kernel, which was continued at the University of Utah. The BSD "bits" were selectively copied from the extent continuations of BSD, or rewritten from scratch. The XNU kernel doesn't strongly resemble any particular *BSD tree, to my knowledge.
Darwin's origins are messier, since it looks like it was a direct continuation of the existing NeXT packaging (but only Apple would know for sure). NeXT, very much unlike BSD, split its userland into distinct packages, which were versioned independently. This practice has carried on to this day, where e.g. Darwin's libc is packaged and versioned separately from the kernel and other utilities.
For that matter, for a very brief period of history, Darwin used Debian's dpkg for building and installing packages. Evidence of this stayed until OS X 10.4-ish, in the Darwin releases, but they returned to NeXT style BOM files for package management.
All that to say, does NeXT/macOS have a BSD-like userland? Yes, but so does Chimera Linux. Does the kernel resemble BSD? In some ways yes, but in many ways no, it's very semantically different.
And is it descendant from BSD? Again, "yes", but it also doesn't really "come" directly from BSD anymore than, say, OSF/1 did. There's no specific BSD it forked from, and no specific point at which it ever really looked like BSD, in terms of userland, packaging, or kernel semantics.
So I think the question just doesn't make much sense to ask.
- Having a single X server that almost everyone used lead to ossification. Having Wayland explicitly be only a protocol is helping to avoid that, though it comes with its own growing pains.
- Wayland-the-Protocol (sounds like a Sonic the Hedgehog character when you say it like that) is not free of cruft, but it has been forward-thinking. It's compositor-centric, unlike X11 which predates desktop compositing; that alone allows a lot of clean-up. It approaches features like DPI scaling, refresh rates, multi-head, and HDR from first principles. Native Wayland enables a much better laptop docking experience.
- Linux desktop security and privacy absolutely sucks, and X.org is part of that. I don't think there is a meaningful future in running all applications in their own nested X servers, but I also believe that trying to refactor X.org to shoehorn in namespaces is not worth the effort. Wayland goes pretty radical in the direction of isolating clients, but I think it is a good start.
I think a ton of the growing pains with Wayland come from just how radical the design really is. For example, there is deliberately no global coordinate space. Windows don't even know where they are on screen. When you drag a window, it doesn't know where it's going, how much it's moving, anything. There isn't even a coordinate space to express global positions, from a protocol PoV. This is crazy. Pretty much no other desktop windowing system works this way.
I'm not even bothered that people are skeptical that this could even work; it would be weird to not be. But what's really crazy, is that it does work. I'm using it right now. It doesn't only work, but it works very well, for all of the applications I use. If anything, KDE has never felt less buggy than it does now, nor has it ever felt more integrated than it does now. I basically have no problems at all with the current status quo, and it has greatly improved my experience as someone who likes to dock my laptop.
But you do raise a point:
> It feels like a regression to me and a lot of other people who have run into serious usability problems.
The real major downside of Wayland development is that it takes forever. It's design-by-committee. The results are actually pretty good (My go-to example is the color management protocol, which is probably one of the most solid color management APIs so far) but it really does take forever (My go-to example is the color management protocol, which took about 5 years from MR opening to merging.)
The developers of software like KiCad don't want to deal with this, they would greatly prefer if software just continued to work how it always did. And to be fair, for the most part XWayland should give this to you. (In KDE, XWayland can do almost everything it always could, including screen capture and controlling the mouse if you allow it to.) XWayland is not deprecated and not planned to be.
However, the Wayland developers have taken a stance of not just implementing raw tools that can be used to implement various UI features, but instead implement protocols for those specific UI features.
An example is how dragging a window works in Wayland: when a user clicks or interacts with a draggable client area, all the client does is signal that they have, and the compositor takes over from there and initiates a drag.
Another example would be how detachable tabs in Chrome work in Wayland: it uses a slightly augmented invocation of the drag'n'drop protocol that lets you attach a window drag to it as well. I think it's a pretty elegant solution.
But that's definitely where things are stuck at. Some applications have UI features that they can't implement in Wayland. xdg-session-management for being able to save and restore window positions is still not merged, so there is no standard way to implement this in Wayland. ext-zones for positioning multi-window application windows relative to each-other is still not merged, so there is no standard way to implement this in Wayland. Older techniques like directly embedding windows from other applications have some potential approaches: embedding a small Wayland compositor into an application seems to be one of the main approaches in large UI toolkits (sounds crazy, but Wayland compositors can be pretty small, so it's not as bad as it seems) whereas there is xdg-foreign which is supported by many compositors (Supported by GNOME, KDE, Sway, but missing in Mir, Hyprland and Weston. Fragmentation!) but it doesn't support every possible thing you could do in X11 (like passing an xid to mpv to embed it in your application, for example.)
I don't think it's unreasonable that people are frustrated, especially about how long the progress can take sometimes, but when I read these MRs and see the resulting protocols, I can't exactly blame the developers of the protocols. It's a long and hard process for a reason, and screwing up a protocol is not a cheap mistake for the entire ecosystem.
But I don't think all of this time is wasted; I think Wayland will be easier to adapt and evolve into the future. Even if we wound up with a one-true-compositor situation, there'd be really no reason to entirely get rid of Wayland as a protocol for applications to speak. Wayland doesn't really need much to operate; as far as I know, pretty much just UNIX domain sockets and the driver infrastructure to implement a WSI for Vulkan/GL.
at my last job code review was done directly in your editor (with tooling to show you diffs as well).
What this meant was that instead of leaving nitpicky comments, people would just change things that were nitpicky but clear improvements. They'd only leave comments (which blocked release) for stuff that was interesting enough to discuss.
This was typically a big shock for new hires who were used to the "comment for every nitpick" system; I think it can feel insulting when someone changes your feature. But I quickly came to love it and can't imagine doing code review any other way now. It's so much faster!
I'm not sure how to tie this to AI code review tbh. Right now I don't think I'd trust a model's taste for when to change things and when to leave a comment. But maybe that'll change. I agree that if you automated away my taste for code it'd put me in a weird spot!
As long as your target language has a strict define-before-use rule and no advanced inference is required you will know the types of expressions, and can perform type-based optimizations. You can also do constant folding and (very rudimentary) inlining. But the best optimizations are done on IRs, which you don't have access to in an old-school single pass design. LICM, CSE, GVN, DCE, and all the countless loop opts are not available to you. You'll also spill to memory a lot, because you can't run a decent regalloc in a single pass.
I'm actually a big fan a function-by-function dual-pass compilation. You generate IR from the parser in one pass, and do codegen right after. Most intermediate state is thrown out (including the AST, for non-polymorphic functions) and you move on to the next function. This give you an extremely fast data-oriented baseline compiler with reasonable codegen (much better than something like tcc).
There's an interesting parallel with ML compilation libraries (TensorFlow 1, JAX jit, PyTorch compile) where a tracing approach is taken to build up a graph of operations that are then essentially compiled (or otherwise lowered and executed by a specialized VM). We're often nowadays working in dynamic languages, so they become essentially the frontend to new DSLs, and instead of defining new syntax, we embed the AST construction into the scripting language.
For ML, we're delaying the execution of GPU/linalg kernels so that we can fuse them. For RPC, we're delaying the execution of network requests so that we can fuse them.
Of course, compiled languages themselves delay the execution of ops (add/mul/load/store/etc) so that we can fuse them, i.e. skip over the round-trip of the interpreter/VM loop.
The power of code as data in various guises.
Another angle on this is the importance of separating control plane (i.e. instructions) from data plane in distributed systems, which is any system where you can observe a "delay". When you zoom into a single CPU, it acknowledges its nature as a distributed system with memory far away by separating out the instruction pipeline and instruction cache from the data. In Cap'n Web, we've got the instructions as the RPC graph being built up.
I just thought these were some interesting patterns. I'm not sure I yet see all the way down to the bottom though. Feels like we go in circles, or rather, the stack is replicated (compiler built on interpreter built on compiler built on interpreter ...). In some respect this is the typical Lispy code is data, data is code, but I dunno, feels like there's something here to cut through...
I had a Bash script that parsed my browser history, and for every YouTube video it would run yt-dlp with "--write-info-json --write-subtitles --download-archive=already-downloaded.db" flags. Creating it was the easy part, but keeping it running has presented some challenges. For example, Google started rate limiting my IP quickly, so I had to offload this process to a NAS, where it could keep running for hours overnight, persistently downloading stuff at near dialup speeds. Then I was running out of storage quickly, so I had to add video filtering, and I planned to add basic garbage collection. And of course I had to have youtube-dl (and later yt-dlp) updated at all times.
In the end, I decided it is not worth it. In the scenario you described, I would take the video link/ID and paste it into Bing and Yandex. There is large chance they still have that page cached in their index.
FWIW if you are going to create your own tool, my advice will be to make it a browser extension, and try to pull the video straight from YouTube's <video> element.
If you're going to use SQLite as an application file format, you should:
1. Enable the secure_delete pragma <https://antonz.org/sqlite-secure-delete/> so that when your user deletes something, the data is actually erased. Otherwise, when a user shares one of your application's files with someone else, the recipient could recover information that the sender thought they had deleted.
2. Enable the options described at <https://www.sqlite.org/security.html#untrusted_sqlite_databa...> under "Untrusted SQLite Database Files" to make it safer to open files from untrusted sources. No one wants to get pwned when they open an email attachment.
3. Be aware that when it comes to handling security vulnerabilities, the SQLite developers consider this use case to be niche ("few real-world applications" open SQLite database files from untrusted sources, they say) and they seem to get annoyed that people run fuzzers against SQLite, even though application file formats should definitely be fuzzed. https://www.sqlite.org/cves.html
They fail to mention any of this on their marketing pages about how you should use SQLite as an application file format.
We've been building out our agent [0], and we've found this to be the case.
We actually dialed it back a bunch, because it feels _terrible_. Yes, you get more correct answers, but it's more akin to giving the agent anxiety. Especially with agents that have access to tools, they'll burn enormous amounts of time on tool calls, trying to get enough information to overcome a motivation that's essentially burned into its identity.
(We saw one conversation where it just browsed social media instead of looking at the code for like 5 minutes, which ... you know, I get it.)
It's been much more effective to make uncertainty or further exploration be part of the agents success criteria.
- BAD: "Critique your own thoughts" -> leads to the agent trying really hard to get it right, but still not willing to actually be wrong
- GOOD: "Expose where your thoughts are unsupported or could benefit from further information" -> leads to the agent producing high-quality results, with loose ends that the user can choose to incorporate, ignore, or correct.
That prompt, combined with dialing up the thinking (either with API or prompt tuning) works much better, because it's sidestepping the training and tuning that's implicitly encouraged it to sound correct at all times.
These things are moving so quickly, but I teach a 2nd year combinatorics course, and about 3 months ago I tried th latest chatGPT and Deepseek -- they could answer very standard questions, but were wrong for more advanced questions, but often in quite subtle ways. I actually set a piece of homework "marking" chatGPT, which went well and students seemed to enjoy!
Symbian wasn't really designed from scratch though. It has its roots in the nineteen eighties. The 32 bit version of Psion's Epoch is what eventually became Symbian and it wasn't until other consortium members gave up that Symbian was taken over by Nokia.
I worked at Nokia Research and Nokia Maps between 2005 and 2012. I witnessed the arrival of the Iphone and Android from the inside. There was a lot of disagreement internally on what the right thing to do was. And part of the company was actually very clued in and trying to do smart things. But Nokia did a lot of things wrong and most of that was just not getting that it was a software company. It had all the wrong reflexes.
The problem with Symbian from day 1 was that at the time it launched, it was already getting clear that embedded Linux was going to be a thing and it was a bit lacking in features relative to that. Between the late nineties and 2005, there were quite many attempts to use Linux on mobile and embedded devices. By the early 2000s it was widely used on things like routers and other devices. And there had been some attempts at building pdas and phones with it. Google bought Android (the company) in 2005; and they launched phones around 2008. 2005 was the year I joined Nokia. Rumors about Apple working on a device were already getting quite concrete around that time (i.e. Nokia execs would have had good intel about what was coming) and over the course of the next few years it became clear that it was going to happen.
Nokia was focusing on flip phones instead. They were really worried about the Motorola Razr. And the Blackberry. They completely missed the point of desktop operating systems being repurposed for mobile. They thought mobile was special and that they owned it. They stubbornly ignored all the internal signals (there were many) that that was wrong. Nokia even launched a Linux based device in 2006 the N770. It was not a phone and that was not an accident. Operators would object. This could not be. So, they crippled it.
Apple and Google both proved them wrong. The Symbian strategy was dead as a doornail before they even launched the first devices (around 2003/2004). Apple proved that operators were weak and could not ignore popular consumer demand. And of course IOS was a specialized version of OSX for mobile. And Google ended up benefiting a lot from Nokia's Linux work. The kernel was essentially the same for Maemo and Android. Google even bought N800s to dual boot them into Android before they had the first Nexus phone ready. The point here is that Nokia had a shipping touch screen based Linux device in 2006. Years before Google or Apple had phones on the market. The only reasons it didn't have a sim card were political. It could have been a phone, easily. The N800 continued the tradition. It even had a webcam and skype. But no sim card. Only the N900 fixed that. But that was way too late and they positioned it as a developer toy. Innovation around this topic was stifled. It was the obvious move. But the Symbian crowd successfully blocked and frustrated that.
By 2008 it was scrambling to undo a lot of really bad decision making around Symbian in the years before that. Which included actually cancelling S90, which is a touch screen version of Symbian that never saw the light of day. That happened around 2005. S60 3.x was the current version when Apple announced the iphone and had no touchscreen support at all. Nokia had to rapidly create a new version with touch screen support. It was a rush job and the first versions completely destroyed Nokia's reputation because it was unstable and unusable (both). The launch device for this was a complete flop. And it took until the early 2000s to stabilize it. And by then the phone reviews were brutal for any Nokia device. Apple was running circles around them. And Android was starting to actually eat into non Symbian (S30, S40) Nokia revenue. Which actually were most of the market. Nokia's entire OS strategy was failing at that point.
It's feature phone market started imploding and this was until then the money maker. They sold hundreds of millions of those. And then we got the whole drama with pissing in your pants to stay warm (Ansi Vanjoki, one of the VPs) and then Stephen Elop getting hired and chucking him (and Symbian, Linux, and all the rest) out. And then Windows Phone of course didn't make it and the MS acquisition happened and MS unceremoniously pulled the plug in 2014. Too little too late.
I still feel like the best uses of models we've seen to date is for brand new code and quick prototyping. I'm less convinced of the strength of their capabilities for improving on large preexisting content over which someone has repeatedly iterated.
Part of that is because, by definition, models cannot know what is not in a codebase and there is meaningful signal in that negative space. Encoding what isn't there seems like a hard problem, so even as models get smarter, they will continue to be handicapped by that lack of institutional knowledge, so to speak.
Imagine giving a large codebase to an incredibly talented developer and asking them to zero-shot a particular problem in one go, with only moments to read it and no opportunity to ask questions. More often than not, a less talented developer who is very familiar with that codebase will be able to add more value with the same amount of effort when tackling that same problem.
The most important operation in QNX is MsgSend, which works like an interprocess subroutine call. It sends a byte array to another process and waits for a byte array reply and a status code. All I/O and network requests do a MsgSend. The C/C++ libraries handle that and simulate POSIX semantics. The design of the OS is optimized to make MsgSend fast.
A MsgSend is to another service process, hopefully waiting on a MsgReceive. For the case where the service process is idle, waiting on a MsgReceive, there is a fast path where the sending thread is blocked, the receiving thread is unblocked, and control is immediately transferred without a trip through the scheduler. The receiving process inherits the sender's priority and CPU quantum. When the service process does a MsgReply, control is transferred back in a similar way.
This fast path offers some big advantages. There's no scheduling delay; the control transfer happens immediately, almost like a coroutine. There's no CPU switch, so the data that's being sent is in the cache the service process will need. This minimizes the penalty for data copying; the message being copied is usually in the highest level cache.
Inheriting the sender's priority avoids priority inversions, where a high-priority process calls a lower-priority one and stalls. QNX is a real-time system, and priorities are taken very seriously. MsgSend/Receive is priority based; higher priorities preempt lower ones. This gives QNX the unusual property that file system and network access are also priority based. I've run hard real time programs while doing compiles and web browsing on the same machine. The real-time code wasn't slowed by that. (Sadly, with the latest release, QNX is discontinuing support for self-hosted development. QNX is mostly being used for auto dashboards and mobile devices now, so everybody is cross-developing. The IDE is Eclipse, by the way.)
Inheriting the sender's CPU quantum (time left before another task at the same priority gets to run) means that calling a server neither puts you at the end of the line for CPU nor puts you at the head of the line. It's just like a subroutine call for scheduling purposes.
MsgReceive returns an ID for replying to the message; that's used in the MsgReply. So one server can serve many clients. You can have multiple threads in MsgReceive/process/MsgReply loops, so you can have multiple servers running in parallel for concurrency.
This isn't that hard to implement. It's not a secret; it's in the QNX documentation. But few OSs work that way. Most OSs (Linux-domain messaging, System V messaging) have unidirectional message passing, so when the caller sends, the receiver is unblocked, and the sender continues to run. The sender then typically reads from a channel for a reply, which blocks it. This approach means several trips through the CPU scheduler and behaves badly under heavy CPU load. Most of those systems don't support the many-one or many-many case.
Somebody really should write a microkernel like this in Rust. The actual QNX kernel occupies only about 60K bytes on an IA-32 machine, plus a process called "proc" which does various privileged functions but runs as a user process. So it's not a huge job.
All drivers are user processes. There is no such thing as a kernel driver in QNX. Boot images can contain user processes to be started at boot time, which is how initial drivers get loaded. Almost everything is an optional component, including the file system. Code is ROMable, and for small embedded devices, all the code may be in ROM. On the other hand, QNX can be configured as a web server or a desktop system, although this is rarely done.
There's no paging or swapping. This is real-time, and there may not even be a disk. (Paging can be supported within a process, and that's done for gcc, but not much else.) This makes for a nicely responsive desktop system.
This is (in my experience) a byproduct of good design, so changing the design wouldn't be a great idea.
Every time I've seen this happen it's in code that scales really well to lots of CPUs while having a lot of shared state, and the way it gets there is that it's using very fine-grained locks combined with smart load balancing. The idea is that the load balancer makes it improbable-but-not-impossible that two processors would ever want to touch the same state. And to achieve that, locks are scattered throughout, usually protecting tiny critical sections and tiny amounts of state.
Whenever I've gotten into that situation or found code that was in that situation, the code performed better than if it had coarser locks and better than if it used lock-free algorithms (because those usually carry their own baggage and "tax"). They performed better than the serial version of the code that had no locks.
So, the situation is: you've got code that performs great, but does in fact spend maybe ~2% of its time in the CAS to lock and unlock locks. So... you can get a tiny bit faster if you use a spinlock, because then unlocking isn't a CAS, and you run 1% faster.
let valueIWant: String? = nil
let sem = DispatchSemaphore(value: 0)
someAsyncFunction() { result in
// this is called in another thread
valueIWant = result
sem.signal()
}
sem.wait()
assert(valueIWant != nil)
Note, this is mostly about semaphores, so it’s a bit offtopic from the original discussion here which is about spinlocks. But basically, never lock something in expectation of another thread unlocking it. Since semaphores are ownerless, the scheduler has no idea what thread will eventually signal the semaphore, so it can’t boost the priority of any threads you’re waiting on.
Not to mention the thread explosion issue: If the task running the completion (ie. signal()) is submitted to a libdispatch queue, the thread pool implementation will spawn more threads if all of them are starved, to eliminate deadlocks, which can lead to thread explosion in addition to the priority inversion issue. This advice dates back to when libdispatch was invented (and the internal article was called The Semaphore Antipattern…)
But with Swift Concurrency it’s far, far worse, because the SC thread pool does not spawn new threads if all of them are blocked. Meaning the dispatch block that’s supposed to call the callback may never get run, and you deadlock. Most importantly, this is true even of plain ObjC libdispatch code written 15 years ago: If some ancient framework you’re calling into does the semaphore antipattern, libdispatch is allowed to assume that all Swift Concurrency threads will eventually make forward progress, and so it doesn’t spawn new threads to handle the work, even if all threads are blocked. So ancient code that uses this antipattern suddenly blows up if someone calls it (transitively!) from a swift concurrency context. I’ve squashed countless bugs on this issue alone. (I wrote my own article on this internally too, titled something like “Semaphore abuse: How to deadlock your process and require a device reboot”)
IMHO, the semaphore should be officially marked deprecated and trigger warnings… there are no appropriate uses for it. If you’re in a synchronous context and need to block on the result of async code, the best bet is to stop what you’re doing and file a bug to the people writing the async code and tell them to give you a synchronous API (or refactor your code to be async.) Never ever use semaphores to block on async code. Not even once.
Always cool to see new mutex implementations and shootouts between them, but I don’t like how this one is benchmarked. Looks like a microbenchmark.
Most of us who ship fast locks use very large multithreaded programs as our primary way of testing performance. The things that make a mutex fast or slow seem to be different for complex workloads with varied critical section length, varied numbers of threads contending, and varying levels of contention.
(Source: I wrote the fast locks that WebKit uses, I’m the person who invented the ParkingLot abstraction for lock impls (now also used in Rust and Unreal Engine), and I previously did research on fast locks for Java and have a paper about that.)
One of my favorite aspects of LSP being 'good enough' is that barebones support is incredibly simple to understand and implement. Old link since it was factored out at some point, but here is the entire LSP implementation for the Micro editor, in ~700 lines of Lua (+ utility functions): https://github.com/AndCake/micro-plugin-lsp/blob/dde90295f09...
One trick I may or may not have invented for the enterprise PO problem (manual processes etc) was to offer an Azure Marketplace subscription for the product.
That way they can just go to azure and subscribe to the license that way, without needing any azure resources etc, it's just a billing mechanism.
They can then bundle that into their usual Azure spend and even do manual POs etc that I never have to deal with.
What a tiresome response, but here, I'm feeling in a loquacious mood, so I'll humor it.
First, what else would I use? I don't just want a "well designed" operating system, I also NEED an open source, customizable one that is compatible with the vast majority of developer tools and build systems and toolchains — and that is, without a doubt, Linux. I don't have a better option from a practical standpoint.
And even from a design standpoint, there simply aren't any better options, because Unix killed off all of the other good alternatives, and now it's essentially the least worst option we have right now. Basically the same situation we were in when the Unix-Hater's Handbook was written.
I mean, what else have we got?
Windows? Windows NT, by way of OpenVMS so I've heard, had some very interesting ideas that I think Linux would have done well to imitate, like having a Common Language Runtime that lets you directly interact with the objects and functions of every application and code base on your system and communicate with them in structured data (something Lisp Machines and Smalltalk did first, better, but I digress), but we are beginning to get that with DBUS and things like nushell, and Windows failed its own promise there and has a vast panoply of its own flaws, not least being Microsoft. So that's not really better at all.
MacOS? There are many ideas from MacOS I'm a huge advocate of Linux stealing, but it is still fundamentally a UNIX, and in my opinion has even more flaws than Linux has, especially since Apple seems to have stopped caring about their software quality. Not to mention, uh, >Apple<.
So you're basically just pulling the old "you think our social system is deeply flawed in some ways and want to change it? Just go live somewhere else then."
Furthermore, I was never intimating that everything Unix did was completely irredeemable and the entire system we've spent all this time and energy building should be thrown out completely in favor of something entirely new. Instead I was responding to the failure, the refusal even, to develop this amazing testament to open source development and the operating system design sensibilities ties that drive it forward or in any new directions, among a certain crowd that determines the worth of something new by how well it adheres to the philosophy of the old.
I think we can build an excellent new system on the bones of the Unix systems we have now, if nothing else because it's what we've got to start with, and it did sort of have some interesting ideas. I just think it requires actually thinking outside of the box and reanalyzing our dogmatic adherence to tired old traditions in light of everything we've learned and everything that's changed in the interim, and the good ideas from other operating systems that we can borrow, instead of just repeating the same ideas over and over.
A flaw in our modern notion of operating system research and development which has depressed even the creators of Unix themselves: and while I don't think Plan 9 was the right move exactly — I think it actually recapitulates many of the flaws and fallacies of unix, such as the idea that everything being a file is particularly useful, or that we should use plain text designed first for people to read and only secondly for a computer to process, in idiosyncratic formats, instead of structured data — if even the original creators of Unix believe that it should be transcended (Dennis Richie), and that the original philosophy is dead (Rob Pike), maybe we should learn something from that?
Maybe it's actually okay to look at what other good ideas there may be in the problem space instead of being myopically, slavishly devoted to a sad old religion.
> Linux won on Unix merits.
First of all, even if that was the case, that doesn't mean it can't improve?
Just because, let's say, it's Unixy design was better than anything else that was on the market at the time, and so it won out against the other options that were available, doesn't mean it is the best possible option — that it can't be improved or developed, or even that the philosophy motivating it wasn't fundamentally flawed and broken in some ways; all it means is that whatever improvements could be made, or whatever was broken in the philosophy to begin with, was less broken or less bad than what happened to exist in competition with it. You're making the exact same mistake that people with conservative tendencies in life me and insisting that just because something work in the past, for whatever reasons it did that, it can't possibly be developed or improved upon. You aren't even saying if it ain't broke don't fix it, you're saying if it wasn't literally the worst, don't fix it.
In fact, I don't even think Unix won out in the server, workstation, academic, scientific, and supercomputing Realms as much as it did because of timeless technical merits and design decisions based on eternal truths that mean we should continue genuflecting to them forever (as much as the purveyors of the Art of Unix would like us to think that) but instead because it happened to have features that happened to be extremely relevant merits in the particular time and place and context it arose in, technologically and economically speaking.
Namely, that it combined extreme minimalism and portability with a reasonable level of composability, even if the composability it offered was awkward and half baked, and because the other offerings had made their own more severe tactical mistakes (Lisp Machines).
Thus it won out mostly almost by historical accident, as most things do, because a proper level of composability and integration, using defined IPC and structured data and well integrated families of tools with non-Turing-complete configuration languages was just too difficult for the hardware of the time, and so the choices were either on operating system with very little composability and flexibility and programmability, or something like unix.
But that isn't the case anymore, yet because it did win, we've retroactively deified it.
We can have our cake and eat it too if we choose to, if we choose to not get sucked in to the North Korea like UNIX cult.
The opposite is also exciting: build a loss function that punishes models for storing knowledge. One of the issues of current models is that they seem to favor lookup over reasoning. If we can punish models (during training) for remembering that might cause them to become better at inference and logic instead.
The fundamental operation done by the transformer, softmax(Q.K^T).V, is essentially a KV-store lookup.
The Query is dotted with the Key, then you take the softmax to pick mostly one winning Key (the Key closest to the Query basically), and then use the corresponding Value.
That is really, really close to a KV lookup, except it's a little soft (i.e. can hit multiple Keys), and it can be optimized using gradient descent style methods to find the suitable QKV mappings.
The following is controversial and ill-thought-out, so feel free to flame (I gotta learn somehow!)
Nobody does things for free. We do things because we gain either money or status or pleasure. If you want someone to work for you, and you don't want to pay them money, you have to give them either status or pleasure.
One example of getting people to do things for pleasure is ad-supported social media sites. They are giving people pleasure (modulo engagement psychology) and getting their attention on ads for free.
But let's focus on getting people to do things for status. PhDs are a classic example: if you get a PhD and stay in Academia, your salary is tiny relative to industry. But there is a promise of status ("you're on the frontier of knowledge!"; "people call you 'doctor'!"). The few principal investigators that get the giant grants are successful only because they rely on an army of underpaid experts.
Which means there is an incentive--even if unconscious--to convince people that status is worth the lower salaries. The fights for being first-author, or publishing in a top-tier journal, or even insisting on being called "Doctor" are all competitions for status, because that's what you're getting paid instead of money.
Open Source is the same way. Arguments about purity ("is that really an OSS license") and self-sacrifice ("I won't accept money from corporations") are all evidence that people are earning status instead of money.
By itself, this is not a bad thing (in either OSS or Academia). People should be free to choose how to sell their time. The problem is that those who benefit from the work-for-status arrangement (large corporations, large universities, and their leaders) are incented to use dark patterns to preserve that arrangement.
We're sensitive to social media sites using dark patterns to manipulate people into trading work (or money) for pleasure. We should be equally sensitive to how open-source culture can (even unintentionally) drive people to be underpaid.
The distinction are difficult to grasp, and it doesn't help that meanings evolve. For example, many people today understand "thread" to mean a preemptively scheduled kernel resource with a contiguous region of memory used for storing objects and return addresses, which shares an address space with other such threads. That sort of "thread" is what 10 years ago many people called a LWP (lightweight process), when thread didn't imply preemptive scheduling. And what was a "thread" 10 years ago is often called a "fiber" or "microthread" today. Though I'm coming from the world of Unix. I think connotations had a slightly different evolution in the world of Windows, largely because Unix emphasized the "process" (concurrent execution, non-shared address space) and modern concurrent execution with shared address space were only formalized in the late 1990s, whereas Windows didn't get isolated virtual address spaces until Windows 95, so the primary construct was always a "thread" (with or without preemptive execution, depending on era).
When I say thread in my previous post I'm referring to the abstract construct that represents the flow of control of sequentially executed instructions, regardless of the underlying implementation, and regardless of how you initiate, yield, or resume that control. For languages that support function recursion that necessarily implies some sort of stack (if not multiple stacks) for storing the state of intermediate callers that have invoked another function. Often such a stack is also used to store variables, both as a performance optimization and because many (perhaps most objects) in a program have a lifetime that naturally coincides with the execution of the enclosing function.
Such a "call stack" has historically been implemented as a contiguous region of memory, but some hardware (i.e. IBM mainframes) implement call stacks as linked-lists of activation frames, which effectively is the data structure you're creating when you chain stackless coroutines.
The two best sources I've found that help untangle the mess in both theoretical and practical terms are
Pro jit hacker here. Super cool seeing folks build stuff like this.
Thoughts:
- great to see an SSA pipeline, but that implies that this jit is not ideal for baseline jiting. That’s fine. I guess I would explicitly call out that although it’s light, it is an optimizer.
- which reminds me - a JIT based VM is most defined by how powerful its top JIT is, not how light it is. Maybe since MIR is an optimizer, it’s meant to eventually not be light? It’s generally a bad idea to set out to build a jit, but to then aim in between baseline and full opt. This middle light jits are best built after you’ve evolved your top and bottom tiers (sorta like what happened in V8).
- jit IRs need to be given space to evolve to point directly at the user heap, which precludes any kind of reliable serialization. You can dump IR for debugging but you can’t parse IR. This MIR thing is serializable, which would make it an awesome AOT IR. But the serialization code will get in the way once folks try to implement jit based optimizations (like anything speculative). Best to kill the serialization now before it becomes a problem.
- if the first front end is C11 then you risk biasing the jit to be very C oriented. Languages that need JIts usually also need weird stuff that C doesn’t need. Super important to test a weirder language. Maybe Lua, since it’s weird but simple.
Anyway, this is cool shit. Nice to see people building jits!
Parallelism ended up going off in a few different directions.
For things like running a web service, requests are fast enough, and the real win from parallelism is in handling lots of requests side-by-side. This is where No-GIL comes in.
Within handling a single request, if there are a lot of sub-requests, that's usually handled by async code, but not so much for the async performance win as much as spinning up threads is either expensive or thread pools are a hassle. Remember that async is better for throughput, but worse for latency, and if you're parallelizing a service request, you're probably more worried about latency. Async won mostly on ergonomics.
The other place you see parallelism is large offline jobs. Things like Map-reduce and Presto. Those tend to look like divide-and-conquer problems. GPU model training looks something like this.
What never happened is local, highly parallel algorithms. For a web service, data size is too small to see a latency win, they're complicated, and coordination between threads become costly. The small exceptions are vectorized algorithms, but these run one one core, so there isn't coordination overhead, and online inference, but again, this is heavily vectorized.
Through various market forces, Unix (and its descendants/clones) and Windows killed most of the rest of the OS ecosystem over 20 years ago. There are generations of software engineers and computer scientists who’ve never studied operating systems that weren’t Unix- or Windows-based. Most leading OS textbooks (Tanenbaum’s books, the dinosaur book, Three Easy Pieces) have a Unix slant. Even the systems software research community is heavily Unix-centric; I say that as someone who used to be immersed in the research storage systems community. The only non-Unix or Windows operating systems many practitioners and even researchers may have used in their lives are MS-DOS and the classic Mac OS, and there’s a growing number of people who weren’t even born yet by the time these systems fell out of common use.
However, the history of computing contains examples of other operating systems that didn’t survive the marketplace but have interesting lessons that can apply to improving today’s operating systems. The Unix Hater’s Handbook is a nice example of alternative worlds of computing that were still alive in the 1980s and early 1990s. VMS, IBM mainframe systems, Smalltalk, Symbolics Genera, Xerox Mesa and Cedar, Xerox Interlisp-D, and the Apple Newton were all real-world systems that demonstrate alternatives to the Unix and Windows ways of thinking. Project Oberon is an entire system developed by Wirth (of Pascal fame) whose design goal is to build a complete OS and development environment that is small enough to be understood for pedagogical purposes, similar to MINIX but without any Unix compatibility. Reading the history of failed Apple projects such as the planned Lisp OS for the Newton and the ill-fated Pink/Taligent project are also instructive. Microsoft Research did a lot of interesting research in the 2000s on operating systems implemented in memory-safe languages, notably Singularity and Midori.
From learning about these past projects, we can then imagine future directions for OS design.
Sigbovik is a joke conference put on by CMU students. About 80%-90% of the papers each year are pure jokes, but about 10-20% of them are jokes that have a nontrivial amount of work backing them up. These papers are awesome. Tom publishes at least one every year (sometimes 3-4) and his papers are among the best but there are others that are also awesome. It hits a very fun combination of hard work on trivial problems that is fascinating.
They publish a proceedings every year and they are fun reads.
It's not even private anymore. Anyone can easily find tools on GitHub or other sites[0] that can download widevine'd content. It's easy to find L3 keys on GitHub too. There are public sites[1] that get the decryption keys but using their own keys on the backend if you cant find keys or don't know how to dump the keys. There is an all-in-one tool[2] that lets anyone with 0 knowledge of DRM to dump content from practically any site that uses widevine, costs a leg but it's meant for people who don't want to waste time finding tools and learning how to do it manually.
Another feature that gets a lot of flack from some Linux users is desktop icons. This is something else that a lot of UIs screwed up (maybe stemming from Windows 95? I'm not sure). The classic Mac UI let you drag whatever files you were currently working on to the desktop and do whatever you needed to do. Then when you were done with the files, you could highlight them and select "Put Away" and they'd all get moved back to their original locations. The desktop was a temporary space for what you were actively working on, not a giant catch-all storage location like how modern UIs treat it.