7 ms·
To save C, we must save ABI (2022)
- usrnm 2mo agoThe famous C dilemma: we want to be as close to the machine as possible, but don't want to change anything when the machine changes
- tonyhart7 2mo agoso what they gonna do ??
- pjmlp 2mo agoBecause contrary to urban myths, C is a normal high level language like everything else. The Assembly like abilities have been growing as language extensions in specific compilers, not as part of ISO C. Going back to K&R C, inline Assembly or intrisics were not even available, all of that required using the Assembler directly.
- uecker 2mo agoNot every high-level language gives you byte-level access to the representation of memory objects. But it is also wrong to reduce a language to what is in the spec.
- pjmlp 2mo agoMany do, contrary to what many C advocates talk about. Apparently reducing the language to what is in the spec is only a thing when talking about C and to some extent C++. When other languages have compiler specific extensions beyond the spec, it is a failure in their design. Yet when C and C++ devs have to reach out to compiler specific extensions, it is not a design failure like it is pointed out to others, rather an advantage. It is also wrong to not apply the same measure when it doesn't suit the message.
- uecker 2mo agoLot of stawman arguments.
- embedding-shape 2mo agoWouldn't be a authentic pjmlp comment unless they shit on C/C++ and/or praise Java/.NET with a bunch of straw-men :)
- pjmlp 2mo agoHow wrong you are, C++ isn't in the same league as C, Microsoft was right not wanting to keep updating their C support. It was already outdated by the time Borland released Turbo C++ 1.0 for MS-DOS, and only got new wind thanks to GNU FOSS and their manifest to prefer C as the main compiled language for GNU projects. Everywhere else outside UNIX, was going with a mix of C++ for OS frameworks, Apple, Microsoft, IBM, Be, Nokia, Epoch,.... Naturally given the option, between C, C++ and something else I might prefer that something else, however I managed a few interesting positions exactly due to my C++ skills, and interests. So don't mix my preferences for C and C++ on the same basket.
- embedding-shape 2mo agoWouldn't trade it for anything <3 Enjoy your Tuesday mate :) > So don't mix my preferences for C and C++ on the same basket. That mistake is mine indeed, I'll remember. Thanks, and I hope "no harm meant" was implicit :)
- gchamonlive 2mo ago> Wouldn't be a authentic pjmlp comment unless... > and I hope "no harm meant" was implicit :) Ad hominen then an apology, mixed signals here or I'm missing something. Maybe sarcasm?
- throwlifeaway 2mo ago
- FooBarWidget 2mo ago> Not every high-level language gives you byte-level access to the representation of memory objects. Any code that ventures anywhere near that territory is 99% Undefined Behavior. It's almost impossible to write proper C/C++ code that isn't UB while touching byte-level representations.
- uecker 2mo agoThis is certainly not true. Accessing bytes of objects is well-defined in C.
- FooBarWidget 2mo agoJust look at this: https://blog.habets.se/2026/05/Everything-in-C-is-undefined-behavior.html https://blog.habets.se/2026/05/Everything-in-C-is-undefined-... This is undefined behavior! const int* magic_intp = (const int*)bytes; Heck even something trivial like this is UB: bool bar(char ch) { return isxdigit(ch); } The only safe thing to do is memcpy, but that's super useless. As soon as you try to interpret or manipulate the byte-level data in any way, there are UB traps everywhere you go.
- uecker 2mo agoYes, using an arbitrary type that is different from the one of the object is UB. But any access of a representation byte using a character pointer is well defined, not just memcpy and I would also not call memcpy useless.
- deleted 2mo ago[deleted]
- titzer 2mo agoIt's not normal in any other language that constant-folding in the compiler has different behavior than running an expression on the machine. C exists in a nether world of being neither assembly nor high-level language. People only call it high level because in the 1970s, having blocks, loops, and functions was high level, compared to the SoTa machines available in the day, which were either programmed with assembler or some bespoke thing the manufacturer came up with.
- thin_carapace 2mo agomay I ask specifically what aspects of C lead to examples of quasi low level status such as the one you gave? I'd hazard a guess the C abstract machine is defined in a particular manner differentiable from say the JVM?
- titzer 2mo agoIt has pointers and pointer arithmetic. Pointing into the stack, allocating buffers on the stack (and the resulting decades of stack smashing attacks that came with it). Most languages don't have an underlying model of a flat memory that you can just randomly point at and write things; they have objects and data types and functions that aren't meant to be pointed at (and usually cannot).
- thin_carapace 2mo agoah so other languages emulate harvard to a degree. I wonder if performance could improve by reimplementing a C like language based on a von neumann abstract machine inspired by something other than a PDP. thanks for your response
- Dylan16807 2mo agoIt's a similar distinction, but not really? Harvard is about separating code and data and C still does that with high reliability. Pointers and the stack and the important return addresses on the stack are all data. Strong barriers between different pieces of data are a different concern.
- bigfishrunning 2mo agoI would argue that inline assembly, while not in the standard, is really just a convenience feature -- it is part of the standard to declare an extern reference to a function in the symbol table and jump to it, it just requires a separate ASM object to link alongside your C object. Inline assembly doesn't allow anything you can't do without it.
- convolvatron 2mo agothat's kind of not true. inline asm lets me refer to the register that the compiler placed a value in. lets say I really want to use popcnt in my inner loop. with inline assembly I can just shove it in there. external linkage forces a function call overhead that can't be inlined, which obviates any benefit I might have had from using the specialized instruction.
- treyd 2mo agoYou'd probably also be able to use an intrinsic to avoid the pain of inline asm and make it portable.
- jonhohle 2mo agoIntrinsics usually come after new instructions have existed long enough for the higher level pattern across several architectures to establish a common pattern. If specific hardware is being targeted you may need to use those instructions before intrinsics exist.
- convolvatron 2mo agointrinsics are nicer in every way, assuming they exist. but some instructions are inherently non-portable. performance instruction like my popcnt example are good candidates since they can be implemented at varying costs on other architectures. but for systems programming there are control register and mode switch instructions that really aren't. some of those can be put into separate asm routines, but there are some that are poorly suited. segment long jumps on x86 are maybe an example. rdtsc is another one potentially. its also true that when I unwrap my new spin with fancy new instructions its unlikely to have a robust set of instrinsics around them. inline asm is a real mess, I always regret tussling with it, but its kind of pragmatically necessary if you're actually working at the metal in a high performance or embedded context unless you're doing the whole thing in assembly.
- MisterTea 2mo ago[dead]
- jjav 2mo ago> Because contrary to urban myths, C is a normal high level language like everything else. It's a myth that this is a myth. Have you ever worked with newbies learning C? These students split pretty hard into two camps (of course there are oddball exceptions): those who knew assembly and find C easy, and those who didn't and struggle with pointers until they finally get it (some, never do). Of the mainstream languages, C is the only one where understanding and dealing with direct memory access is a fundamental requirement if you're going to get anything done. The myth argument is that C code doesn't directly translate into the execution flow on the CPU. No, of course it doesn't. That's not the point. What people mean when they say C is lower level than most mainstream languages is because it forces you to deal with details most other languages paper over. Yes, multiple languages have some way of achieving this kind of memory access, but except from C, it is considered an esoteric edge case that mostly nobody needs.
- IsTom 2mo agoI might have learned all this too long ago and lost touch with how it's like to learn this, but: how is learning about using pointers in C different from using object references and array indices in python/js/java?
- mannschott 2mo agoIn C a pointer can point to any arbitrary offset within an object or array, to a local variable somewhere on the stack, to a static variable, to read-only memory, to the first(?) of an indeterminate number of characters (hopefully) terminated by ((char)0). To the first of a statically unknown number of variables located somewhere in memory, to a variable of indeterminate type (thanks to union), to I/O devices, and also to unallocated memory since there's no bounds checking. A reference can only point to an object or an array on the heap. Array indexing is bounds-checked. It seems obvious to me that these restrictions make references easier to reason about than C-style pointers, and therefore easier to learn.
- IsTom 2mo agoI imagine most of these examples (besides strings) isn't something you learn in the beginning and when e.g. doing operations on linked lists you're pointing to either allocated structs or null.
- wren6991 2mo agoExcept for the basic integer types. Change those as much as possible. Hell, CHAR_BIT=12 just to keep them on their toes. Personal pet theory: C is portable as in "you can retarget the compiler to any machine" moreso than "your code will run on any machine".
- trashb 2mo ago> Personal pet theory This is actually how c grew up. This is also one of the reasons why the spec is quite ambiguous in certain locations. C is made to be easily portable not a universal codebase for all platforms (though you can get quite close with some tricks like macros). Remember the spec allows C to run on a Unisys 1100/2200 just as well as on a pdp-11. I may be to embedded for this but if you want your code to handle long long as int64_t use <stdint.h>. I am of the opinion that you should always use fixed width types as portable types are a huge footgun and kind off redundant. Especially when you start doing a little more complex things expecting them to work exactly the same, like 128bit values on a 64bit platform.
- imtringued 2mo agoint was supposed to be the signed integer version of size_t. Meaning that it conforms to the native word size of the machine. But then a lot of software assuming that int means 32 bit got written and even 64 bit ABIs have kept int as 32 bits.
- wren6991 2mo agoI imagine there would also be some cache pressure increase if you inflated all of those ints to 64 bits.
- flohofwoe 2mo agoIt really hasn't much to do with C (e.g. there is no such thing as a "C ABI", and especially no such thing as a "standardized C ABI" - not sure if that's even a hot-take anymore). ABIs are defined by CPU and operating system vendors. Those ABIs usually happen to be quite 'C friendly', but that's not a requirement (for instance the AmigaOS ABI was primarily meant to be used from handwritten assembly code, and Amiga C compilers had to adapt to those ABI rules or they wouldn't be able to call into the operating system DLLs).
- sylware 2mo agoYep, an ABI (function call convention) is computer language agnostic. It is a binary specification. And in real life, only a subset of it is actually used. If they want to find something really obsolete, they better have a look at executable/dynamic lib file formats (PE+, ELF64). In other words, they better look at that first: I am using my own, which is beyond simple (a little RFC would suffice), no loader of any kind, basically userland syscalls. And I do embbed exes in an ELF64 capsule to run them transparently on linux systems (writting a internal linux exe loader would be copying ELF loading code while trashing 90% of its code). (hopefully in some not too far future, I'll try to build a mesa AMD vulkan driver for this very simple format and for that the main issue is.. c++ with its runtime, as always...).
- leoc 2mo agoNo-one cares anymore about ancient computers or weird specialist systems of course, but don't neglect my important requirements by breaking compatibility with any the platforms I am relying on at any point in time. We also need all of the most aggressive optimisations that compiler writers can come up with—this is high-performance code, after all!—but we certainly don't have time to deal with any breaking changes that would force revisions to our big important codebases. Make sure we can realise significant performance gains with just a simple recompilation. But remember to keep everything straightforward and close to the machine: we really hate all that weird UB which it's so easy to trigger by making an obvious, reasonable assumption which turns out to be wrong for some inexplicable reason.
- jdw64 2mo agoBut the industry ultimately runs on compatibility, so I get why they do it. But if compatibility breaks, wouldn't hardware vendors die out? If you look at PLC and other hardware manufacturers, they're not even using modern coding. They're still running on old code. They say it's 'safe and certified code,' but in reality, it's just legacy code. Because in hardware, programmers, aside from researchers, are often paid much less and work in worse conditions compared to their software counterparts. At a software company, code is the product itself. But in manufacturing, software is treated as a cost attached to machines worth billions of dollars. While equipment and sensors keep getting updated and more expensive, the people connecting everything are seen as a cost cutting target. So hardware programmers generally have good job security, but their salaries aren't high. In that situation, asking them to learn something new instead of sticking with the old ways usually gets resistance, because they're not being properly compensated for that learning
- mschuster91 2mo ago> In that situation, asking them to learn something new instead of sticking with the old ways usually gets resistance, because they're not being properly compensated for that learning And on top of that... these things are battle tested, often running machinery that isn't just worth millions of dollars but runs goods worth orders of magnitude more. Stuff breaking because some new shiny thing has been introduced... no one bats too much an eye when Reddit's UI is missing a widget here and there because someone pushed vibecoded garbage to prod again, but a car manufacturing line? A chemical plant that needs to operate 24/7 so that nothing solidifies in pipes, wrecking the entire facility to the point you need to fully dismantle it? When this kind of consequences are in the air, everyone is much much more conservative, because no one wants to be left holding that bag.
- jdw64 2mo agoExactly. You're in the same industry as me. The moment you try to change something, if the production line stops, the losses are enormous—so everyone becomes conservative. That makes it even harder to change later... It's a really difficult problem
- 2mo ago
- xyzsparetimexyz 2mo agoYeah, well, that didn't happen
- aw1621107 2mo agoNot yet, at least. There's a paper in the works [0] and it's been continually updated for the past few years so at the very least it doesn't look like this approach has been outright rejected by the committee. [0]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3913.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3913.htm
- codeflo 2mo agoThousands of words that boil down to "intmax_t isn't ABI-stable". Who knew? (Everybody.)
- aw1621107 2mo agoI think that's somewhat overly reductive. A significant portion (maybe 1/3-1/2?) of the article is devoted to describing mechanisms by which an ABI could be evolved, including a new (?) mechanism implemented in the author's Clang fork and submitted to the C committee ([0] in the blog post, currently on revision 8 [1]). Sure, it isn't a perfect solution, but as the author says: > at least we’ll finally have the chance to have that discussion [about breaking ABI] with our communities, rather than just being outright denied the opportunity before Day 0. [0]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2901.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2901.htm [1]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3913.htm https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3913.htm
- uecker 2mo agoThe mechanism actually does not solve the problem it claims to solve.
- aw1621107 2mo agoHow so?
- uecker 2mo agoThe issue is that a programmer is allowed to declare a function on its own without including the header, but then a function aliasing feature would not be visible and does not help. But if we waived this allowance, then a simple macro would do the job as well.
- aw1621107 2mo agoI think the most recent revision of the proposal basically says it not helping for such use cases is intentional? e.g., from Section 4.4.1. Standard Library Redeclaration [0]: > Thankfully, we are not particularly concerned about the ability to upgrade this [user-redeclared stdlib] function: users who are declaring Standard Library functions without including the header like this are doing this strictly as experts. They have a strong expectation of what symbol they are getting from their distribution. Transparent aliases are meant to be used for functions which rely on type definitions or structures which may change, prompting the need to provide updated global variables and updated functions without breaking old binaries. > <snip> > Therefore, we do not do anything to support or inhibit such declarations. Implementations looking to keep such declarations working from older versions of code should consider leaving those old symbols within their binary artifacts (system tables, shared/static libraries, etc.) to continue supporting such a use case; this proposal is not going to address it or the myriad of other issues around this (such as strong/weak symbols and other attributes/aliasing issues). To be fair, that section is talking about the stdlib specifically, but nothing jumps out to me as precluding it applying to libraries in general. [0]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3913.htm#design-compatibility-redeclaration https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3913.htm#d...
- aw1621107 2mo agoPrevious submissions with comments: - 2023-06-10, 64 points, 16 comments: (https://news.ycombinator.com/item?id=36249253 https://news.ycombinator.com/item?id=36249253) - 2022-03-13, 175 points, 129 comments: (https://news.ycombinator.com/item?id=30660528 https://news.ycombinator.com/item?id=30660528)
- add2 2mo agoCOM-like C ABI saves C++
- solar_quick0p 2mo agoCurious. Care to elaborate further?
- sylware 2mo agoLook at directx.
- pjmlp 2mo agoYeah, if you ignore the type libraries and metadata that comes along with it.
- clbrmbr 2mo agoI was battling GCC… until the new guy (a smart business major) pointed out I could just compile from Lua to ASM directly. Claude was happy to write a compiler over night. :facepalm:
- fragmede 2mo agothat sounds like a fascinating use case. why were you writing Lua in the first place?
- clbrmbr 2mo agoCreating on-demand automations for an ESP32-based product, that includes driving a display up to 1000px.
- Dylan16807 2mo agoDoes this relate to the article? Does it follow the lua spec? How are tables implemented? How fast is it compared to normal Lua, luajit interpreter, and luajit jit?
- clbrmbr 2mo agoOh just that it made me confront ABIs. I have a special purpose use case (home automation), so I wasn’t worried about general purpose compilation, just the subset of lua that fable tends to write for the prompt we use. I have no idea how it’s implemented. Lua goes in, position-independent dependency-free xTensa LX7 code comes out the other. Maybe I should read the code? It’s extremely fast. 3kB of lua becomes 1kB of machine code. I’ll have to get a blog post together on it.
- creshal 2mo agoIs C even worth saving at this point? Even standards committee members can't consistently write C code that doesn't overflow and segfault.
- dummydummy1234 2mo agoWhat other cross-language abi do you propose?
- pjmlp 2mo agoWhat everyone else is doing, COM, XPC, Android IDL, FIDL, D-BUS, gRPC,... Especially more relevant when going with microservices, microkernels, serverless, static linking (and still have plugins),...
- bigfishrunning 2mo agoThose are all RPC mechanisms, not an ABI. They exist at a much higher level. You could have an RPC protocol be your only interface to the OS, and that would be a valid design, but would have a performance cost (see the classic Tanenbaum–Torvalds debate)
- pjmlp 2mo agoNope, when you use in-proc calls, which some of them do support, they become an ABI as well.
- zbentley 2mo agoThe subset of the list that supports in-process calls and which allows for zero-copy (and, realistically, probably mutually mutable) data sharing of its types is shorter still. I think the useful definition of "ABI" includes more than just API discoverability (aka what D-Bus provides if you take away the RPC system).
- wasmperson 2mo ago
- flohofwoe 2mo agoShould have a (2022) in the title, not that anything has changed (AFAIK), but the sky didn't fall either ;) In the end, OS/CPU combinations define ABIs, compiler toolchains (no matter what language) can't do much more then follow (if they want to be able to talk to the operating system at least). E.g. if one day operating systems implement stable Rust-friendly ABIs, then C compilers will have to adapt to those conventions instead.
- okanat 2mo agoOS and CPU combination define their ABI as "whatever our most popular / default compiler did in 90s". That's the problem with C-based ABIs. OS ABIs aren't independent of the language. So any new language must include a C compiler within for ABI access. See https://faultlore.com/blah/c-isnt-a-language/ https://faultlore.com/blah/c-isnt-a-language/ for more detailed analysis.
- flohofwoe 2mo agoSure, but the C standard is the wrong place to do anything about the "problem". Also IIRC it was really only UNIX which had this "whatever our C compiler does" mishmash. On most other operating systems it was the other way around, C compilers had to implement whatever calling convention the OS already had defined before there even was a C compiler for that OS (for instance early Windows version used a PASCAL calling conventions, and others (CP/M, DOS, AmigaOS...) some random rules that were most convenient for handwritten assembly code). PS: and yeah I know that other article (or rather: incoherent rant). It's basically a lot of barking up the wrong tree.
- imtringued 2mo agoIt's barking up the right tree. I don't want to say more, because that would tip off competitors and I'm just a lone wolf working on this.
- bigfishrunning 2mo ago> OS ABIs aren't independent of the language. I'd push back on this a bit. The ABI is "put the arguments on the stack this way and jump to this address". The fact that it's easy to do in C doesn't really make a difference, or make that process "C specific" at all, any other language would need to do the same thing. If you require those arguments to represent rust objects or be reference-counted in some way, it would impose more restrictions on the caller, not fewer.
- sylware 2mo agoI skimmed the article, is this guy actually advocating for planned obsolescence or did I miss something??
- deleted 2mo ago[deleted]
- davemp 2mo ago> legacy choices when we quite literally were just not smart enough to be making permanent decisions like this Unnecessary hubris. I assure you the original ABI authors were plenty smart and just faced a different set of problems. > Our forebears are either not interested in a world without the mounting, crushing debt or just prefer not to tackle that mess right now The article mentions the organizational/social part of this problem, but then goes on to drop this turd. I can also assure you that our forebears were neither malicious or lazy; but faced the same problem this proposal does. I don’t like seeing such disrespect for the folks who laid out the groundwork for us.
- bluGill 2mo agoThey faced much slower computers with very limited memory. They couldn't afford the tricks that today are minimal cost.
- kmacleod 2mo agoI discovered this recently as I tested an old idea I had to allow a dynamic language to use a C++-like vtable instead of an inline cache (like Objective-C or JavaScript does). It turns out modern CPUs predict an inline cache hit/miss result much better than always incurring the cost of the fetch in a vtable lookup.
- Decabytes 2mo agoOne of the most frustrating things in my opinion about new systems languages, is that they refuse to have a stable ability to, so everything has to pretend to be C at the boundaries.
- imtringued 2mo agoThat's the correct decision. When Rust chose an unstable ABI it did so for a good reason. I personally think the only place where a stable ABI would be warranted is inside the panic machinery and this is mostly because it is kind of annoying to write perfect no panic Rust just to get rid of the 300KiB overhead but even here I'd take my time, possibly decades, before making the decision to have a stable ABI. The wrong decision is to choose to support a stable ABI and lock in design mistakes early on. I'm already hinting at the solution so I'll be taking my leave for now.
- CurbStomper 2mo ago[dead]
- raytracer1 2mo agoI used to an embedded software engineer, but I did not know this abi thing.
- hdhcbdb 2mo agoIt's quite normal for embedded systems to be built in one go and thus not have to care about ABI In my opinion, that's the correct thing to do anyhow, even for bigger systems
- black_13 2mo ago[dead]
- Elder_Baker 2mo agoWho cares if C dies. If dies then dies do not deserve to more. It means progress.
- icedchai 2mo agoYou must grossly underestimate how much C code is out there. It will be alive (though not necessarily well) after everyone here is gone.
- potus_kushner 2mo agothe ramblings of a madman.
- Panzerschrek 2mo ago> // Proposed: Transparent Aliases > _Alias g = f; I think defining a constant function pointer should work. As long the compiler doesn't store this pointer in the result executable and optimizes-out all calls via it into calls to the source function.
- Panzerschrek 2mo agoThe root cause of all C ABI problems are shared libraries. They give so much trouble with little to no benefits. Ideally no application should use them. All dependencies should be compiled from sources with the same compiler and standard library. System libraries aren't needed either, it should be possible to perform syscalls directly. In such approach no ABI incompatibility can happen. One may say, that shared libraries can save some space for both disk storage and RAM. But such savings aren't that huge and in some cases are even negative - if a library is linked-in, it's possible to discard unused functionality and even inline many library functions.
- zbentley 2mo agoThis has been somewhat argued to death, but even if you put aside operational concerns with static linking (security/size/independent upgradability), many attempts to do away with shared library ABIs end up reinventing them--at least for software whose job it is to integrate with other software on the machine, which is a lot of it. If you statically link a cryptography stack, you suddenly need a lot more information about what certificate/cipher systems are available on the host via IPC. If you statically link media codecs, you suddenly need a lot more information about hardware acceleration from the host via IPC. If you statically link libraries to manipulate binary data in some shared format, you need extra code to runtime-determine things like endianness. If you're asking hardware to do chunky numerical math, suddenly you need to prepare data with specific widths/sizes and you need to determine that from somewhere. Windows did good work here with COM, but the average windows app is both more self-contained and targets fewer configurations than a Linux app, so many of those issues don't come up as often as they do on Linux. Linux has a long way to go here, both because there's nothing as capable/ubiquitous as COM there, and because so much Linux software is small and therefore necessarily not self-contained (not talking about the UNIX philosophy and shell tools here--talking more about runtime intermediate layers like VAAPI or compat shims or protocols with multiple implementations).