7 ms·
C Is Not a Low-Level Language (2018)
- actionfromafar 9d agoIf anyone was thinking, but in practice it is a low level language, behold Fil-C.
- legobmw99 9d agoI’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?
- MrBuddyCasino 9d agoIn what way would exposing the true microcoded out-of-order etc nature of the beast benefit certain tasks?
- legobmw99 9d agoBetter 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
- 12_throw_away 9d agoIt'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?
- jjtheblunt 9d agoVLIW investigated this idea, where the compiler took responsibility for scheduling multiple functional units at compile time, in contrast with dynamic reordering as in superscalar. https://en.wikipedia.org/wiki/Very_long_instruction_word https://en.wikipedia.org/wiki/Very_long_instruction_word
- aDyslecticCrow 9d agoThe 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.
- legobmw99 9d agoThat 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
- aDyslecticCrow 9d agoMabie hand-rolling LLVM IR representations would count.
- jpollock 9d agoDoesn't assembly allow devs to ignore speculative execution as well? You can place LFENCE(x86)/CSDB(arm) around code blocks, but you can do that in C too.
- giancarlostoro 9d agoProbably 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.
- deleted 9d ago[deleted]
- poly2it 9d agoBut Mojo is a high level language?
- giancarlostoro 9d agoWith intimate low-level capabilities. Just because a language can get deeply low-level, doesn't mean it needs to look complicated...
- poly2it 9d agoThere are many other languages we could consider low-level based on the premise of them allowing low access (e.g. "raw" assembly). We don't, because it would be nonsensical to judge the height of a language based on its abilities to forgo the abstractions it was built to provide.
- huijzer 9d agoMojo 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.
- kllrnohj 9d agoIn the case of this article, even assembly isn't low level. It's not possible at all to write low level code for a modern superscalar CPU. So no, Mojo wouldn't be low level. It can't be.
- ferguess_k 9d agoWondering can we write microcode? That's definitely closer to the metal.
- wat10000 9d agoThat was kind of the original idea of RISC. Expose simple instructions that could be implemented without microcode. Push the complexity into the program instead of the microcode. Instead of writing a memory-to-memory add instruction that decomposes into load, load, add, store microcode, you directly write the load, load, add, store. This didn’t quite work out in the long term since hardware evolves faster than ISAs. Today’s “maps directly to the hardware” instruction is tomorrow’s “we add more hardware and play tricks to make this faster.” You explode all of the physical registers as logical registers, then a few years later you double the physical registers count and do clever mapping to extract more speed. My favorite is the MIPS branch delay slot. Instead of complicated branch prediction to hide latency, expose the pipeline directly to the programmer. And then a couple of hardware generations down the line, the pipeline becomes much longer and more complicated and the CPU is back to playing tricks to hide latency, and the weird branch delay slot remains as essentially a vestige of bygone days.
- parlortricks 8d agoIs eMIPS something along the same idea https://www.microsoft.com/en-us/research/project/emips/ https://www.microsoft.com/en-us/research/project/emips/ ?
- jjtheblunt 9d ago> 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) ?
- glouwbug 9d ago[dead]
- lelanthran 9d ago[flagged]
- melodyogonna 9d agoThe thing is, you can only program what is programmable. If a CPU has some capability that isn't programmable, I don't know what C or any other language is expected to do.
- veqq 9d agoThis 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.
- adonovan 9d agoWhat do you mean it stole your time?
- EGreg 9d agoIt's just a matter of personal definitions, it seems. Here is an example: https://ulanguage.org 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?
- rfgplk 9d agoUnique 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.
- bee_rider 9d agoI think that would be confusing. Intuitively “low level language” describes the language. Your definition actually describes the nature of the compiler implementation.
- applfanboysbgon 9d agoThis 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.
- nizmow 9d agoI think that’s the point.
- rfgplk 9d ago> 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.
- jact 9d agoCan 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?
- Joel_Mckay 9d agoSome C compilers include special interpretations of static and volatile to change how both the code is compiled and linked. It can be "low-level", but it depends how you define what that means in the age of microcode abstractions. Most people shouldn't write in C, but that is because their skills belong in Application user space. =3
- 9d ago
- glouwbug 9d agoMaybe not then, but we basically have our own poor man's template system now: #define array(T, N) struct array##T##N { T value[N]; } void copy(array(int, 32)* x, array(int, 32)* y) { *x = *y; } int main() { array(int, 32) x; array(int, 32) y = { 1, 2, 3, 4 }; copy(&x, &y); } 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) https://en.wikipedia.org/wiki/C29_(C_standard_revision)
- warmwaffles 9d agoI remember `defer` being up for consideration for the last consortium but it got yanked. Lambdas would definitely be nice to have.
- glouwbug 9d agoSeems like its in C29, but who knows. I've waited since 2009 for just about anything
- warmwaffles 9d agoC29 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.
- leptons 9d ago
- fsckboy 9d ago>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.
- fsckboy 9d agoand just to make the point more firmly, PDP-11 didn't have a preincrement/postincrement pair of instructions, it simply had indexing with post-increment of the index or pre-decrement, i.e. one paradigm, not two. Or in other words, "stack" behavior.
- blastonico 9d agoIn 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.
- xhrpost 9d agoSee also "x86 is a high-level language" https://blog.erratasec.com/2015/03/x86-is-high-level-language.html?m=1 https://blog.erratasec.com/2015/03/x86-is-high-level-languag...
- bee_rider 9d ago“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.
- Peteragain 9d agoOkay. 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?
- stephen_cagle 9d agoI'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/ https://clash-lang.org/ for Verilog design, which sounds a little like what you are talking about (never really looked at it myself).
- Peteragain 9d agoFound it! Thanks for the pointer: https://researchportal.hw.ac.uk/en/persons/rob-stewart/ https://researchportal.hw.ac.uk/en/persons/rob-stewart/ My argument is that verilog is to FPGAs as assembler is to pdp11s, and C is closer to assembler than Java or prolog. High level languages are easier for programmers, but interestingly, the high level language Haskel readily compiles to FPGA. What's happening here?
- mathisfun123 9d ago> what would a low level language look like for an FPGA? Probably verilog Verilog is not a programming language (because FPGAs are not programmed) it's a hardware description language. It's also very lossy (every vendor has reams of coding guides for using it just right with their synthetizer).
- jrhey 9d agoI’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
- pornel 9d agoModern CPUs hide so much logic that the assembly language is an abstraction itself. CPUs have many times more registers than their assembly language, execute instructions speculatively and out of order. Multiple layers of caches are synchronized in complex ways. There's an invisible complex work scheduling algorithm that can even make one CPU core interleave work of two (hyperthreading). GPUs can expose more of their internals thanks to shader compilation. They don't have to emulate previous-gen chip, and instead every chip can expose exactly what it supports and rely on software being recompiled for it.
- weitendorf 9d agoThis 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): int main() { __m512i vecA = _mm512_setr_epi32(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15); __m512i vecB = _mm512_setr_epi32(0,5,10,15,20,25,30,35,40,45,50,55,60,65,70,75); unsigned short mask = 0; __asm__ ( "vp2intersectd %[B], %[A], %%k2" : "=@cck2" (mask) : [A] "v" (vecA), [B] "v" (vecB) : "k3" ); printf("Intersection Mask: 0x%04X\n", mask); return 0; } 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
- stackghost 9d agoIsn't the point that x86 instructions are themselves no longer a good mental model for what the processor is actually doing under the hood, and thus C which was long billed as a thin layer over top of assembly is itself a higher abstraction? There is AFAIK no way to express or interact with speculative execution/branch prediction, for example.
- weitendorf 9d agoSure, but then you're really arguing that the ISA no longer maintains 1:1 instruction-level implementation and that this is the definitive quality of whether or not something is low level, to the point that any deviation from that model makes it not officially "low level". To me that's just a very tedious pedantic argument that simply fails to capture the actual meaning behind why/when we might call something low level. TFA famously argues that Spectre/Meltdown et al break that abstraction. But note that they are quite literally exceptions to the rule: the only reason we know/care about them is that the "magic under the hood" that was supposed to make CPUs faster while maintaining that abstraction introduced a bug that caused the implementation details to leak to the end users. Similarly even vp2intersectd took multiple cycles in its original Intel impl and even in the performant AMD Zen5 impl it still takes >1 cycle with 6 levels of pipelining or somesuch. Ok. If literally not even a chip's ISA is "low level" then the term is effectively meaningless. The only way you could define a "low level" language capable of exercising that hardware's capabilities fully would be to have some kind of per-cycle, pipeline-aware annotation layer over the actual machine code... which really seems like quite a lot of noise/cruft you'd not typically want to add on top of everything, all in the name of still technically being low-level according to some dubiously pedantic criteria nobody would event want in practice.
- serbuvlad 9d agoC 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).
- pornel 9d ago> I fail to see how any sort of much lower level access to the machine would be portable across price ranges and microarchitecture generations That's the assumption that can be removed. GPUs don't have stable ISAs, and their assembly-like code gets recompiled for each microarchitecture. In the Intel's world of prebaked machine code adoption of a wider set of SIMD instructions takes a decade+. In GPUs it's just a driver update.
- serbuvlad 9d agoSure, but SIMD extensions and base ISA solve different problems. As for GPUs, while the ISA is not constant, it's STILL C-ish running over a dynamic hardware scheduling layer. Edit: To clarify, I have nothing against a closed ISA, I just don't see how making that ISA non-C-ish is valuable.
- p0w3n3d 9d agoC is not a low level language. It's a macro assembler
- WillPostForFood 9d agoFrom the preface of first edition The C Programming Language. Just interesting to note the authors never claimed it was low level, just not "very high level." --- C is a general-purpose programming language with features economy of expression, modern flow control and data structures, and a rich set of operators. C is not a "very high level" language, nor a "big" one, and is not specialized to any particular area of application. But its absence of restrictions and its generality make it more convenient and effective for many tasks than supposedly more powerful languages.
- spaintech 9d agoI enjoy this article showing up here once in a while. It makes me think about the stack of abstractions we actually live in… CPU -> microcode -> ISA -> firmware/BIOS -> OS + drivers -> C abstract machine -> your app. ( I left our virtualization purposely thinking of a bare metal stack ) Current ISAs have so much machinery underneath that it’s hard to tell when you’re talking to the iron and when you’re talking to the microcode You can still argue that Forth on a Forth CPU is a genuinely low-level language. :)
- melodyogonna 9d agoWell, C does give you the control when it matters, even if they feel bolted on for more modern features. I actually believe Mojo is the only modern language not designed to pretend every computer is a PDP-11. C has been so successful that many succeeding languages just did C things as a matter of course. In Mojo, everything is designed with the complexity of the modern computer in mind, and at every stage the programmer has complete control of outcomes. You decide what gets inlined, what gets passed in registers, what gets unrolled, etc. The language has excellent ... ney, probably the best portable SIMD support there is; all integers are built on top of SIMD, and the scalar integers are just SIMD with length of 1. You get complete control of what gets compiled as well due to powerful compile-time programming that is similar to, but more powerful than Zig's (imo, because you can supply a lot more information). While C and Rust allow inline asm, Mojo goes further by letting you supply inline MLIR and LLVM as well, so in situations that warrant it, you can tell the compiler to compile to a specific LLVM intrinsic. The language also does not assume you're compiling to run on just one machine; every modern computer is heterogeneous by nature and may contain multiple programmable units, so the compilation pipeline is designed to allow compiling certain parts of code for one target and other parts for other targets... as one compilation unit.
- joas_coder 7d ago[flagged]
- jasomill 8d agoI tend to think of this a bit differently, and would say that C is a low-level language, independent of implementation architecture, because it provides a small set of primitives with relatively simple behavior. Reason being, I don't see any fundamental difference between an x86 CPU that translates x86 instructions into simpler microinstructions for execution and a machine that translates, say, Haskell code into simpler microinstructions for execution, and I'd still consider Haskell a higher-level language than C.