| ▲ | weitendorf 2 hours ago |
| 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): 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 |
|
| ▲ | II2II 2 hours ago | parent | next [-] |
| There are many reasons why C is not a low level language. Take the example: while `asm()` blocks are a common extension to C compilers, anything within the block is (a) compiler dependent and (b) architecture dependent. To choose an extreme counter example: you may as well claim that versions of BASIC with the POKE keyword are low level languages simply because you can POKE machine code directly into memory. 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. |
| |
| ▲ | weitendorf an hour ago | parent | next [-] | | That's fair. C is very old and used for almost all hardware so I think while you can make the argument that "only clang and gcc extensions asm blocks available like that, and intrinsics are only available through vendor-specific headers" and be right, by that same logic literally nothing except binary machine code for hardware without any kind of microcode can be low-level, and even then it's probably always hardware dependent (because if it's not fully bijective to the actual hardware it's implemented on top of, the semantics leak). Practically speaking, we have a word for the kind of "abstractionless" model you're describing: machine code. I mean, even assembler is a bunch of abstractions about 'registers' and 'instructions' that are really just specific portions of the hardware or opcodes! So we either descend endlessly into pedantry arguing that cosmic rays and electron tunnelling represent inexcusable deviations from the overly abstracted semantics that hardware vendors expose in their products or maybe we draw the line somewhere else. You may not agree with mine, that "practical and simple interop with machine-level language impls across a high-level language interface is sufficiently close to the hardware as to be low level" but there has to be a limit somewhere between that and "technically the hardware's operating temperature is part of its logical semantics because if it exceeds a certain value for long enough it starts to degrade and yield incorrect results or terminate execution". I think eventually it just becomes unproductive nerd sniping, personally | |
| ▲ | tremon an hour ago | parent | prev [-] | | > Processors have a very weak notion of data types This is absolutely not true, unless you mean to say that processors should somehow support composite (aka C struct) data types as an instruction primitive. Processor operations have to be strongly typed, by definition. For example, these are the data types supported by operations in the modern x86 instruction set (ignoring vector extensions): - signed and unsigned integers of 8, 16, 32 and 64 bits - floating-point decimals of 32, 64 and 80 bits (and 128 via sse) - nul-terminated byte strings > For example: casting a `float` to an `int` has a very specific definition in C, and that definition involves altering the pattern of bits I don't understand this example. Casting a float to an int also has a very specific definition in IEEE-754 and is pretty much universally implemented as a hardware instruction. It has been in the x86 family since its inception: https://www.felixcloutier.com/x86/fisttp | | |
| ▲ | Pannoniae an hour ago | parent [-] | | I wholly agree, processors are strongly typed, even if there are holes like using integer instructions on floating-point values in XMM regs, very insightful comment:) btw a bit of nitpick: to be fair basically no one uses x87 anymore, it's https://www.felixcloutier.com/x86/cvttss2si and friends but yes :) | | |
|
|
|
| ▲ | torginus 2 hours ago | parent | prev | next [-] |
| I think a reasonable definition of 'lower-level' is getting the programmer to take over some tasks from the compiler. Mechanically expanding a block of code into intrinsics isn't really that. 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. |
|
| ▲ | senfiaj an hour ago | parent | prev | next [-] |
| Yeah, and also not every idiomatic C/C++ code is portable. Hardcoded structure sizes, assumptions about the byte order in integers or assumptions about alignments in certain data structures might cause headaches with porting the code to another CPU. Truly high level languages hide these details. |
|
| ▲ | dismalaf an hour ago | parent | prev | next [-] |
| Common Lisp allows you to define VOPs (compiler instructions) in user code. Smalltalk allows you to write inline assembly. Are those also low level languages? |
| |
| ▲ | Ygg2 25 minutes ago | parent [-] | | Not just Lisp. C# also has compiler intrinsic. Does that mean that C# is low-level language? Is Java then? Is adding intrinsic enough to turn a language from high-level to low-level? |
|
|
| ▲ | stackghost 2 hours ago | parent | prev [-] |
| Isn'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 2 hours ago | parent [-] | | Sure, 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. | | |
| ▲ | stackghost 5 minutes ago | parent [-] | | >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. The part I find tedious is C programmers who cling to the language by claiming it lets you understand and finely control what the machine is doing, when it clearly does not, because x86 assembly itself is being emulated by the cpu underneath |
|
|