ES version is available. Content is displayed in original English for accuracy.
Advertisement
Advertisement
⚡ Community Insights
Discussion Sentiment
76% Positive
Analyzed from 2577 words in the discussion.
Trending Topics
#level#low#language#assembly#article#languages#hardware#high#code#more

Discussion (55 Comments)Read Original on HackerNews
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
Yet one of the more interesting reasons, in my mind, is that C adds a tonne of abstractions. The roster of data types is one of those abstractions. Processors have a very weak notion of data types, and memory has absolutely no notion of memory types at all. For example: casting a `float` to an `int` has a very specific definition in C, and that definition involves altering the pattern of bits. While you can create a float and force the C compiler to regard that memory location as an int (via casting pointers), it isn't how the language is meant to be used (outside of rare cases).
If I recall correctly, some of the direct predecessors of C were typeless, which is closer to how the CPU and RAM treat data.
What makes 'C' not really low level by a reasonable definition, is that the register allocation decisions are not yet made. Which, depending on how it works out, can effect ordering, inlining, unrolling etc, so the compiler can pretty much go to town on your code and create something unrecognizable.
Since registers aren't really allocated here, this is basically on the level of C code, and all that stuff can happen here, so this really isn't much lower level than C.
Not being elitist, it's just worth knowing what's going on under the hood of compilers, and the nature of the contract they uphold.
There is AFAIK no way to express or interact with speculative execution/branch prediction, for example.
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.
---
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.
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. :)
> 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.
do you mean low level but higher level than assembly language for those processors (like MIPS assembly for an R10k, for example) ?
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.
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
https://en.wikipedia.org/wiki/C29_(C_standard_revision)
What does this mean?
I don’t think byte code qualifies as human readable but it is closer to the metal obviously
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).
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).
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).
IMHO, C is the lowest level a procedural programming language can get.
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?
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 is embarrassingly bad.
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 the article's 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.
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:
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 author obviously knows that assembly suffers from the same abstraction penalty. The author is saying, because C and processor design has been so tightly intertwined, we cannot program "close to the metal" because "the machine" is actually a very fast PDP-11 emulator.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 (which are actually contradictions)."
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.
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.
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.