This is such a pedantic point IMO. C is low level because it makes it very easy to work with machine language/assembly and do stuff like this (LLM assisted example follows):
This is something "low level" programmers use very often to realize the benefits of a high-level language while exercising explicit control over using specific hardware instructions (vp2intersectd being an AVX-512 instruction used in highly optimized search algorithm impls).
Obviously if you rely on implicit behavior from the compiler to optimize your code you are no longer "low level". But if you can quickly and easily drop into machine-level instructions to provide explicit implementation semantics, and the language indeed makes that relatively simple and easy to do, that sure seems "low level" to me
“Low level language” is one of those terms like “VLSI” (very large scale integration) where they defined it in the 70’s or something, so the academic definition is out-of-sync with what most people would expect.
This is fine, it’s a term of art and those don’t need to be immediately obvious.
I don’t like the title of this article for that reason, though. Really a better title would be something like “a modern x86 processor is not a PDP-11.” The subtitle is perfect basically.
Edit: also IMO it is not really fair to beat up on C for this, the problem is not really one of low-level-ness. A language that actually exposed the complexity of speculative execution and all that could be pretty high level. It would just be harder to read in a linear text editor, right? We’d be better off drawing the dependency graph or something.
I’ve been a fan of this article for years, though it does often make me think that there really aren’t any true low level languages for our super scalar modern CPUs. Does anyone know of any?
> GPUs achieve very high performance without any of this logic, at the expense of requiring explicitly parallel programs.
GPU cores are in some ways closer to "PDP-11", they're either acting as thousands of parallel simple processors, or expose pretty raw instructions for very parallel use-cases.
That seems fair, CUDA kernels and shader code do feel like they're at a similar level of abstraction over the hardware as C was to the PDP-11. But I do think there isn't really an equivalent for modern CPU ISAs
Probably Mojo, it doesnt just talk to your CPU it also will talk to your GPU bypassing the need for CUDA. Its early days, but I see strong potential in Mojo. Currently its primary focus is GPUs for AI inference, but give it a year or two and it will be really interesting for more than just that.
Mojo to me seems like a high level language with some additional support for low level control especially around GPUs. A bit like Rust or C but with more streamlined Python integration and more low level GPU (matrices) support.
Better control over the async nature of the hardware is part of what makes GPU kernels efficient, but I'm not terribly sure the same thing would be the case on the other side of the PCIe bus.
But even before you get to out-of-order/speculative execution, I think most languages lack good (i.e. non-intrinsic-based) support for wide registers or anything SIMD related. I know C++ and Rust are both working on this
It's a good and interesting question, why is it important whether or not it will "benefit certain tasks"? And how would we even know if we haven't tried it?
WoW released a steady stream of expansions from 2004 to present day. At some point players wanted the old game, so Blizzard released WoW 2004, known as classic, in 2018, and reran the stream of expansions, stopping before any mass enshittification. You can draw the same parallels with C and C++, where C began picking the best from C++ in 1999, 2011, 2023, and soon to be 2029, and carving its own "classic" path.
C29 is shaping up to be some quality of life changes. And it looks like clang has a lot of it implemented already. GCC seems to be implementing some of them. `countof(thing)` seems handy so I don't have to define some constant and use them in both places.
I’d say assembly is the lowest level programming language we have. You have to balance the abstraction of hardware instructions with being human readable to also qualify as a programming language
I don’t think byte code qualifies as human readable but it is closer to the metal obviously
Okay. I like this article and I've thought about it regularly since it last made the rounds here.
1) C is a low level language for a PDP11, or for a single core on a GPU.
2) But what would a low level language look like for an FPGA? Probably verilog.
3) The point worth pursuing however is whether there might be a Hardware agnostic "low level language".
4) yep Haskel by the looks of things. If only I could find the reference.. :-/ There's a set of slides from a crew in Edinburgh doing the history of functional languages. Does any one remember something similar?
I've never done verilog professionally but I did "Digital Design and Computer Architecture, RISC-V Edition: RISC-V Edition" as an exercise 2 years ago.
I would say Verilog is very much NOT a low level language.
Metaphorically, it feels closer to SQL to me. I mean this in that you theoretically tell the system what it should do, and it builds it into the messy real world. However, the reality is that the planner (sql) or linker/placer/router/whatever (verilog) are very good, but you often end up needing to actually fully understand the problem anyway when things don't work in the abstract.
I know there is https://clash-lang.org/ for Verilog design, which sounds a little like what you are talking about (never really looked at it myself).
C is a low-level language for the current ISAs we have, though not for Itanium.
So the question is if we really want lower level ISAs. Probably not?
There are many ways in which our current ISAs are actually thoughtfully optimized for superscalar out-of-order processors. Just look at all of the big differences from 32 bit arm to 64 bit arm, which all exist to make execution faster on superscalar processors.
And yet they are still perfectly implementable in cheap microcontrollers. The Cortex-A53, available in boards for a little over $15, is a simple 2-wide perfectly in-order design, without a physical register page beyond the ISA register. Basically, it is a simple Pentium-type chip.
The Apple M chips are some of the most impressive feats of out-of-order superscalar micro-engineering ever. And yet both of these can run the same software with the same ISA. This is enormously valuable.
I fail to see how any sort of much lower level access to the machine would be portable across price ranges and microarchitecture generations. I also fail to see how it would provide a non-trivial speedup over C code pattern recommendations and targeted extensions (eg. vector extensions).
This is one of my favorite papers; it stole about a year and a half of my time. I still pine for Lisp processors although array languages can now self-host on GPUs, which, APL-pilled, I now feel is better. It'd be so cool (...for compiler writers) to be able to control precisely which kernels stay in which cache levels etc.
>and even the pre- and post-increment operators cleanly lined up with the PDP-11 addressing modes.
pre- and post- increment operators cleanly lined up with... the programmer's conceptualization and objectives--the index is/was frequently used in other contexts than loop bounds and indexing. if that's not your conceptualization, don't use that operator. whether you are on a PDP-11 makes no difference.
To me the definition of low-level vs high-level strictly comes from the indirection the language runtime provides for you. If the language compiles down to asm, it's low level. It literally does _not_ matter what it looks like. The only other constraint is possibly whether you can manipulate low-level CPU level constructors like memory, albeit it's not necessary. You can take python and write an LLVM frontend for it and it would instantly become a low-level language.
This article is so blatantly fallacious I can't even get past the first couple of paragraphs. Perhaps it makes a stronger case later in the article, but the early claims it makes invoke Meltdown/Spectre, eg. speculative execution, and your CPU being more advanced than a PDP-11, and that C doesn't expose modern CPU features like speculative execution, therefore C is not low-level. But assembly doesn't either. You could attempt to make the claim that assembly is no longer a low-level language, but the article explicitly does not do this, instead listing assembly as the low-level extreme that C is being compared against.
> C doesn't expose modern CPU features like speculative execution, therefore C is not low-level
This is a 100% skill issue of the author, as is always the case. C does expose it fully, except it's implicitly implied by your code rather than explicitly declared. Same with all of the other arguments that always plague these type of articles.
Can you elaborate? It’s “implicitly implied” by your code? How is that “exposing it fully” except in the sense that speculative execution is “implicitly implied” in all code targeting the relevant hardware?
> You could attempt to make the claim that assembly is no longer a low-level language
Assembly expose instructions that C was never meant to work with. Compilers force C to do so anyway. If you had a compiler that converted 8086 x86 assembly to modern x86 or CUDA bytecode; I'd consider that pretty equivalent.
LLMV intermediate representation is probably more low level (closer to the real compute model it runs on) than that theoretical 8086 x86 compiler.
> You could attempt to make the claim that assembly is no longer a low-level language, but the article explicitly does not do this, instead listing assembly as the low-level extreme that C is being compared against.
The article mentions assembly once. But it's not an argument about how assembly is "low level" and C isn't, although it may sound like that, upon a first reading, given its contentious tone.
The article is really an argument about how C programmers believe, and constantly state, that they are programming "close to the metal", but what they are really programming is a very fast PDP-11 emulator with lots of implicit behavior.
Implicit behavior like speculative execution and asynchronous execution and lots and lots of caching.
> The article mentions assembly once. But it's not an argument about how assembly is "low level"
It is, though:
> Think of programming languages as belonging on a continuum, with assembly at one end
The article explicitly states that assembly is the end of the continuum, that it is the lowest of the low-level. Therefore, it is not making the argument that assembly is not low-level. But the exact same arguments it makes to distinguish C as not low-level can be applied to assembly. The entire article is based on a fundamental logical error.
Again -- I think your impression is the result of the contentious tone of the article. Yes, the article explicitly states:
"Think of programming languages as belonging on a continuum, with assembly at one end and the interface to the Starship Enterprise’s computer at the other. Low-level languages are “close to the metal,” whereas high-level languages are closer to how humans think."
But then spends the rest of the article debunking this commonly held notion, specifically and explicitly re: C, but also implicitly re: assembly.
See the very next section "FAST PDP-11 EMULATORS"
"The root cause of the Spectre and Meltdown vulnerabilities was that processor architects were trying to build not just fast processors, but fast processors that expose the same abstract machine as a PDP-11. This is essential because it allows C programmers to continue in the belief that their language is close to the underlying hardware."
He obviously knows that assembly suffers from the same abstraction.
See also the section "IMAGINING A NON-C PROCESSOR", where the author explicitly discusses alternative processor designs (which would of course require new assembly languages!).
The author is actually trying something like a reductio on your mental model. When the author states "Think of programming languages as belonging on a continuum", the author is really saying "This is everyone's impression, but when you look a little deeper you see the cracks."
This is such a pedantic point IMO. C is low level because it makes it very easy to work with machine language/assembly and do stuff like this (LLM assisted example follows):
This is something "low level" programmers use very often to realize the benefits of a high-level language while exercising explicit control over using specific hardware instructions (vp2intersectd being an AVX-512 instruction used in highly optimized search algorithm impls).Obviously if you rely on implicit behavior from the compiler to optimize your code you are no longer "low level". But if you can quickly and easily drop into machine-level instructions to provide explicit implementation semantics, and the language indeed makes that relatively simple and easy to do, that sure seems "low level" to me
“Low level language” is one of those terms like “VLSI” (very large scale integration) where they defined it in the 70’s or something, so the academic definition is out-of-sync with what most people would expect.
This is fine, it’s a term of art and those don’t need to be immediately obvious.
I don’t like the title of this article for that reason, though. Really a better title would be something like “a modern x86 processor is not a PDP-11.” The subtitle is perfect basically.
Edit: also IMO it is not really fair to beat up on C for this, the problem is not really one of low-level-ness. A language that actually exposed the complexity of speculative execution and all that could be pretty high level. It would just be harder to read in a linear text editor, right? We’d be better off drawing the dependency graph or something.
I’ve been a fan of this article for years, though it does often make me think that there really aren’t any true low level languages for our super scalar modern CPUs. Does anyone know of any?
The article does make an example quite early;
> GPUs achieve very high performance without any of this logic, at the expense of requiring explicitly parallel programs.
GPU cores are in some ways closer to "PDP-11", they're either acting as thousands of parallel simple processors, or expose pretty raw instructions for very parallel use-cases.
That seems fair, CUDA kernels and shader code do feel like they're at a similar level of abstraction over the hardware as C was to the PDP-11. But I do think there isn't really an equivalent for modern CPU ISAs
Mabie hand-rolling LLVM IR representations would count.
> really aren’t any true low level languages for our super scalar modern CPUs
do you mean low level but higher level than assembly language for those processors (like MIPS assembly for an R10k, for example) ?
Wondering can we write microcode? That's definitely closer to the metal.
Probably Mojo, it doesnt just talk to your CPU it also will talk to your GPU bypassing the need for CUDA. Its early days, but I see strong potential in Mojo. Currently its primary focus is GPUs for AI inference, but give it a year or two and it will be really interesting for more than just that.
Mojo to me seems like a high level language with some additional support for low level control especially around GPUs. A bit like Rust or C but with more streamlined Python integration and more low level GPU (matrices) support.
But Mojo is a high level language?
In what way would exposing the true microcoded out-of-order etc nature of the beast benefit certain tasks?
Better control over the async nature of the hardware is part of what makes GPU kernels efficient, but I'm not terribly sure the same thing would be the case on the other side of the PCIe bus.
But even before you get to out-of-order/speculative execution, I think most languages lack good (i.e. non-intrinsic-based) support for wide registers or anything SIMD related. I know C++ and Rust are both working on this
It's a good and interesting question, why is it important whether or not it will "benefit certain tasks"? And how would we even know if we haven't tried it?
Maybe not then, but we basically have our own poor man's template system now:
With (rumors of) lambdas and defer on the way, C is going the way of classic WoW.https://en.wikipedia.org/wiki/C29_(C_standard_revision)
>C is going the way of classic WoW
What does this mean?
it means C is going the way of classic World of Warcraft.
What does that mean?
WoW released a steady stream of expansions from 2004 to present day. At some point players wanted the old game, so Blizzard released WoW 2004, known as classic, in 2018, and reran the stream of expansions, stopping before any mass enshittification. You can draw the same parallels with C and C++, where C began picking the best from C++ in 1999, 2011, 2023, and soon to be 2029, and carving its own "classic" path.
We even have our Herb Sutter: Jens Gustedt
I remember `defer` being up for consideration for the last consortium but it got yanked. Lambdas would definitely be nice to have.
Seems like its in C29, but who knows. I've waited since 2009 for just about anything
C29 is shaping up to be some quality of life changes. And it looks like clang has a lot of it implemented already. GCC seems to be implementing some of them. `countof(thing)` seems handy so I don't have to define some constant and use them in both places.
Not having to write
Is arguably the feature I've waited for for 20 yearsI’d say assembly is the lowest level programming language we have. You have to balance the abstraction of hardware instructions with being human readable to also qualify as a programming language
I don’t think byte code qualifies as human readable but it is closer to the metal obviously
Okay. I like this article and I've thought about it regularly since it last made the rounds here. 1) C is a low level language for a PDP11, or for a single core on a GPU. 2) But what would a low level language look like for an FPGA? Probably verilog. 3) The point worth pursuing however is whether there might be a Hardware agnostic "low level language". 4) yep Haskel by the looks of things. If only I could find the reference.. :-/ There's a set of slides from a crew in Edinburgh doing the history of functional languages. Does any one remember something similar?
I've never done verilog professionally but I did "Digital Design and Computer Architecture, RISC-V Edition: RISC-V Edition" as an exercise 2 years ago.
I would say Verilog is very much NOT a low level language.
Metaphorically, it feels closer to SQL to me. I mean this in that you theoretically tell the system what it should do, and it builds it into the messy real world. However, the reality is that the planner (sql) or linker/placer/router/whatever (verilog) are very good, but you often end up needing to actually fully understand the problem anyway when things don't work in the abstract.
I know there is https://clash-lang.org/ for Verilog design, which sounds a little like what you are talking about (never really looked at it myself).
C is a low-level language for the current ISAs we have, though not for Itanium.
So the question is if we really want lower level ISAs. Probably not?
There are many ways in which our current ISAs are actually thoughtfully optimized for superscalar out-of-order processors. Just look at all of the big differences from 32 bit arm to 64 bit arm, which all exist to make execution faster on superscalar processors.
And yet they are still perfectly implementable in cheap microcontrollers. The Cortex-A53, available in boards for a little over $15, is a simple 2-wide perfectly in-order design, without a physical register page beyond the ISA register. Basically, it is a simple Pentium-type chip.
The Apple M chips are some of the most impressive feats of out-of-order superscalar micro-engineering ever. And yet both of these can run the same software with the same ISA. This is enormously valuable.
I fail to see how any sort of much lower level access to the machine would be portable across price ranges and microarchitecture generations. I also fail to see how it would provide a non-trivial speedup over C code pattern recommendations and targeted extensions (eg. vector extensions).
This is one of my favorite papers; it stole about a year and a half of my time. I still pine for Lisp processors although array languages can now self-host on GPUs, which, APL-pilled, I now feel is better. It'd be so cool (...for compiler writers) to be able to control precisely which kernels stay in which cache levels etc.
In this sense, not even assembly is a low-level language because an instruction may hide what the microcode is actually doing.
IMHO, C is the lowest level a procedural programming language can get.
See also "x86 is a high-level language" https://blog.erratasec.com/2015/03/x86-is-high-level-languag...
If anyone was thinking, but in practice it is a low level language, behold Fil-C.
>and even the pre- and post-increment operators cleanly lined up with the PDP-11 addressing modes.
pre- and post- increment operators cleanly lined up with... the programmer's conceptualization and objectives--the index is/was frequently used in other contexts than loop bounds and indexing. if that's not your conceptualization, don't use that operator. whether you are on a PDP-11 makes no difference.
It's just a matter of personal definitions, it seems. Here is an example:
https://ulanguage.org
What level would you say this language was? Is it a low-level systems language, or is it also usable for writing web sites?
Unique language!
To me the definition of low-level vs high-level strictly comes from the indirection the language runtime provides for you. If the language compiles down to asm, it's low level. It literally does _not_ matter what it looks like. The only other constraint is possibly whether you can manipulate low-level CPU level constructors like memory, albeit it's not necessary. You can take python and write an LLVM frontend for it and it would instantly become a low-level language.
This article is so blatantly fallacious I can't even get past the first couple of paragraphs. Perhaps it makes a stronger case later in the article, but the early claims it makes invoke Meltdown/Spectre, eg. speculative execution, and your CPU being more advanced than a PDP-11, and that C doesn't expose modern CPU features like speculative execution, therefore C is not low-level. But assembly doesn't either. You could attempt to make the claim that assembly is no longer a low-level language, but the article explicitly does not do this, instead listing assembly as the low-level extreme that C is being compared against.
This is embarrassingly bad.
> C doesn't expose modern CPU features like speculative execution, therefore C is not low-level
This is a 100% skill issue of the author, as is always the case. C does expose it fully, except it's implicitly implied by your code rather than explicitly declared. Same with all of the other arguments that always plague these type of articles.
Can you elaborate? It’s “implicitly implied” by your code? How is that “exposing it fully” except in the sense that speculative execution is “implicitly implied” in all code targeting the relevant hardware?
> You could attempt to make the claim that assembly is no longer a low-level language
Assembly expose instructions that C was never meant to work with. Compilers force C to do so anyway. If you had a compiler that converted 8086 x86 assembly to modern x86 or CUDA bytecode; I'd consider that pretty equivalent.
LLMV intermediate representation is probably more low level (closer to the real compute model it runs on) than that theoretical 8086 x86 compiler.
> You could attempt to make the claim that assembly is no longer a low-level language, but the article explicitly does not do this, instead listing assembly as the low-level extreme that C is being compared against.
The article mentions assembly once. But it's not an argument about how assembly is "low level" and C isn't, although it may sound like that, upon a first reading, given its contentious tone.
The article is really an argument about how C programmers believe, and constantly state, that they are programming "close to the metal", but what they are really programming is a very fast PDP-11 emulator with lots of implicit behavior.
Implicit behavior like speculative execution and asynchronous execution and lots and lots of caching.
> The article mentions assembly once. But it's not an argument about how assembly is "low level"
It is, though:
> Think of programming languages as belonging on a continuum, with assembly at one end
The article explicitly states that assembly is the end of the continuum, that it is the lowest of the low-level. Therefore, it is not making the argument that assembly is not low-level. But the exact same arguments it makes to distinguish C as not low-level can be applied to assembly. The entire article is based on a fundamental logical error.
> The article explicitly states
Again -- I think your impression is the result of the contentious tone of the article. Yes, the article explicitly states:
But then spends the rest of the article debunking this commonly held notion, specifically and explicitly re: C, but also implicitly re: assembly.See the very next section "FAST PDP-11 EMULATORS"
He obviously knows that assembly suffers from the same abstraction.See also the section "IMAGINING A NON-C PROCESSOR", where the author explicitly discusses alternative processor designs (which would of course require new assembly languages!).
The author is actually trying something like a reductio on your mental model. When the author states "Think of programming languages as belonging on a continuum", the author is really saying "This is everyone's impression, but when you look a little deeper you see the cracks."
I think that’s the point.