weitendorf
a day 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
a day ago
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
a day ago
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
II2II
a day ago
> 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!
I understand what you are getting to here, and agree that we are getting into the domain of semantics. Yet it could be argued that (for the most part) there is a 1:1 mapping between assembly and the assembly processes is (mostly) reversible. Personally, this is where I would draw the line.
> So we either descend endlessly into pedantry arguing that cosmic rays and ...
I think I see where you're going, though I don't agree with the particular example. If you're saying that machine language is an abstraction in itself, that those sequences of 1's and 0's are a construct of electrical engineers to describe electrical pulses that control transistors in a chip, then I fully agree with you. And if you say that those electrical pulses and transistors are themselves abstractions of physical processes, then I fully agree with you. But I wouldn't really describe it as nerd sniping. These involve different disciplines that are examining the machine at fundamentally different levels.
rightbyte
a day ago
> 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.
The contemporary Basic dialects of that time I could argue were low level languages. They were really thin. Like bcpl.
tremon
a day ago
> 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
II2II
a day ago
> Processor operations have to be strongly typed, by definition.
Individual instructions assume the data they operate on is of a particular type, but it doesn't differentiate data types in memory. Here's an example where I forced the C compiler to treat the bit pattern of two floats as integers, then add those values as integers. The result is, of course, absolutely meaningless.
float dx = 1.0;
float dy = 1.0;
int *pix = &dx;
int *piy = &dy;
int isum = *pix + *piy;
float *pdsum = &isum;
printf("%d\n", isum);
printf("%f\n", *pdsum);
That's just how processors work, right? Apparently it doesn't have to be that way. From my understanding of the iAPX 432, attempts were made to encode object types in hardware.reichstein
a day ago
Processor instructions are not _strongly typed_. They take bit patterns as input and output new bit patterns.
The bits are untyped, the choice of operation decides how the bits are interpreted. Nothing enforces the type of that result, you can always interpret it as something else. It may not be meaningful. Or it may be, like a fast inverse square root.
Strong typing means that each value has an intrinsic type, and there is no reinterpreting it. What CPUs do is not that, or rather the only types are "_n_-bits" (_n_ a power of 2).
Pannoniae
a day ago
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 :)
uecker
a day ago
For "strongly typed" I would expect some type checking.
TheOtherHobbes
a day ago
True for IEE754 floating point operations, which are constrained to valid bit patterns. An operation on an invalid pattern - can happen with uninitialised memory - throws an exception.
Otherwise, no.
Dylan16807
18 hours ago
What do you mean by invalid patterns? Signalling NaNs? I wouldn't really call those invalid, but also that's only about one in a thousand bit patterns. If it kicks in less than 1% of the time it's not really "type checking".
uecker
20 hours ago
This is still not type checking, it accepts whenever the bit pattern is a valid for floating point even when it originally was used as another type.
torginus
a day ago
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
a day ago
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
a day ago
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
a day ago
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?
kelseyfrog
20 hours ago
> Is adding intrinsic enough to turn a language from high-level to low-level?
Yes, Java and C# are low level languages.
dismalaf
11 hours ago
Lol if languages that literally run a virtual machine and abstract away everything about the hardware are low-level, what's high level?
kelseyfrog
10 hours ago
Excel equations and whatever the hell kids are building with redstone in Minecraft.
stackghost
a day ago
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
a day ago
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.
Cold_Miserable
a day ago
Intel don't have vpintersect. Its micro-coded dog slow but its such an obscure operation and of little use if any. I'd rather AMD re-invest the hardware into speeding up useful operations, shave 1 cycle off divide or square root or something.
stackghost
a day ago
>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. To me, anyway, that's where I find some credence in the "C is a high-level language" meme.