I struggle to comprehend getting this upset about Apple quietly choosing to scale back, delay, or entirely cancel some of their LLM slop features. Even in the scripted demos all of it looks like useless poison, and the less of it that’s genuinely shipped in MacOS and iOS the better.
If anything, joining the GenAI hype-train is the part that damaged Apple’s credibility, and it’s heroic that someone at Apple leadership may be exhibiting the good sense to not release garbage that doesn’t work.
quietly choosing to scale back, delay, or entirely cancel some of their LLM slop features
I feel like the compliant in the blog post is less about that, and more about how they are doing the opposite - they are loudly promoting it, while they’ve delayed it twice without ever showing an actual demo of it working. Everything shown so far has been pre-recorded concepts.
In fact, I feel that Gruber doesn’t really care what’s promised, and more that they keep promising it without showing any sign of ever delivering.
They’re never actually going to do it! It’s a conspiracy to give MS FOMO to put more AI in Windows so it becomes worse quicker, and more people will want to use Apple!
I understand the reflex to call everything “AI”-related “LLM slop”, however, if you think about it, then interpreting natural language within a natural-language context and generating machine commands accordingly is pretty much the thing LLMs are supposed to do.
I think this is completely untrue. RISC-V is real, exists, works, there are hardware products being built, embedded into mass produced devices, etc.
It’s just in the space that most of us are mostly interested in - modern high performance CPUs - the instruction set is maybe 1% of the story. Modern CPUs are one of the most elaborated artifacts of human engineering and result of decades of research and improvements, part of a huge industrial and economical base built around it. That’s not something that can be significantly altered rapidly.
Just look how long it took Arm to get into high performance desktop CPUs. And there was big and important business behind it, with strategy and everything.
Beyond microcontrollers, I really haven’t seen anything remotely usable. I’d love to be wrong though.
I tried to find a Pi Zero replacement, but the few boards available all had terrible performance. The one I bought in the end turned out has an unusable draft implementation of vector instructions and it’s significantly slower than any other Linux board I’ve ever used, from IO to just CPU performance. Not to mention the poor software support (I truly despise device trees at this point).
They’re not asking for high-performance desktop CPUs here though. Judging by the following statement on IRC:
<q66> there isn't one that is as fast as rpi5
<q66> it'd be enough if it was
it sounds like anything that even approaches a current day midrange phone SoC would be enough. RPi5 is 4x Cortex-A76, which is about a midrange phone SoC in 2021.
Last I checked, the most commonly recommended RISC-V boards were slower than a Raspberry Pi 3, and the latest and greatest and most expensive boards were somewhere in the middle between RPi 3 and 4. So yeah, pretty slow.
That’s very misleading. That’s what Geekbench says, but GB isn’t relevant to software package build farms. For building software most common RISC-V SBCs at the moment are far closer to Pi 4 than to Pi 3, ESPECIALLY given that Pi 3 never has more than 1 GB RAM (and Pi4 has never had more than 8 GB) while 8 GB, 16 GB, 32 GB RAM RISC-V SBCs are everywhere and you can get the 128 GB RAM 64 core Milk-V Pioneer for a lot less money than the Ampere Altra they talk about in that post.
Just for a data point, the RP2350 chip in the Raspberry Pi Pico 2 includes a pair of RV32IMACZb* cores as alternates for the main Cortex-M33 cores: you can choose which architecture to boot into. The Pico 2 costs $5 in quantities of 1.
Copyright law protects creative works and provide creators with control.
Copyright law provides copyright owners with control. As many musicians would tell you, these are absolutely not the same thing. And that’s not to mention models like Adobe Firefly that are trained on licensed works.
The labor issues around AI are vast but copyright isn’t the answer.
Fully agree. If your goal was to kill GenAI, doing so based on copyright infringement would honestly have scary implications. I felt like it wasn’t that long ago that we were celebrating the lawsuit where Google Books defended their ability to allow books to be freely indexed and searched (Authors Guild, Inc. v. Google, Inc.
), and when Google argued that their reimplementation of the Java API in Android fell under Fair Use (Google LLC v. Oracle America, Inc.). I feel like OpenAI losing their lawsuit(s) due to copyright infringement could deal a massive blow to what Fair Use protects on the web today (I am by no means a lawyer though)
I don’t think it would have scary implications - both the Google Books and Android cases fell under fair use because they passed the four prong test for being fair use. Commercial LLMs have would have a hard time doing so, given that they are ingesting whole works, and significantly affecting the market of those works.
Fair use decisions are probably the least impactful decisions that can be taken, since they are very specific, so they don’t apply in a straight forward manner in a lot of other cases.
The TypeScript dev lead posted this response about the language choice on Reddit, for anyone who’s curious:
(dev lead of TypeScript here, hi!)
We definitely knew when choosing Go that there were going to be people questioning why we didn’t choose Rust. It’s a good question because Rust is an excellent language, and barring other constraints, is a strong first choice when writing new native code.
Portability (i.e. the ability to make a new codebase that is algorithmically similar to the current one) was always a key constraint here as we thought about how to do this. We tried tons of approaches to get to a representation that would have made that port approach tractable in Rust, but all of them either had unacceptable trade-offs (perf, ergonomics, etc.) or devolved in to “write your own GC”-style strategies. Some of them came close, but often required dropping into lots of unsafe code, and there just didn’t seem to be many combinations of primitives in Rust that allow for an ergonomic port of JavaScript code (which is pretty unsurprising when phrased that way - most languages don’t prioritize making it easy to port from JavaScript/TypeScript!).
In the end we had two options - do a complete from-scrach rewrite in Rust, which could take years and yield an incompatible version of TypeScript that no one could actually use, or just do a port in Go and get something usable in a year or so and have something that’s extremely compatible in terms of semantics and extremely competitive in terms of performance.
And it’s not even super clear what the upside of doing that would be (apart from not having to deal with so many “Why didn’t you choose Rust?” questions). We still want a highly-separated API surface to keep our implementation options open, so Go’s interop shortcomings aren’t particularly relevant. Go has excellent code generation and excellent data representation, just like Rust. Go has excellent concurrency primitives, just like Rust. Single-core performance is within the margin of error. And while there might be a few performance wins to be had by using unsafe code in Go, we have gotten excellent performance and memory usage without using any unsafe primitives.
In our opinion, Rust succeeds wildly at its design goals, but “is straightforward to port to Rust from this particular JavaScript codebase” is very rationally not one of its design goals. It’s not one of Go’s either, but in our case given the way we’ve written the code so far, it does turn out to be pretty good at it.
And it’s not even super clear what the upside of doing that would be (apart from not having to deal with so many “Why didn’t you choose Rust?” questions)
People really miss the forest for the trees.
I looked at the repo and the story seems clear to me: 12 people rewrote the TypeScript compiler in 5 months, getting a 10x speed improvement, with immediate portability to many different platforms, while not having written much Go before in their lives (although they are excellent programmers).
This is precisely the reason why Go was invented in the first place. “Why not Rust?” should not be the first thing that comes to mind.
I honestly do think the “Why not Rust?” question is a valid question to pop into someone’s head before reading the explanation for their choice.
First of all, if you’re the kind of nerd who happens to follow the JavaScript/TypeScript dev ecosystem, you will have seen a fair number of projects either written, or rewritten, in Rust recently. Granted, some tools are also being written/rewritten in other languages like Go and Zig. But, the point is that there’s enough mindshare around Rust in the JS/TS world that it’s fair to be curious why they didn’t choose Rust while other projects did. I don’t think we should assume the question is always antagonistic or from the “Rust Evangelism Strike Force”.
Also, it’s a popular opinion that languages with algebraic data types (among other things) are good candidates for parsers and compilers, so languages like OCaml and Rust might naturally rank highly in languages for consideration.
So, I honestly had the same question, initially. However, upon reading Anders’ explanation, I can absolutely see why Go was a good choice. And your analysis of the development metrics is also very relevant and solid support for their choice!
I guess I’m just saying, the Rust fanboys (myself, included) can be obnoxious, but I hope we don’t swing the pendulum too far the other way and assume that it’s never appropriate to bring Rust into a dev conversation (e.g., there really may be projects that should be rewritten in Rust, even if people might start cringing whenever they hear that now).
While tweaking a parser / interpreter a few years ago written in Go, I specifically replaced a struct with an ‘interface {}’ in order to exercise its pseudo-tagged-union mechanisms. Together with using type-switch form.
These day’s I’d actually make it a closed interface such that it is more akin to a tagged-union. Which I did for another project which was passing around instances of variant-structs (i.e. a tagged union), rather than building an AST.
So it is quite possible to use that pattern in Go as a form of sum-type, if for some reason one is inclined to use Go as the implementation language.
A relevant quote is that C# has “some ahead-of-time compilation options available, but they’re not on all platforms and don’t really have a decade or more of hardening.”
Yeah Hjelsberg also talks about value types being necessary, or at least useful, in making language implementations fast
If you want value types and automatically managed memory, I think your only choices are Go, D, Swift, and C# (and very recently OCaml, though I’m not sure if that is fully done).
I guess Hjelsberg is conceding that value types are a bit “second class” in C#? I think I was surprised by the “class” and “struct” split, which seemed limiting, but I’ve never used it. [1]
And that is a lesson learned from the Oils Python -> C++ translation. We don’t have value types, because statically typed Python doesn’t, and that puts a cap on speed. (But we’re faster than bash in many cases, though slower in some too)
Now that I’ve worked on a garbage collector, I see a sweet spot in languages like Go and C# – they have both value types deallocated on the stack and GC. Both Java and Python lack this semantic, so the GCs have to do more work, and the programmer has less control.
There was also a talk that hinted at some GC-like patterns in Zig, and I proposed that TinyGo get “compressed pointers” like Hotspot and v8, and then you would basically have that:
Yeah, I’m kind of curious about whether OCaml was considered at some point (I asked about this in the Reddit thread, haven’t gotten a reply yet).
OCaml seems much more similar to TS than Go, and has a proven track record when it comes to compilers. Maybe portability issues? (Good portability was mentioned as a must-have IIRC)
Not sure what more TypeScript would have needed. In fact, Flow’s JavaScript parser is available as a separate library, so they would have shaved off at least a month from the proof of concept…
Although thinking about it a bit more, I think Nim, Julia, (and maybe Crystal) are like C#, in that they are not as general as Go / D / Swift.
You don’t have a Foo* type as well as a Foo type, i.e. the layout is orthogonal to whether it’s a value or reference. Instead, Nim apparently has value objects and reference objects. I believe C# has “structs” for values and classes for references.
I think Hjelsberg was hinting at this category when saying Go wins a bit on expressiveness, and it’s also “as close to native as you can get with GC”.
I think the reason this Go’s model is uncommon is because it forces the GC to support interior pointers, which is a significant complication (e.g. it is not supported by WASM GC). Go basically has the C memory model, with garbage collection.
I think C#, Julia, and maybe Nim/Crystal do not support interior pointers (interested in corrections)
Someone should write a survey of how GC tracing works with each language :) (Nim’s default is reference counting without cycle collection.)
Yeah that’s interesting. Julia has a distinction between struct (value) and mutable struct (reference). You can use raw pointers but safe interior references (to an element of an array for example) include a normal reference to the (start of the) backing array, and the index.
I can understand how in Rust you can safely have an interior pointer as the borrow checker ensures a reference to an array element is valid for its lifetime (the array can’t be dropped or resized before the reference is dropped). I’m very curious - I would like to understand how Go’s tracing GC works with interior pointers now! (I would read such a survey).
Ok - Go’s GC seems to track a memory span for each object (struct or array), stored in kind of a span tree (interval tree) for easy lookup given some pointer to chase. Makes sense. I wonder if it smart enough to deallocate anything dangling from non-referenced elements of an array / fields of a struct, or just chooses to be conservative (and if so do users end up accidentally creating memory leaks very often)? What’s the performance impact of all of this compared to runtimes requiring non-interior references? The interior pointers themselves will be a performance win, at the expense of using an interval tree during the mark phase.
It’s been a few years since I’ve written any Go, but I have a vague recollection that the difference between something being heap or stack allocated was (sometimes? always?) implicit based on compiler analysis of how you use the value. Is that right? How easy it, generally, to accidentally make something heap-allocated and GC’d?
That’s the only thing that makes me nervous about that as a selling point for performance. I feel like if I’m worried about stack vs heap or scoped vs memory-managed or whatever, I’d probably prefer something like Swift, Rust, or C# (I’m not familiar at all with how D’s optional GC stuff works).
$ go build -gcflags "-m" main.go
.\main.go:8:14: *y escapes to heap
.\main.go:11:13: x does not escape
So the toolchain is pretty transparent. This is actually something I would like for the Oils Python->C++ compiler, since we have many things that are “obviously” locals that end up being heap allocated. And some not so obvious cases. But I think having some simple escape analysis would be great.
Hejlsberg said they got about 3x performance from native compilation and value types, which also halved the memory usage of the compiler. They got a further 3x from shared-memory multithreading. He talked a lot about how neither of those are possible with the JavaScript runtime, which is why it wasn’t possible to make tsc 10x faster while keeping it written in TypeScript.
Yeah but I can get bigger memory wins while staying inside JS by sharing the data structures between many tools that currently hold copies of the same data: the linter, the pretty-formatter, the syntax highlighter, and the type checker
I can do this because I make my syntax tree nodes immutable! TS cannot make their syntax tree nodes immutable (even in JS where it’s possible) because they rely on the node.parent reference. Because their nodes are mutable-but-typed-as-immutable, these nodes can never safely be passed as arguments outside the bounds of the TS ecosystem, a limitation that precludes the kind of cross-tool syntax tree reuse that I see as being the way forward
Hejlsberg said that the TypeScript syntax tree nodes are, in fact, immutable. This was crucial for parallelizing tsgo: it parses all the source files in parallel in the first phase, then typechecks in parallel in the second phase. The parse trees from the first phase are shared by all threads in the second phase. The two phases spread the work across threads differently. He talks about that kind of sharing and threading being impractical in JavaScript.
In fact he talks about tsc being designed around immutable and incrementally updatable data structures right from the start. It was one of the early non-batch compilers, hot on the heels of Roslyn, both being designed to support IDEs.
It’s true that I haven’t watched the interview yet, but I have confirmed with the team that the nodes are not immutable. My context is different than Hejlsberg’s context. For Hejlsberg if something is immutable within the boundaries of TS, it’s immutable. Since I work on JS APIs if something isn’t actually locked down with Object.freeze it isn’t immutable and can’t safely be treated as such. They can’t actually lock their objects down because they don’t actually completely follow the rules of immutability, and the biggest thing they do that you just can’t do with (real, proper) immutable structures is have a node.parent reference.
So they have this kinda-immutable tech, but those guarantees only hold if all the code that ever holds a reference to the node is TS code. That is why all this other infrastructure that could stand to benefit from a shared standard format for frozen nodes can’t: it’s outside the walls of the TS fiefdom, so the nodes are meant to be used as immutable but any JS code (or any-typed code) the trees are ever exposed to would have the potential to ruin them by mutating the supposedly-immutable data
To be more specific about the node.parent reference, if your tree is really truly immutable you need to replace a leaf node you must replace all the nodes on the direct path from the root to that leaf. TS does this, which is good.
The bad part is that then all the nodes you didn’t replace have chains of node.parent references that lead to the old root instead of the new one. Fixing this with immutable nodes would mean replacing every node in the tree, so the only alternative is to mutate node.parent, which means that 1) you can’t actually Object.freeze(node) and 2) you don’t get all the wins of immutability since the old data structure is corrupted by the creation of the new one.
See https://ericlippert.com/2012/06/08/red-green-trees/ for why Roslyn’s key innovation in incremental syntax trees was actually breaking the node.parent reference by splitting into the red and green trees, or as I call them paths and nodes. Nodes are deeply immutable trees and have no parents. Paths are like an address in a particular tree, tracking a node and its parents.
Hm yeah it was a very good talk. My summary of the type checking part is
The input to the type checker is immutable ASTs
That is, parsing is “embarassingly parallel”, and done per file
They currently divide the program into 4 parts (e.g. 100 files turns into 4 groups of 25 files), and they do what I’d call “soft sharding”.
That is, the translation units aren’t completely independent. Type checking isn’t embarassingly parallel. But you can still parallelize it and still get enough speedup – he says ~3x from parallelism, and ~3x from Go’s better single core perf, which gives you ~10x overall.
What wasn’t said:
I guess you have to de-duplicate the type errors? Because some type errors might come twice, since you are duplicating some work
Why the sharding is in 4 parts, and not # CPUs. Even dev machines have 8-16 cores these days, and servers can have 64-128 cores.
I guess this is just because, empirically, you don’t get more than 3x speedup.
That is interesting, but now I think it shows that TypeScript is not designed for parallel type checking. I’m not sure if other compilers do better though, like Rust (?) Apparently rustc uses the Rayon threading library. Though it’s hard to compare, since it also has to generate code
A separate thing I found kinda disappointing from the talk is that TypeScript is literally what the JavaScript code was. There was never a spec and will never be one. They have to do a line-for-line port.
There was somebody who made a lot of noise on the Github issue tracker about this, and it was basically closed “Won’t Fix” because “nobody who understands TypeScript well enough has enough time to work on a spec”. (Don’t have a link right now, but I saw it a few months ago)
Why the sharding is in 4 parts, and not # CPUs. Even dev machines have 8-16 cores these days, and servers can have 64-128 cores.
Pretty sure he said it was an arbitrary choice and they’d explore changing it. The ~10x optimization they’ve gotten so far is enough by itself to keep the project moving. Further optimization is bound to happen later.
I’m not sure if other compilers do better though, like Rust (?) Apparently rustc uses the Rayon threading library.
Work has been going on for years to parallelize rust’s frontend, but it apparently still has some issues, and so isn’t quite ready for prime time just yet, though it’s expected to be ready in the near term.
Under 8 cores and 8 threads, the parallel front end can reduce the clean build (cargo build with -Z threads=8 option) time by about 30% on average. (These crates are from compiler-benchmarks of rustc-perf)
I guess this is just because, empirically, you don’t get more than 3x speedup.
In my experience, once you start to do things “per core” and want to actually get performance out of it, you end up having to pay attention to caches, and get a bit into the weeds. Given just arbitrarily splitting up the work as part of the port has given a 10x speed increase, it’s likely they just didn’t feel like putting in the effort.
But check the chapters, they’re really split into good details. The video is interesting anyway, technically focused, no marketing spam. I can also highly recommend watching it.
Another point on “why Go and not C#” is that, he said, their current (typescript) compiler is highly functional, they use no classes at all. And Go is “just functions and data structures”, where C# has “a lot of classes”. Paraphrasing a little, but that’s roughly what he said.
Acknowledging some weak spots, Go’s in-proc JS interop story is not as good as some of its alternatives. We have upcoming plans to mitigate this, and are committed to offering a performant and ergonomic JS API.
It was an absolute delight to discover that modern CSS, with its support for variables, can be used without a build process.
I went on my journey to that realization over the last 18 months. I haven’t phased out SCSS everywhere yet, but once native CSS nesting crosses 95% support, I’ll default to vanilla CSS, and be skeptical of deviating in any new projects.
I was curious how this looked in terms of code… I’m a little surprised at how concise/spartan it is. There doesn’t seem to be a css reset even? Is that not needed anymore?
There doesn’t seem to be a css reset even? Is that not needed anymore?
Modern browsers differ much less in terms of default styles, so not really, unless you want absolutely pixel perfect equivalent design between browsers.
The main purpose was to DRY up the CSS. It makes theming possible but not really something that makes sense for Sidekiq and my users so I haven’t spent any time or thought on it.
That makes sense. I suppose I expected to find a complete bootstrap replacement - rather than “just the laces we thread”. Obviously the simpler solution is better when that’s all you need.
It’s wonderful that it’s pretty straightforward to netboot a VAX, SPARC, or Alpha computer and root on NFS (it’s a pain on x86 / amd64, though). There are lots of lessons that informed the design of these kinds of machines, and it’s unfortunate that x86 / amd64 never learned them, or where there are similarities, they’re poorly implemented. The best servers give you 100% control of anything you need to do via the serial port.
NetBSD/sparc packages aren’t currently being built because of how much power the systems to build them take, but I’m actively looking for a place to host / colocate. In the meanwhile, there’s a non-trivial number of binary packages for the JavaStation here:
If we talk pre-UEFI PXE, the things everyone implemented is extremely simple, and several vendors still managed to mess things up to the point that the “Universal Network Driver Interface” is hardly universal.
I don’t think I want to see what their attempt at something like NFS would’ve looked like.
I’m surprised it works on Windows, because the kernel docs suggest it shouldn’t. DRM’d media is sent to the GPU as an encrypted stream, with the key securely exchanged between the GPU and the server. It’s decrypted as a special kind of texture and you can’t (at least in theory) copy that back, you can just composite it into frames that are sent to the display (the connection between the display and GPU is also end-to-end encrypted, though I believe this is completely broken).
My understanding of Widevine was that it required this trusted path to play HD content and would downgrade to SD if it didn’t exist.
No one is going to create bootleg copies of DRM-protected video one screenshotted still frame at a time — and even if they tried, they’d be capturing only the images, not the sound
If you have a path that goes from GPU texture back to the CPU, then you can feed this straight back into something that recompresses the video and save it. And I don’t know why you’d think this wouldn’t give you sound: secure path for the sound usually goes the same way, but most things also support sound via other paths because headphones typically don’t support the secure path. It’s trivial to write an Audio Unit for macOS that presents as an output device and writes audio to a file (several exist, I think there’s even an Apple-provided sample that does). That just leaves you having to synchronise the audio and video streams.
I’m pretty sure that what Gruber is describing is basically just “hardware acceleration is not being enabled on many Windows systems”, but because he has his own little narrative in his head he goes on about how somehow the Windows graphics stack must be less integrated. Windows is the primary platform for so much of this stuff!
I would discount this entire article’s technical contents and instead find some other source for finding out why this is the case.
Well it depends on the type of acceleration we’re speaking of. But I’ve tried forcing hardware acceleration on video decode and honestly you’d be surprised how much it failed and I did this on rather new hardware. It was actually shockingly unreliable. I’m fairly certain it’s significantly worse if you extend your view to older hardware and other vendors.
I’m also fairly sure, judging by people’s complaints, that throwing variable refresh rate, higher bit depths and hardware-accelerated scheduling in the mix has not resulted in neither flagship reliability or performance.
It can be the primary platform but this doesn’t mean it’s good or always does what it should or promises it’ll do.
I think it means: enabling the feature to screenshot DRM protected media would not by itself enable piracy, since people would not use screenshots to pirate media frame at a time.
What you are saying reads like “one technical implementation of allowing screenshots would enable piracy.” I trust that you’re probably right, but that doesn’t contradict the point that people would not use that UI affordance itself for piracy.
No one would use screenshots for piracy because all the DRM is already cracked. Every 4k Netflix, Disney, etc, show is already on piracy websites, and they’re not even re-encoded from the video output or anything, it’s straight up the original h264 or h265 video stream. Same with BluRays.
Yup, if you go through GitHub there are several reverse-engineered implementations of widevine, which just allow you to decrypt the video stream itself with no need to reencode. That then moves the hard part to getting the key - fairly easy to get the lower security ones since you can just root an Android device (and possibly even get it from Google’s official emulator? At least it supports playing widevine video!), the higher security ones are hardcoded into secure enclaves on the GPU/CPU/Video decoder though, but clearly people have found ways to extract them - those no-name TV streaming boxes don’t exactly have a good track record of security, so if I were to guess that’s where they’re getting the keys.
Still, no point blocking screenshots - pirates are already able to decrypt the video file itself which is way better than reencoding.
Those no-name TV streaming boxes usually use the vendor’s recommended way to do it, which is mostly secure, but it’s not super-unusual for provisioning data to be never deleted off the filesystem, even on big brand devices.
The bigger issue with the DRM ecosystem is that all it takes is for one secure enclave implementation to be cracked, and they have a near infinite series of keys to use. Do it on a popular device, and Google can’t revoke the entire series either.
Personally, I’m willing to bet the currently used L1 keys have come off Tegra based devices, since they have a compromised boot chain through the RCM exploit, as made famous by the Nintendo Switch.
For the ringer, you could play around with a boost converter. You can get tiny boards on aliexpress for about €2.
I struggle to comprehend getting this upset about Apple quietly choosing to scale back, delay, or entirely cancel some of their LLM slop features. Even in the scripted demos all of it looks like useless poison, and the less of it that’s genuinely shipped in MacOS and iOS the better.
If anything, joining the GenAI hype-train is the part that damaged Apple’s credibility, and it’s heroic that someone at Apple leadership may be exhibiting the good sense to not release garbage that doesn’t work.
I feel like the compliant in the blog post is less about that, and more about how they are doing the opposite - they are loudly promoting it, while they’ve delayed it twice without ever showing an actual demo of it working. Everything shown so far has been pre-recorded concepts.
In fact, I feel that Gruber doesn’t really care what’s promised, and more that they keep promising it without showing any sign of ever delivering.
They’re never actually going to do it! It’s a conspiracy to give MS FOMO to put more AI in Windows so it becomes worse quicker, and more people will want to use Apple!
I understand the reflex to call everything “AI”-related “LLM slop”, however, if you think about it, then interpreting natural language within a natural-language context and generating machine commands accordingly is pretty much the thing LLMs are supposed to do.
So I don’t think this is helpful.
RISC-V is like quantum…always just a few years away
I think this is completely untrue. RISC-V is real, exists, works, there are hardware products being built, embedded into mass produced devices, etc.
It’s just in the space that most of us are mostly interested in - modern high performance CPUs - the instruction set is maybe 1% of the story. Modern CPUs are one of the most elaborated artifacts of human engineering and result of decades of research and improvements, part of a huge industrial and economical base built around it. That’s not something that can be significantly altered rapidly.
Just look how long it took Arm to get into high performance desktop CPUs. And there was big and important business behind it, with strategy and everything.
Beyond microcontrollers, I really haven’t seen anything remotely usable. I’d love to be wrong though.
I tried to find a Pi Zero replacement, but the few boards available all had terrible performance. The one I bought in the end turned out has an unusable draft implementation of vector instructions and it’s significantly slower than any other Linux board I’ve ever used, from IO to just CPU performance. Not to mention the poor software support (I truly despise device trees at this point).
Could be worse, could be ACPI.
The Milk-V duo beats the Pi Zero in every way, and that draft 0.7.1 vector implementation is perfectly usable using asm or C intrinsics in GCC 14+.
They’re not asking for high-performance desktop CPUs here though. Judging by the following statement on IRC:
it sounds like anything that even approaches a current day midrange phone SoC would be enough. RPi5 is 4x Cortex-A76, which is about a midrange phone SoC in 2021.
Last I checked, the most commonly recommended RISC-V boards were slower than a Raspberry Pi 3, and the latest and greatest and most expensive boards were somewhere in the middle between RPi 3 and 4. So yeah, pretty slow.
Ah, roughly comparable to a Sun E450 then
(!)
That’s very misleading. That’s what Geekbench says, but GB isn’t relevant to software package build farms. For building software most common RISC-V SBCs at the moment are far closer to Pi 4 than to Pi 3, ESPECIALLY given that Pi 3 never has more than 1 GB RAM (and Pi4 has never had more than 8 GB) while 8 GB, 16 GB, 32 GB RAM RISC-V SBCs are everywhere and you can get the 128 GB RAM 64 core Milk-V Pioneer for a lot less money than the Ampere Altra they talk about in that post.
We would have had faster than Pi 5 RISC-V machines right around now if US sanctions hadn’t nerfed the SG2380.
Just for a data point, the RP2350 chip in the Raspberry Pi Pico 2 includes a pair of RV32IMACZb* cores as alternates for the main Cortex-M33 cores: you can choose which architecture to boot into. The Pico 2 costs $5 in quantities of 1.
Copyright law provides copyright owners with control. As many musicians would tell you, these are absolutely not the same thing. And that’s not to mention models like Adobe Firefly that are trained on licensed works.
The labor issues around AI are vast but copyright isn’t the answer.
Fully agree. If your goal was to kill GenAI, doing so based on copyright infringement would honestly have scary implications. I felt like it wasn’t that long ago that we were celebrating the lawsuit where Google Books defended their ability to allow books to be freely indexed and searched (Authors Guild, Inc. v. Google, Inc. ), and when Google argued that their reimplementation of the Java API in Android fell under Fair Use (Google LLC v. Oracle America, Inc.). I feel like OpenAI losing their lawsuit(s) due to copyright infringement could deal a massive blow to what Fair Use protects on the web today (I am by no means a lawyer though)
I don’t think it would have scary implications - both the Google Books and Android cases fell under fair use because they passed the four prong test for being fair use. Commercial LLMs have would have a hard time doing so, given that they are ingesting whole works, and significantly affecting the market of those works.
Fair use decisions are probably the least impactful decisions that can be taken, since they are very specific, so they don’t apply in a straight forward manner in a lot of other cases.
The TypeScript dev lead posted this response about the language choice on Reddit, for anyone who’s curious:
Source: https://www.reddit.com/r/typescript/comments/1j8s467/comment/mh7ni9g
People really miss the forest for the trees.
I looked at the repo and the story seems clear to me: 12 people rewrote the TypeScript compiler in 5 months, getting a 10x speed improvement, with immediate portability to many different platforms, while not having written much Go before in their lives (although they are excellent programmers).
This is precisely the reason why Go was invented in the first place. “Why not Rust?” should not be the first thing that comes to mind.
I honestly do think the “Why not Rust?” question is a valid question to pop into someone’s head before reading the explanation for their choice.
First of all, if you’re the kind of nerd who happens to follow the JavaScript/TypeScript dev ecosystem, you will have seen a fair number of projects either written, or rewritten, in Rust recently. Granted, some tools are also being written/rewritten in other languages like Go and Zig. But, the point is that there’s enough mindshare around Rust in the JS/TS world that it’s fair to be curious why they didn’t choose Rust while other projects did. I don’t think we should assume the question is always antagonistic or from the “Rust Evangelism Strike Force”.
Also, it’s a popular opinion that languages with algebraic data types (among other things) are good candidates for parsers and compilers, so languages like OCaml and Rust might naturally rank highly in languages for consideration.
So, I honestly had the same question, initially. However, upon reading Anders’ explanation, I can absolutely see why Go was a good choice. And your analysis of the development metrics is also very relevant and solid support for their choice!
I guess I’m just saying, the Rust fanboys (myself, included) can be obnoxious, but I hope we don’t swing the pendulum too far the other way and assume that it’s never appropriate to bring Rust into a dev conversation (e.g., there really may be projects that should be rewritten in Rust, even if people might start cringing whenever they hear that now).
While tweaking a parser / interpreter a few years ago written in Go, I specifically replaced a struct with an ‘interface {}’ in order to exercise its pseudo-tagged-union mechanisms. Together with using type-switch form.
https://github.com/danos/yang/commit/c98b220f6a1da7eaffbefe464fd9e734da553af0
These day’s I’d actually make it a closed interface such that it is more akin to a tagged-union. Which I did for another project which was passing around instances of variant-structs (i.e. a tagged union), rather than building an AST.
So it is quite possible to use that pattern in Go as a form of sum-type, if for some reason one is inclined to use Go as the implementation language.
That is great explanation of “Why Go and not Rust?”
If you’re looking for “Why Go and not AOT-compiled C#?” see here: https://youtu.be/10qowKUW82U?t=1154s
A relevant quote is that C# has “some ahead-of-time compilation options available, but they’re not on all platforms and don’t really have a decade or more of hardening.”
That interview is really interesting, worth watching the whole thing.
Yeah Hjelsberg also talks about value types being necessary, or at least useful, in making language implementations fast
If you want value types and automatically managed memory, I think your only choices are Go, D, Swift, and C# (and very recently OCaml, though I’m not sure if that is fully done).
I guess Hjelsberg is conceding that value types are a bit “second class” in C#? I think I was surprised by the “class” and “struct” split, which seemed limiting, but I’ve never used it. [1]
And that is a lesson learned from the Oils Python -> C++ translation. We don’t have value types, because statically typed Python doesn’t, and that puts a cap on speed. (But we’re faster than bash in many cases, though slower in some too)
Related comment about GC and systems languages (e.g. once you have a million lines of C++, you probably want GC): https://lobste.rs/s/gpb0qh/garbage_collection_for_systems#c_rrypks
There was also a talk that hinted at some GC-like patterns in Zig, and I proposed that TinyGo get “compressed pointers” like Hotspot and v8, and then you would basically have that:
https://lobste.rs/s/2ah6bi/programming_without_pointers#c_5g2nat
[1] BTW Guy Steele’s famous 1998 “growing a language” actually advocated value types in Java. AFAIK as of 2025, “Project Valhalla” has not landed yet
Compilers written in OCaml are famous for being super-fast. See eg OCaml itself, Flow, Haxe, BuckleScript (now ReScript).
Yeah, I’m kind of curious about whether OCaml was considered at some point (I asked about this in the Reddit thread, haven’t gotten a reply yet).
OCaml seems much more similar to TS than Go, and has a proven track record when it comes to compilers. Maybe portability issues? (Good portability was mentioned as a must-have IIRC)
Maybe, but given that Flow, its main competitor, distributes binaries for all major platforms: https://github.com/facebook/flow/releases/tag/v0.264.0
Not sure what more TypeScript would have needed. In fact, Flow’s JavaScript parser is available as a separate library, so they would have shaved off at least a month from the proof of concept…
Also Nim.
Also Julia.
There surely are others.
Yes good points, I left out Nim and Julia. And apparently Crystal - https://colinsblog.net/2023-03-09-values-and-references/
Although thinking about it a bit more, I think Nim, Julia, (and maybe Crystal) are like C#, in that they are not as general as Go / D / Swift.
You don’t have a
Foo*type as well as aFootype, i.e. the layout is orthogonal to whether it’s a value or reference. Instead, Nim apparently has value objects and reference objects. I believe C# has “structs” for values and classes for references.I think Hjelsberg was hinting at this category when saying Go wins a bit on expressiveness, and it’s also “as close to native as you can get with GC”.
I think the reason this Go’s model is uncommon is because it forces the GC to support interior pointers, which is a significant complication (e.g. it is not supported by WASM GC). Go basically has the C memory model, with garbage collection.
I think C#, Julia, and maybe Nim/Crystal do not support interior pointers (interested in corrections)
Someone should write a survey of how GC tracing works with each language :) (Nim’s default is reference counting without cycle collection.)
Yeah that’s interesting. Julia has a distinction between
struct(value) andmutable struct(reference). You can use raw pointers but safe interior references (to an element of an array for example) include a normal reference to the (start of the) backing array, and the index.I can understand how in Rust you can safely have an interior pointer as the borrow checker ensures a reference to an array element is valid for its lifetime (the array can’t be dropped or resized before the reference is dropped). I’m very curious - I would like to understand how Go’s tracing GC works with interior pointers now! (I would read such a survey).
Ok - Go’s GC seems to track a memory span for each object (struct or array), stored in kind of a span tree (interval tree) for easy lookup given some pointer to chase. Makes sense. I wonder if it smart enough to deallocate anything dangling from non-referenced elements of an array / fields of a struct, or just chooses to be conservative (and if so do users end up accidentally creating memory leaks very often)? What’s the performance impact of all of this compared to runtimes requiring non-interior references? The interior pointers themselves will be a performance win, at the expense of using an interval tree during the mark phase.
https://forum.golangbridge.org/t/how-gc-handles-interior-pointer/36195/5
It’s been a few years since I’ve written any Go, but I have a vague recollection that the difference between something being heap or stack allocated was (sometimes? always?) implicit based on compiler analysis of how you use the value. Is that right? How easy it, generally, to accidentally make something heap-allocated and GC’d?
That’s the only thing that makes me nervous about that as a selling point for performance. I feel like if I’m worried about stack vs heap or scoped vs memory-managed or whatever, I’d probably prefer something like Swift, Rust, or C# (I’m not familiar at all with how D’s optional GC stuff works).
Yes, that is a bit of control you give up with Go. Searching for “golang escape analysis”, this article is helpful:
https://medium.com/@trinad536/escape-analysis-in-golang-fc81b78f3550
So the toolchain is pretty transparent. This is actually something I would like for the Oils Python->C++ compiler, since we have many things that are “obviously” locals that end up being heap allocated. And some not so obvious cases. But I think having some simple escape analysis would be great.
Yes, the stack/heap distinction is made by the compiler, not the programmer, in Go.
Why did you leave JS/TS off the list? They seem to have left it off too and that confuses me deeply because it also has everything they need
Hejlsberg said they got about 3x performance from native compilation and value types, which also halved the memory usage of the compiler. They got a further 3x from shared-memory multithreading. He talked a lot about how neither of those are possible with the JavaScript runtime, which is why it wasn’t possible to make tsc 10x faster while keeping it written in TypeScript.
Yeah but I can get bigger memory wins while staying inside JS by sharing the data structures between many tools that currently hold copies of the same data: the linter, the pretty-formatter, the syntax highlighter, and the type checker
I can do this because I make my syntax tree nodes immutable! TS cannot make their syntax tree nodes immutable (even in JS where it’s possible) because they rely on the
node.parentreference. Because their nodes are mutable-but-typed-as-immutable, these nodes can never safely be passed as arguments outside the bounds of the TS ecosystem, a limitation that precludes the kind of cross-tool syntax tree reuse that I see as being the way forwardHejlsberg said that the TypeScript syntax tree nodes are, in fact, immutable. This was crucial for parallelizing tsgo: it parses all the source files in parallel in the first phase, then typechecks in parallel in the second phase. The parse trees from the first phase are shared by all threads in the second phase. The two phases spread the work across threads differently. He talks about that kind of sharing and threading being impractical in JavaScript.
In fact he talks about tsc being designed around immutable and incrementally updatable data structures right from the start. It was one of the early non-batch compilers, hot on the heels of Roslyn, both being designed to support IDEs.
Really, you should watch the interview https://youtu.be/10qowKUW82U
AIUI a typical LSP implementation integrates all the tools you listed so they are sharing a syntax tree already.
It’s true that I haven’t watched the interview yet, but I have confirmed with the team that the nodes are not immutable. My context is different than Hejlsberg’s context. For Hejlsberg if something is immutable within the boundaries of TS, it’s immutable. Since I work on JS APIs if something isn’t actually locked down with
Object.freezeit isn’t immutable and can’t safely be treated as such. They can’t actually lock their objects down because they don’t actually completely follow the rules of immutability, and the biggest thing they do that you just can’t do with (real, proper) immutable structures is have anode.parentreference.So they have this kinda-immutable tech, but those guarantees only hold if all the code that ever holds a reference to the node is TS code. That is why all this other infrastructure that could stand to benefit from a shared standard format for frozen nodes can’t: it’s outside the walls of the TS fiefdom, so the nodes are meant to be used as immutable but any JS code (or any-typed code) the trees are ever exposed to would have the potential to ruin them by mutating the supposedly-immutable data
To be more specific about the
node.parentreference, if your tree is really truly immutable you need to replace a leaf node you must replace all the nodes on the direct path from the root to that leaf. TS does this, which is good.The bad part is that then all the nodes you didn’t replace have chains of
node.parentreferences that lead to the old root instead of the new one. Fixing this with immutable nodes would mean replacing every node in the tree, so the only alternative is to mutatenode.parent, which means that 1) you can’t actuallyObject.freeze(node)and 2) you don’t get all the wins of immutability since the old data structure is corrupted by the creation of the new one.See https://ericlippert.com/2012/06/08/red-green-trees/ for why Roslyn’s key innovation in incremental syntax trees was actually breaking the
node.parentreference by splitting into the red and green trees, or as I call them paths and nodes. Nodes are deeply immutable trees and have no parents. Paths are like an address in a particular tree, tracking a node and its parents.You are not joking, just the hack to make type checking itself parallel is well worth an entire hour!
Hm yeah it was a very good talk. My summary of the type checking part is
That is, the translation units aren’t completely independent. Type checking isn’t embarassingly parallel. But you can still parallelize it and still get enough speedup – he says ~3x from parallelism, and ~3x from Go’s better single core perf, which gives you ~10x overall.
What wasn’t said:
I guess this is just because, empirically, you don’t get more than 3x speedup.
That is interesting, but now I think it shows that TypeScript is not designed for parallel type checking. I’m not sure if other compilers do better though, like Rust (?) Apparently rustc uses the Rayon threading library. Though it’s hard to compare, since it also has to generate code
A separate thing I found kinda disappointing from the talk is that TypeScript is literally what the JavaScript code was. There was never a spec and will never be one. They have to do a line-for-line port.
There was somebody who made a lot of noise on the Github issue tracker about this, and it was basically closed “Won’t Fix” because “nobody who understands TypeScript well enough has enough time to work on a spec”. (Don’t have a link right now, but I saw it a few months ago)
Pretty sure he said it was an arbitrary choice and they’d explore changing it. The ~10x optimization they’ve gotten so far is enough by itself to keep the project moving. Further optimization is bound to happen later.
Work has been going on for years to parallelize rust’s frontend, but it apparently still has some issues, and so isn’t quite ready for prime time just yet, though it’s expected to be ready in the near term.
In my experience, once you start to do things “per core” and want to actually get performance out of it, you end up having to pay attention to caches, and get a bit into the weeds. Given just arbitrarily splitting up the work as part of the port has given a 10x speed increase, it’s likely they just didn’t feel like putting in the effort.
Can you share the timestamp to the discussion of this hack, for those who don’t have one hour?
I think this one: https://www.youtube.com/watch?v=10qowKUW82U&t=2522s
But check the chapters, they’re really split into good details. The video is interesting anyway, technically focused, no marketing spam. I can also highly recommend watching it.
Another point on “why Go and not C#” is that, he said, their current (typescript) compiler is highly functional, they use no classes at all. And Go is “just functions and data structures”, where C# has “a lot of classes”. Paraphrasing a little, but that’s roughly what he said.
They also posted a (slightly?) different response on GitHub: https://github.com/microsoft/typescript-go/discussions/411
Yes please!
Congrats on the release!
I went on my journey to that realization over the last 18 months. I haven’t phased out SCSS everywhere yet, but once native CSS nesting crosses 95% support, I’ll default to vanilla CSS, and be skeptical of deviating in any new projects.
I was curious how this looked in terms of code… I’m a little surprised at how concise/spartan it is. There doesn’t seem to be a css reset even? Is that not needed anymore?
Stylesheet:
https://github.com/sidekiq/sidekiq/blob/main/web/assets/stylesheets/style.css
Layout (html page template):
https://github.com/sidekiq/sidekiq/blob/main/web/views/layout.erb
A view:
https://github.com/sidekiq/sidekiq/blob/main/web/views/scheduled.erb
Looks quite reasonable - but also looks like they’re not really using their semantic variables that much (yet)? Like theme/custom color support?
Modern browsers differ much less in terms of default styles, so not really, unless you want absolutely pixel perfect equivalent design between browsers.
The main purpose was to DRY up the CSS. It makes theming possible but not really something that makes sense for Sidekiq and my users so I haven’t spent any time or thought on it.
That makes sense. I suppose I expected to find a complete bootstrap replacement - rather than “just the laces we thread”. Obviously the simpler solution is better when that’s all you need.
It’s wonderful that it’s pretty straightforward to netboot a VAX, SPARC, or Alpha computer and root on NFS (it’s a pain on x86 / amd64, though). There are lots of lessons that informed the design of these kinds of machines, and it’s unfortunate that x86 / amd64 never learned them, or where there are similarities, they’re poorly implemented. The best servers give you 100% control of anything you need to do via the serial port.
NetBSD/sparc packages aren’t currently being built because of how much power the systems to build them take, but I’m actively looking for a place to host / colocate. In the meanwhile, there’s a non-trivial number of binary packages for the JavaStation here:
https://cdn.netbsd.org/pub/pkgsrc/packages/NetBSD/sparc/10.1/
Glad to see old hardware getting preserved :)
If we talk pre-UEFI PXE, the things everyone implemented is extremely simple, and several vendors still managed to mess things up to the point that the “Universal Network Driver Interface” is hardly universal.
I don’t think I want to see what their attempt at something like NFS would’ve looked like.
This is extremely cool. Anyone with one knocking around willing to profile the rise and fall times? :)
I’m surprised it works on Windows, because the kernel docs suggest it shouldn’t. DRM’d media is sent to the GPU as an encrypted stream, with the key securely exchanged between the GPU and the server. It’s decrypted as a special kind of texture and you can’t (at least in theory) copy that back, you can just composite it into frames that are sent to the display (the connection between the display and GPU is also end-to-end encrypted, though I believe this is completely broken).
My understanding of Widevine was that it required this trusted path to play HD content and would downgrade to SD if it didn’t exist.
If you have a path that goes from GPU texture back to the CPU, then you can feed this straight back into something that recompresses the video and save it. And I don’t know why you’d think this wouldn’t give you sound: secure path for the sound usually goes the same way, but most things also support sound via other paths because headphones typically don’t support the secure path. It’s trivial to write an Audio Unit for macOS that presents as an output device and writes audio to a file (several exist, I think there’s even an Apple-provided sample that does). That just leaves you having to synchronise the audio and video streams.
I’m pretty sure that what Gruber is describing is basically just “hardware acceleration is not being enabled on many Windows systems”, but because he has his own little narrative in his head he goes on about how somehow the Windows graphics stack must be less integrated. Windows is the primary platform for so much of this stuff!
I would discount this entire article’s technical contents and instead find some other source for finding out why this is the case.
Well it depends on the type of acceleration we’re speaking of. But I’ve tried forcing hardware acceleration on video decode and honestly you’d be surprised how much it failed and I did this on rather new hardware. It was actually shockingly unreliable. I’m fairly certain it’s significantly worse if you extend your view to older hardware and other vendors.
I’m also fairly sure, judging by people’s complaints, that throwing variable refresh rate, higher bit depths and hardware-accelerated scheduling in the mix has not resulted in neither flagship reliability or performance.
It can be the primary platform but this doesn’t mean it’s good or always does what it should or promises it’ll do.
Wait wait wait is this,,, checks URL, oh, lmao. Yeah Gruber is useless there’s literally no point in ever reading a single word he says.
I think it means: enabling the feature to screenshot DRM protected media would not by itself enable piracy, since people would not use screenshots to pirate media frame at a time.
What you are saying reads like “one technical implementation of allowing screenshots would enable piracy.” I trust that you’re probably right, but that doesn’t contradict the point that people would not use that UI affordance itself for piracy.
No one would use screenshots for piracy because all the DRM is already cracked. Every 4k Netflix, Disney, etc, show is already on piracy websites, and they’re not even re-encoded from the video output or anything, it’s straight up the original h264 or h265 video stream. Same with BluRays.
Yup, if you go through GitHub there are several reverse-engineered implementations of widevine, which just allow you to decrypt the video stream itself with no need to reencode. That then moves the hard part to getting the key - fairly easy to get the lower security ones since you can just root an Android device (and possibly even get it from Google’s official emulator? At least it supports playing widevine video!), the higher security ones are hardcoded into secure enclaves on the GPU/CPU/Video decoder though, but clearly people have found ways to extract them - those no-name TV streaming boxes don’t exactly have a good track record of security, so if I were to guess that’s where they’re getting the keys.
Still, no point blocking screenshots - pirates are already able to decrypt the video file itself which is way better than reencoding.
Those no-name TV streaming boxes usually use the vendor’s recommended way to do it, which is mostly secure, but it’s not super-unusual for provisioning data to be never deleted off the filesystem, even on big brand devices.
The bigger issue with the DRM ecosystem is that all it takes is for one secure enclave implementation to be cracked, and they have a near infinite series of keys to use. Do it on a popular device, and Google can’t revoke the entire series either.
Personally, I’m willing to bet the currently used L1 keys have come off Tegra based devices, since they have a compromised boot chain through the RCM exploit, as made famous by the Nintendo Switch.
Pre-DoD Speed Ripper early DVD rips jacked into a less-than-protected PowerDVD player and did just screenshot every frame.