C isn't a programming language anymore
faultlore.com
faultlore.com
My approach was to write a script which generated functions for all possible signatures, up to N parameters. The functions verified the values they were passed both natively and through NSInvocation.
This flushed out a huge number of weird, undocumented ABI corner cases. For example on PPC, when returning a struct containing a bitfield, sometimes the bits are aligned at the top (MSB) of the register, other times at the bottom, depending on the width of the bitfield! This was just an old gcc bug, but it can't be fixed without breaking the ABI.
The resulting C code was so large it also unearthed a PPC64 compiler bug! PPC jump instructions have a 24-bit immediate offset. If the offset needs to be larger than 24 bits, it's supposed to load the offset into a register and emit an indirect jump. This just didn't work, but nobody noticed, since you'd need to branch past ~33MB of code in a single function to hit it.
imagine unit tests existing for claimed functionalities :: eye roll ::
> you'd need to branch past ~33MB of code in a single function to hit it.
That's... an impressive amount of C code.
But that actually works? As in that the call generated by GCC correctly captures the structure returned by the function compiled by GCC, in spite of the misplaced bitfield?
Libffi did a stupid thing on PPC64 with returns. Quantities less than 64 bits are on the wrong end of the word, as if the register value were lifted from a big endian piece of memory. E.g a char value of 'A' gets returned as 0x4100_0000_0000_0000. You can't just cast that to a char and be done.
Any language which wants to have a comparable feature set to C in terms of breadth of platforms and depth of penetration will run into issues like this. Maybe it can, with the benefit of half a century of hindsight, make better choices.
I understand the frustration of principled PL authors trying to bootstrap an ecosystem for programs compiled from their language to run on. It can be frustrating having to interact with foreign memory that you don't control and specifications that intentionally leave much to be desired by allowing the hardware platform to decide the memory layout...
But if you didn't have these things it would be a lot harder to get even a basic system going.
It would be nice if there were _one_ standard way to encode an sized integer to rule them all... but we don't live in that ideal world. Pick an arbitrary, common layout, and stick with that. Your language might not let users target some micro-controller that runs on a toothbrush but you probably don't need to anyway.
I've been saying a variation of this for half a decade now. My version is: the only way to do everything that c/c++ does is to be just as terrible as they are. Replacing them therefore requires a small host of languages in order to do it.
Which is what we're seeing now: D, P, Rust, Zig, Odin, Carbon, Go, C#, Java. They're all slowly chipping away from what we used to need c/c++ to do. I think we probably need one or two more (and then more maturity for several of the existing options) before the process is complete and we're left with a bunch of languages in their own niches and c/c++ in a small niche of their own.
EDIT: It looks like it's also being used in AWS.
It's perfectly possible to create an ABI standard and have every symbol well defined, a completely specified grammar, and even keep it simple enough so people are able to implement it.
None of the problems on the article are necessary for C to run on microcontrollers and super computers. They all exist because C is an emergent semi-agreement created by people that mostly didn't talk between themselves and meant different things with their words on the few times they talked.
(Anyway, isn't C-- exactly an attempt to do that?)
Sure. But that is pretty much inevitable if you try to get people from a few dozen industries with vastly different goals to try to agree to some form of standard. You will have to make bad choices, because the good choice is totally unacceptable for one group on the standard committee (maybe for a very good reason).
The problems in C exist because of the vast space of problems it tries to solve. If your programming languages targets x86 and maybe ARM, there is a vast sea of problems you will never have to think about.
>It's perfectly possible to create an ABI standard and have every symbol well defined, a completely specified grammar, and even keep it simple enough so people are able to implement it.
Yes, it is possible but as standard which goes beyond a single system this is about as realistic as people only using "one programming language" and "one operating system".
To switch in to C ABI mode in C++ you have to wrap the declarations in 'extern "C" { ... }' blocks, otherwise you get the C++ ABI, not the C ABI. It's done similarly in many other languages.
Actually, you can. I added one to the D compiler, and dubbed it ImportC. With ImportC, in your D code you can write:
import stdio;
and if there's a stdio.h file, the D compiler will read the .h file, parse it, and present the ABI to it in a D-ish manner to your D code. Here it is:https://github.com/dlang/dmd/blob/master/compiler/src/dmd/cp...
It's so darned useful I expect other languages will do it, too. People were skeptical at first, but it's a solid gold productivity enhancer.
2. How does the D compiler handle GCC- and Clang-specific extensions to C?
It's important to note that C cannot be fully parsed without keeping a symbol table, and cparse.d handles that, too.
2. C extensions will always be a problem. ImportC supports the most widely used ones. (Most C extensions are rarely used and can be ignored.)
His very insightful observation is that you can replace every plank in the ship one at a time and the resulting ship still looks the same. That's because each time you replace one plank, it must fit with the surrounding existing ones. So while the components are replaced, the interface between them is unchanging. If you believe the result of the thought experience is that it is the same boat, then it's because the set of interfaces define the boat.
This applies very much to C as the universal ABI. It's common to see programs written in a combination of languages who interface with each other using a C ABI even though none of those languages is itself C. And, for better worse, it's really hard to change this because while components are easily swapped out, moving an interface is hard because you have to simultaneously fix every component that uses that interface.
I'd go with the glass half full point of view. This has to do with fixing stuff that ain't broken. This has everything to do with developers not wanting to go the Java route and reinvent the wheel and have to reimplements all basic functionalities under the sun. They just target C in particular or in some cases POSIX and they are up and running in no time, reusing everything under the sun with minimal work.
As far as using C as an ABI - I agree, that needs to evolve. I'm not opposed to using an interface specification language created specifically for ABI's. I think that's the way forward.
It's an ABI, there have to be one, and C was the de-facto system programming language.
I don't get the complaint, there are flaws, it could have been much MUCH worse.
C is also a terrible language to define an ABI because, by design, it's very imprecise in the way that it defines types.
C was never the only systems language.
It's perfectly precise, particularly if you choose to use <stdint.h> which has been available in the language for a very long time. You may have some issues with specific types and platform variability, but it's absurd to cast this as "it's imprecise with types."
It's _flexible_ with types.
People seem to miss that this is the reason why C is the lingua franca. It isn't trying to "perfect" computing. It just makes it possible. There's a lesson for new languages here.
This is why C is mostly portable with minimum effort unless you do hardware specific things, or use the variable to their limits. In that case you can always define fixed size types.
I don't get why people love to bash C. No language has an obligation to do please the programmer in its default modus operandi. Programming languages exist to interface hardware with humans, and C operates in the realm of the hardware, and that's perfectly OK for me.
C ABIs (of which there are many because the C standard doesn’t cover ABI) are full of legacy cruft, because they need to be stable and backwards-compatible more than they need to be sensible or efficient.
The C ABI can even vary depending on compiler flags, e.g. availability of AVX affects calling conventions. It’s not easy to be interoperable with this, especially when ABI-affecting compiler flags and macros may be set by an arbitrary build system, not even the C source code.
Android userspace uses Java and can only talk to native code via JNI, which definitely isn't a C ABI.
Likewise there isn't any writing to metal on ChromeOS for the official userspace APIs, running Linux (Crostini) implies running a sandboxed version on top of the actual ChromeOS, while Android on ChromeOS not only has the same JNI restrictions, it also is sandboxed without access to host OS.
EDIT: I also forgot that ChromeOS and Android expose many of their key APIs to native code via OS IPC, on top of the constraints mentioned above. With endpoints written in a mix of C++, Java and now Rust.
You don't do OS syscalls via JNI, rather marshal representation of Java objects for consuptiom from native languages.
JNI is an API between the JVM and C- or C++-coded plugins that can implement native methods. "Native method" means "written in C (or C++)". There's a) a standard interface that C-coded methods must present (depending on their Java signature) and b) a set of utility C functions provided by the JVM.
Because these plugins are loaded via `dlopen()`/`LoadLibrary_Ex()`/etc. the JNI API has an ABI.
If you don't believe me go look it up. There's a ton of resources on JNI. Here's an example taken from the wikipedia page on JNI:
extern "C"
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
const char *nativeString = env->GetStringUTFChars(javaString, 0);
//Do something with the nativeString
env->ReleaseStringUTFChars(javaString, nativeString);
}Then dive into Vax/VMS with Bliss, Multics with PL/I and plenty of others that lost to UNIX.
Loosely defined, most embedded Forth systems act as an OS and they tend to use the system's assembly or the Forth system itself as the ABI.
PC-DOS derivatives obviously use x86 registers and interrupts as an ABI - that's more assembly and machine language oriented than C.
Even with these restrictions in place, modern register-based calling conventions can be still be rather complex, but these restrictions reduce it somewhat. They help to avoid areas in which implementations traditionally diverge, too.
The bits of C that need to be avoided in APIs are:
- bitfields unless no bitfield crosses a
byte boundary
- struct fields of enum types (because the
size of the enum type is implementation
specific)
The spec should fix this, damnit.If C is such a problem id like to see some alternatives which offer what C does but less painfully.
C is only useful in certain domains, but within the domains where C is really good, theres no others. (unless you want to go assembly mode... good luck :) can be fun)..
anyhting that is less painful, exposes less complexity and is usually therefore less generally applicable.
A lot of the pains of C are lack of understanding how to use it because using it requires deep knowledge of it, and the target platform.
that being said, i do get userland applications and C dont really mix anymore. that is a fair point imho.
I find it amusing that after half a century of existence and powering the world's IT infrastructure, suddenly some illuminated individual felt entitled to claim they alone found problems that they alone can fix.
The one thing that could be improved is to come up with a better language than C headers to specify cross-language APIs/ABIs.
The fact that different ABIs exist cannot be helped. That’s something one has to cope with in any case, independently of C.
One way to sidestep many of the issues is to target the JVM. ;)
Among type issues, it also has the infamous "Macros" that are really just unanalyzable text replacements.
In order for my new and improved Rectangle to talk to another really cool Rectangle, I have to resize one of my edges to fit nicely on the Square and the 2nd Rectangle must do the same. The Square is a stable interface that rarely changes.
I hate that the Square is a stable structure that doesn't change sizes dramatically when it's proven that a new size is better.
In conclusion, Squares are no longer Rectangles.
POSIX describes its interface (types and procedures) with Standard C and specifically requires that a compliant system has a C compiler available.
If you want to get rid of C, you will have to get rid of POSIX too or have POSIX use something else.
POSIX specification:
What we really need to interop w/ C but w/o C is a C compiler based tool that outputs DWARF or DWARF-like debug output that actually lays out everything -- type sizes and layouts down to the individual bits that bitfields map to, enum values, constant-like macros, etc. Function-like macros you'll never really be able to use from other languages, so, oh well. Such debug metadata would have to be encoded in a stable/committed encoding, expressed in a stable schema.
With that sort of debug info a language like Rust could take care of all C interop natively w/o C, especially if those debug files were included with the OS so that Rust wouldn't need to invoke the tool that generates them. Though, obviously some `-D...` C compiler/pre-processor arguments can radically change the contents of the parsed headers, so some rationalization would be needed to cut down the number of "ABIs" to a manageable set (like GNU, BSD, POSIX).
That sounds like C2FFI: https://github.com/rpav/c2ffi
The only language ecosystem where I've seen it used is Common Lisp, in the CL-Autowrap library (https://github.com/rpav/cl-autowrap).
But C2FFI emits plain JSON, so I don't see any reason why you couldn't build e.g. a Python auto-binding library on top of it. It depends on LLVM to generate the spec file, but end users don't need to have that.
> This tool helps automate testing that two languages/compilers agree on ABIs for the purposes of FFI. This is still in early development so lots of stuff is stubbed out.
...
> By running this natively on whatever platform you care about, this will tell you what FFI interfaces do and don't currently work. Ideally all you need to do is cargo run, but we're dealing with native toolchains so, expect toolchain bugs!
I wish it was in LLVM/GCC out-of-the-box, rather than a separate open source project (whose maintainer only seems to have rather limited time to spend on it). Ideally there would be a standard format so LLVM, GCC, MSVC, etc, could all produce it (obviously with some extension points since each has some unique features the other doesn’t). From memory, the actual JSON produced by c2ffi follows rather closely clang’s internal data structures and so might not be the right design for something intended to be generic.
Of course it is more voluminous and slower than a binary format. One could always define a binary encoding which straightforwardly maps to the JSON one (or just reuse an existing one such as UBJSON, BSON, CBOR, etc)
I agree that JSON is probably the best choice from among the choices you listed.
(Also, ASN.1 has JSON encoding rules, FYI. ASN.1 is not just the awful DER.)
In my experience I have seen plenty of standards violated and downright disregarded because the platform has special functionalities or quirks. My view is that even if there was an ironclad ABI spec platforms would still violate it with their special implementation of the system interface.
I think we would end up with exactly the same problems only the rant would not be targetting C but something else.
It probably would be even worse. For instance it possible to write a leaf function in assembly for AMD64 that runs both on linux and windows if all you do is return a value. This is because they both use RAX as the return register. There subtle difference between the two but that is mainly what registers are used. The biggest difference comes in the stack layout.
Now when it comes to pretty much any other OS for instance on x64 they pretty much use the System V calling convention.
The thing is if C was not the glue, I could imagine the ABI would be wildly different on each OS heck maybe even each version of the OS.
If we look at windows you can't safely call any system calls directly since a major update could change the system call number. However, windows provides wrappers around these system calls. Linux is an outlier in OSes that considers changing system calls like this as breaking user-space.
Now if we did not have C what would operating systems be using to wrap all these system calls? I suspect something tailored to that OS specifically and it would be quite the mess so I bet it would even be worse to handle. Since at least C keeps things similar. Whereas without it you would get OS specific schemes that could vary by a lot more! Making the code base to support multiple platforms even larger.
https://www.humprog.org/~stephen/research/papers/kell17some-...
Lack of a platform is a strength.
The spartan nature of the language itself, and all of the inevitable difficulties around linking has really made nearly everything else seem so much more simple.
You can also do zig style memory management (ex: allocator pools per request etc) which helps.
I almost feel the opposite, C is the only language that has *never* let me down. It's the only one that's always delivered on it's promises, it's the only one that never fights me on what I want to do. It always feels like the sky is the limit. And it's so nice to know that you're the one who's wasting cycles and memory, not the language.
This is the #1 thing I've learned not to like about it.
The problem is simple. How can I trust that code written by other programmers is up to my standards? If the language obediently does anything they want, how am I supposed to judge its quality? Do I have to review every line of code in every piece of software running on every piece of hardware I interact with? Do I have to go around building a complete and accurate mental model of literally any program I plan to use?
I don't have time for that. Nobody does. It doesn't scale.
When other people write software, I want a vigilant, tireless critic to watch them write code and stop them from doing things that are unambiguously stupid. That's the only way to deal with the vast number of programmers with skill levels beneath mine, writing code that threatens to affect my life. If they can't do that with C, I want them to do it with something else.
I also love programming in C! I even went so far as to write a metaprogramming layer so I no longer have to use cxx templates for generics. It's such a delightfully simple language that really sparks the Joy of Programming in me
I'm unclear on whether it would have been possible to do better without an implausible amount of standardization early in computing history. A low-level language that works on CPUs with different endianness and word size and pointer size and number of registers is by necessity going to be somewhat vague about calling conventions.
I don't think this idea is in any way controversial -- it would just be a lot of work to do across a large number of systems.
Also, isn't it disingenous to take a cheap shot at C for not being "parse-able" when the OP's favorite language, Rust, has the same syntatic grey areas? I don't know enough about Rust, but from what I know it should have the same issues called out in the HAL paper OP cites.
Which ones? Afaik in Rust it's always clear whether an identifier belongs to the value or the type (or macro) namespace, for example a variable declaration is `let foo =` or `let foo: Bar =` (possibly with a pattern on the left hand side), parameters & constants being similar. This alone rules out most of them. It also doesn't have if/else branches without braces, nor _Atomic. So yeah, seems odd to claim it's disingenuous.
Any other language could do it, just no one does, because its not sexy or fun.
In theory, if you scrapped libc and up, you could limit your "speaking C" to the kernel of whatever OS/architecture you're using. Different OS/architecture combos would be more of a nightmare than others, but we cheat off libc for that , except we have to do things to support multiple libc implementations some times.
It doesn't solve interop with other languages, because they don't want to reinvent wheels either.
In theory, you could scrap it all and go hardware up. Have a nice OS with a well defined message passing interface that any language could interact with. You could even make a libc wrapper on top to address portability. In 20 years, it may have serious adoption.
The problem is that the situation is "good enough" that nobody wants to solve it. That applies to most technical debt, and really any other human concern.
That would make interacting with OS slower, and probably even more important are things like .DLLs/.SOs. If the dynamic library or heck even a static library your linking against used message passing imagine all the cases where that would be a bad idea. All that overhead just so you can call a function not written in whatever language you happen to be using. ABI's like we have now are a much better for this reason. Also just because one uses message passing does not mean there are no compatibility issues.
I think the main issue here is if we reference the article linked. "You Can’t Actually Parse A C Header". To know the size of types and therefore figure out memory layout you need to be able to read a C header. With that information you can construct a lot the ABI for a C function. Aside from some OS specific things like stack layout requirements and hardware/OS specific things like what registers are used for certain parameters of a function ect...
I don't see a way around this complaint other than starting from scratch. If you "fixed" C, you'd have to rebuild all those things underneath you. And the reason we do FFI is to not reinvent some chunk of code.
I think you fundamentally run into issues with ABI compatibility regardless of language. You get a step better if you could derive the ABI from the code, but there's always interop issues cross-language. I don't think you can avoid wandering into having to implement the isms of the provider's language.
So much baggage just comes from strings. Then you have higher level constructs like making threading/concurrency, memory management, etc. jive. That's even an issue at the API level. I use a ton of libraries that just wrap a C library with a language-idiomatic interface.
pub type intmax_t = i64;
With D's ImportC: import stdint; // reads stdint.h and all its declarations
intmax_t x; // declare x as type intmax_t
This is fun!Now if he was complaining of the move to ANSI c from K&R, then I would listen :)
What does “talking” C mean? It means getting descriptions of an interface’s types and functions in the form of a C header and somehow:
matching the layouts of those types
doing some stuff with linkers to resolve the function’s symbols as pointers
calling those functions with the appropriate ABI (like putting args in the right registers)
Well we’ve got a few problems here:
You can’t actually write a C parser.
C doesn’t actually have an ABI. Or even defined type layouts.
That we have this situation today still surprises me. I mean I know how we ended up here, but it is so unfortunate and has a huge cost.* but see the whole linked article for where portability breaks down.
If you had to write really assembler programs across multiple machines, you'd appreciate how much above assembly C really is for remarkably low performance cost. I agree with you - I think that's a major part of the popularity - it was not the first systems language, nor high(er)-level language. It was the first that was widely portable, widely applicable (vs. FORTRAN), and equal or better performance than native assembly.
This situation is much more noticeable now that we have Rust, Go, etc. all trying to be completely detached from the C run-time. And it's not just those. Even languages like Java and Python that do use the C run-time, they all want to have nice and easy FFIs nowadays, but it's still hard to do it safely and elegantly.
I imagine a cross language module concept with an accommodating module object file format would be ideal?
The modern way to interact with an OS is to stick a request on a ring buffer and get back a request ID, and (eventually) check the response ring for that ID to show up. Like in io_uring.
If your language is powerful enough, its OS library can encode that directly (without first marshalling arguments onto a stack that they must then be copied from) for minimal overhead.
You might also have an FFI thing so you can call out to libcurl, libzstd and libsqlite. (Do any others matter?) Again, if your language is expressive enough, you can avoid building up an argument list the usual way that must then be reorganized for what the foreign library wants.
It's a perfectly fine programming language. Some of us like working with sharp tools (and sub 1 second compile times for 100kloc programs, and close enough to optimal performance, and actually knowing what's really going on).
I do sympathise about the API abstractions; some of them have definitely not weathered well. As for ABI's, I've been constantly amazed how well they've been preserved (kernel folks will understand, e.g. 32-bit userspace with 64-bit kernels just works).
What will the API/ABI's look like 40 years down the road? (will any modern language endure like C?).
Of course, services aren't panacea and they incur large performance penalties due to the context switching. However, for non-performance critical tasks services are the future.
I liked the COM/ActiveX-way, which was fast and somewhat flexible. But proprietary.
They seem to be doing just fine.
XPC, drivers, Android/Fuchsia/ChromeOS IDL, COM, WinRT
D-Bus is not about interacting with libraries, it is about inter-process communication.
(Correction: i wrote an uninteresting paragraph about some thought i had here while re-reading the article to avoid misquoting and misinterpreting: re-reading convinced me that the author conclusions are right, and my paragraph is now useless. If you have nice counterpoints to the article in the comments, i would love to be wrong).
Forgetting how much would need to be replaced with objectively much slower code, Rust simply cannot do some things. You cannot always make absolute guarantees about memory safety for example when it comes to low-level programming. Sometimes to be fast, you have to make assumptions and take risks.
I am currently watching the Veloron game [1] as an example of how larger Rust projects may begin to look. I see something like this [2] and it doesn't look all dissimilar from C, just with a new syntax. Has writing this game eliminated all bugs? Nope [3]. Maybe there are less segfaults and bad memory management, but this was just one class of bugs.
[1] https://gitlab.com/veloren/veloren
[2] https://gitlab.com/veloren/veloren/-/blob/master/server/src/...
[3] https://gitlab.com/veloren/veloren/-/issues/?sort=closed_des...
Then, having used the C and C++ toolchains to boost themselves into a simulacrum of self-hosting, they immediately decide they don't want to actually implement their preferred abstractions in the operating systems they want to use, so they write huge FFI libraries to take advantage of the massive amounts of library code and OS interfaces that we've spent the past fifty years writing.
Finally, having arrived on the scene, decided that everyone else is wrong and they're right, using the bad-and-wrong toolchains to bootstrap their throbbingly correct and superior approach, we're treated to angry rants about how fifty years of engineering is messy and why wasn't some omnipotent project manager in the sky keeping all this in check while the world awaited the arrival of someone smart enough to write us, at last, a good and correct programming language?
I totally understand that looking into the spaces where our programming languages and our kernels intersect can lead to anger, dismay, and confusion. They're all living projects who have had to adapt to decades of massive change in the computing world; once the LispM went away all hope was lost for coherence. But whining about it in the context of how it makes this thing you felt like doing slightly more difficult is just petulant noise.
Just like open source developers don't owe anyone specific features or bugfixes, the Unix and C ecosystem doesn't owe anyone a rigorous, provably correct ABI. If you're not happy about it, don't use it. If you want to benefit from the decades of work, you have to accept the pitfalls that sprung up over that half-century. The alternative is to do it over, yourself, according to your principles. I desperately wish someone would; the benefit to computing would be incredible. So far, everyone dips a toe in the FFI pool, declares it to be too cold, and then swims around in it regardless.
The problem here is once compiled this information is not readily accessible without being able to read a C Header. That's why the author here mentions parsing a C header and says most people just end up letting a compiler do that.
If your writing C and ignoring things like INT_MAX or the minimum range requirements specified by the standard or not using sizeof() when your worried the memory size of these base types. Your code is not going to be portable.
C definitely let's you handle and know the domain of your variables.
I'd hate to see that happen, and I write C every day, read hundreds of lines of C everyday, and actually formally prove C everyday. C is not meant to be Rust or Go or Java. I'm actually against most of additions those days to the new standard. They complicate things, the (barely) portable assembly it's meant to be. I wouldn't write a database in C today, but I'd be happy to start a new bootloader project in C tomorrow.
The author is actually angry that the OSes still expose a C interface when they should use something higher level, like it seems Apple is doing (I may be wrong). I get that, but like we say in France, don't discard the baby with the bath water. The problem is that, as they say clearly, C is the lingua franca. C isn't the problem, its use is.
Not like GObject declaring the interface in a special comment (modulo typos). Something that's fully machine-parseable in any programming language and does not require access to the source code.
I mentioned C2FFI (https://github.com/rpav/c2ffi) in another post in this thread. That's an extra "spec.json" file that you need to distribute along with your library, but maybe that's a whole lot easier and simpler than inventing a new library format. And it's JSON, which is human-readable, so it's relatively easy to debug.
Although at least a lot of it can be setup with automated tooling. Although the interface seems to also have a lot more overhead than a basic function call. I hardly would call it a better situation.
This isn't the problem it appears to be. D's ImportC adjusts its semantics to match what we call the "associated C compiler", which is the predominant one on the target platform.
I know the purpose of the article is to shit on C, but the fact that it is the lingua franca _despite_ of it's shortcomings, is more than anything a testament to the versatility of the language, and a proof that it somehow ended up the right abstraction distance between javascript and microcode..
Nobody forbids you from making a language that does not speak C, and write a compiler that generates machine-code that has nothing in common with what would have come out of C source, and an OS that natively support whatever conventions that language has. It's not like hardware requires you to write C for it..
C compilers existed for all kinds of architectures at that time, including CPUs featuring 36-bit (yes, thirty-six bits) words.
A lack of flexibility would have resulted in a lack of adoption which would have rendered their work meaningless.
There's a macro, CHAR_BIT I think, that you can use to get a bit count if you're attempting to write really portable C
Clickbait headline, but an interesting article.
Aggressive ranting language, extreme emotional volatility, narrow collectivist vision, and very deep technical analysis & know-how.
Been this way for decades, at least as long as I’ve been paying attention. It’s a culture.
We use it for crossplatform dev.
We use json as data exchange format everywhere
We will run our apps in webassembly based sandboxes
So how about using this mature techstack to talk to OSes too?
You can't bootstrap an interpreted language like JavaScript or even WebAssembly, the VM has to be written in something.
Isn't this only for JIT? My understanding was that non-JIT VMs basically function as emulators of a non-existent CPU, so they interpret each bytecode instruction rather than asking the CPU proper to do a jump.
What makes you think that you need C/C++ for this? Compilers were and still are written in many languages, and you can always come up with a compiler written in an "arbitrary" language which outputs e.g. an executable ELF file containing your VM runtime code.
There are a few examples of this in the wild btw, look at e.g. the GraalVM and its SubstrateVM sub-project.
A compiler is different from a JIT engine since yeah you can just write whatever binary you want to disk, hell you could write an x86 compiler in javascript. but if you want to emit instructions to memory and send the CPU there to consume them, you need a smidge of native code.
If C is just wrapper over asm
Then emit it from any other lang
To replace this, you would need another compiled language. It can't be "any other lang" - it needs to be something compiled down to machine code that can be directly executed on your CPU. So you could write a new OS that presents an interface in Rust, or Zig, but never WASM.
That last requirement rules out most languages with any significant community besides C/C++, Rust, Zig, and a few other even lower profile C replacements.