Rendered at 22:23:40 GMT+0000 (Coordinated Universal Time) with Cloudflare Workers.
davemp 10 hours 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 10 hours ago [-]
They faced much slower computers with very limited memory. They couldn't afford the tricks that today are minimal cost.
kmacleod 8 hours ago [-]
I 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.
p_l 3 hours ago [-]
Inline caches require runtime supports that were ideologically verboten to C++
Conscat 2 hours ago [-]
You can implement it with templates and people do things like this often. In modern C++, it's even pretty easy to make this type of trick support `constexpr`.
Dylan16807 3 hours ago [-]
There's plenty of direct evidence of preferring not to tackle the mess right now. What makes you think that claim is a turd?
You're taking the idea of respect too far.
usrnm 14 hours ago [-]
The 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
pjmlp 13 hours ago [-]
Because 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 13 hours ago [-]
Not 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 13 hours ago [-]
Many 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.
groundzeros2015 9 hours ago [-]
You comment this almost everyone it comes up. The hardware also isn’t x86! That’s an abstraction too.
The point is in C you have greater control of execution and resources, not that it matches the hardware exactly. It’s a spectrum and C is closer on that spectrum than JavaScript.
pjmlp 7 hours ago [-]
Because just like your comment proves the point, many think only C can do this.
So I keep re-educating folks that isn't the case.
Every thread has different people reading it, so there is always a first time for many of them.
jstimpfle 7 hours ago [-]
You are again misreading even the most clearly put statement. Compared to e.g. Javascript, C is "closer" to the hardware, gives you "more control" of it. It would be completely ridiculous to deny this fact.
And if you move to e.g. C# / Java or similar, if you squint, and you try to be a smart-arse, then you could deny that C is closer to the hardware than C#, because C# probably has everything you need to control it, to the same degree that C allows you to. But if you work in these languages for a while, and look at the code that you ended up producing, then again you will absolutely find that it would be ridiculous to not admit that C gives you better control.
And you could even extend this to Rust, because the language encourages you to use high-level prefabricated components. It discourages you from doing low-level things, at least a little bit I think (I'm not a Rust user).
I think what you are doing all the time, is you are being a smart-arse, nothing else. What interesting low-level performant things have you actually programmed lately?
pjmlp 7 hours ago [-]
Smart-arse is comparing C versus JavaScript, instead of C vs C++, for example.
And then coming with such lengthy ad hominem.
Let make a fun exercise for the audience, given your performance remark.
Paste a random C code that I should replicate in whatever language I feel like.
There is one rule.
If the sample code is pure ISO C, then I will only use what is in the standard of whatever language I pick up.
If the sample code makes use of single language extension not part of ISO C, then I will have the freedom to also pick whatever language extensions I feel like.
nomel 1 hours ago [-]
Requesting a block of system memory, by address, and writing to it.
This is common with C, when interfacing with hardware.
Dylan16807 3 hours ago [-]
> Smart-arse is comparing C versus JavaScript, instead of C vs C++, for example.
Using C++ as your other comparison point when arguing that C isn't low level is by far the most smartass idea in this thread.
jstimpfle 5 hours ago [-]
So do you want to "rewrite" some C code in C++ to think you made a point? I think you should do C# or Java.
Or what about you do an audio or video codec? Or an operating system?
Not going to paste any of my own code, because any non-trivial stuff is hundreds to thousands of lines. But one more example (that I recently did myself): Create a block allocator (power of two blocks) with bookkeeping in shadow memory (administered in individually committed zones representing virtual memory regions of 64 MB (2^26)). Any used memory has bookkeeping support for being sub-allocated at any and all levels up from 64 KB (2^16) to 64 MB (2^26), and even higher (by joining committed regions). Individual blocks are collected (using intrinsic linking, because no memory allocation) in a hierarchy of pools of same-sized chunks that have the same parent, and can be recursively sub-allocated on any smaller chosen power-of-2 level, and finally be consumed in linear fashion (arenas). Blocks are pooled with a moderate retain policy (watermark system) to allow subsystems to almost completely avoid any system calls and avoid inter-thread synchronisation. The memory overhead must be below 1% even though it's totally flexible (as said has metadata for all levels from 64 KB up).
The bookkeeping should function on 32-bit systems (small virtual space, occupancy range from megabytes to 3 GB) as well 64-bit systems (2^48-2^57 bytes of virtual address space, occupancy range from megabytes to hundreds of gigabytes) with reasonable overhead compared to actual usage.
This requires intrusively linked lists, occupancy bitmasks, bit-counting and bit-prefix counting, OS syscall access (virtual memory), pointer arithmetic (alignment needed to address shadow bookkeeping memory) and thread synchronisation. The reference code is >> 95% pure ISO C++11 (could be C99 with few changes), with a little platform code glued in. It works on Windows but it could be ported to Linux in a few hours. It supports a mostly-immediate-mode GUI with hundreds of thousands (maybe millions?) of small variable-sized allocations per second. Allocation has almost completely disappeared from the CPU profile, well below 1% of CPU usage.
groundzeros2015 7 hours ago [-]
> If the sample code makes use of single language extension not part of ISO C
What are you even arguing right now? (Btw -ansi compiler flag)
> Smart-arse is comparing C versus JavaScript
I chose JavaScript to make the idea of a spectrum clearer using extremes. I can do C++ if you like. The machine doesn’t care about destructors, move, concepts, initializer lists, virtual methods, launder, or inheritance. You are programming against an abstract model further divorced from how x86 CPUs work.
jstimpfle 11 hours ago [-]
_You_ do that. All the time. And then you fight these strawmans.
pjmlp 10 hours ago [-]
And you reply to that all the time with the C bias as well, oh well.
uecker 12 hours ago [-]
Lot of stawman arguments.
embedding-shape 12 hours ago [-]
Wouldn't be a authentic pjmlp comment unless they shit on C/C++ and/or praise Java/.NET with a bunch of straw-men :)
pjmlp 12 hours ago [-]
How 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 12 hours ago [-]
Wouldn'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 10 hours 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?
embedding-shape 9 hours ago [-]
Yeah, original/parent comment I wrote with a twinkle in my eye (hard to communicate though), hoping that the smiley at the end conveyed it, but I might have replaced ; with : mistakenly.
I think many of us throughout the years been reading pjmlp's comments which fits a certain "theme". I don't mind though, it's just text after all, but was hard to keep myself from entering the meta-conversation when the opportunity just sat there. I still don't mean no harm by it, we all have our less agreeable ways of writing our comments, I'm surely guilty of it in some way too.
gchamonlive 9 hours ago [-]
I understand now this is old gripe, and we are all to blame for this guilty pleasure I think :)
throwlifeaway 10 hours ago [-]
> Ad hominen then an apology, mixed signals here or I'm missing something. Maybe sarcasm?
You can express annoyance at someone's pattern of behavior without it being personal. embedding-shape isn't the only person annoyed by pjmlp's repeated disdain and snark towards people who use C (or Zig or WebAssembly or Rust or...).
pjmlp 7 hours ago [-]
Usually I reply in the same tone as I get talked with, I have no qualms with touchy feeling culture of modern times.
throwlifeaway 2 hours ago [-]
> Usually I reply in the same tone as I get talked with
No, you're usually the initiator. Usually it's with some off-hand quip about how C programmers don't understand C, or how the people designing WebAssembly are ignorant of COM or the JVM, or how Zig is just Modula-2, etc.
Most threads you participate in aren't filled with snark until you enter them.
gchamonlive 6 hours ago [-]
That explains the escalations
gchamonlive 10 hours ago [-]
I didn't see snark prior to being provoked, but this seems like a discussion with history beyond this scope so I think I'd better sit this out
pjmlp 12 hours ago [-]
Really?! That is how many in C circles, including your regular comments to my comments happen to be like.
Two measures two weights, in C versus other systems languages.
uecker 12 hours ago [-]
Maybe provide a concrete example instead of making vague accusations. Or rather, please not, it is not a useful discourse. A productive response to my comment would be an insightful explanation of how byte-level access to memory objects is done in other languages.
muvlon 11 hours ago [-]
> explanation of how byte-level access to memory objects is done in other languages.
I'm not pjmlp but I can explain this for the case of Rust, where this works a bit like C but with a few interesting differences.
Mainly, in Rust there is not a concept of a "memory object" per se in the runtime semantics. Memory is made of allocations and allocations are made of bytes. Unlike C, bytes are guaranteed to be 8 bits in size. Every byte of memory can hold integer values (0x00 to 0xff), pieces of a pointer or be uninitialized. That means there is nothing like strict aliasing, and therefore no need to have special rules for byte-level access. You can alias any type as any other type, so long as you avoid all the other sources of UB (out-of-bounds access, uninitialized memory access etc.).
The way to practically access this is much the same as in C. You can do things like cast pointers between different types and project a pointer to a struct to a pointer to one of its fields. It should be noted that, unlike with major C implementations, structs do not have a stable, well-defined layout, so if you do manual pointer math you need to put #[repr(C)] on the struct to get C layout rules (which might still yield platform-dependent field offsets, e.g. size_t is not the same size everywhere).
Note also that these are the dynamic rules of Rust, you need to follow these when writing unsafe code to avoid UB. The static rules of safe Rust are much more restrictive and don't allow much at all. It is possible to write unsafe code that exposes safe abstractions for this, one example is the "bytemuck" crate. It provides macros that can parse a type definition to check certain properties (e.g. well-defined layout, no padding) and then provide you with safe functions for byte-level access. Since there is no strict aliasing, for certain types you can also get safe functions for access at other granularities. For example:
#[repr(C)] struct Foo {
x: u32,
y: u16,
z: u16
}
can be safely accessed as an array of u32 values (uint32_t in C), but
Yes, Rust copied many good ideas from C (and added many new).
BTW: If you use character-pointers, you also do not need to worry about strict-aliasing in C.
pjmlp 10 hours ago [-]
Easy accessible in a NEWP, Mesa, PL/I, Modula-2 or Ada manual, on how to map structs into byte arrays.
Or for something more modern either D or C++ will do.
Examples omitted on request.
throwlifeaway 10 hours ago [-]
> That is how many in C circles
Being able to find someone who's made the argument you're rebutting doesn't make it not a straw man. What matters is whether the person you're arguing with is making the argument.
Specifically this:
> When other languages have compiler specific extensions beyond the spec, it is a failure in their design.
Is not a point I've seen anyone here make.
sylware 11 hours ago [-]
"When other languages have compiler specific extensions beyond the spec, it is a failure in their design."
This one of the failures of Linus T. with the linux kernel: he was not able to keep the assembly source code with plain and simple C code you can compile with a small and alternative C compiler (same failure for the glibc devs I think).
I don't blame him, he is already keeping the linux ABI stable, and pulling that off is something.
Dylan16807 2 hours ago [-]
> assembly source code with plain and simple C code you can compile with a small and alternative C compiler
Which part of that big clause is the part that failed? Because I thought you could still compile Linux with TCC.
jstimpfle 10 hours ago [-]
Many other languages only have one compiler available to start with.
Each additional compiler supported by a project means variance in functionality and thus additional work for the project. That work could make the codebase more robust. Or it could be a ton of useless work. Or anything in between. Depends on the context of the project.
FooBarWidget 12 hours 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 12 hours ago [-]
This is certainly not true. Accessing bytes of objects is well-defined in C.
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 9 hours ago [-]
Yes, 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.
9 hours ago [-]
bigfishrunning 10 hours ago [-]
I 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.
Dwedit 3 hours ago [-]
Inline assembly is inline. You're not following the platform ABI's calling convention here, you are choosing input registers, output registers, and trashed registers right there. Following the platform's ABI and carrying out the function call has a cost, merely picking registers does not.
jonhohle 8 hours ago [-]
What does an external function have to do with the C supporting or not supporting ASM? The symbol is just a symbol from another object. That could be written in any language that follows the ABI (not that ASM has any enforcement of ABi to begin with). The symbol resolves to an address and nothing more.
Saying inline ASM is no different than a function call is like saying standard control structures are no different from function calls. I suppose from a Smalltalk perspective that could be true, but is that the mental model most programmers use?
I work on a system from the 90s with custom instructions. GNU-as was patched to understand the instructions. They’re used through macros that ultimately expand to inline ASM. Without this, you’d need function inlining, which may or may not be possible with a linked object (it certainly wasn’t standard in the 90s). So now a single instruction turns into stack management, a jump, more stack management and a return. At that point any benefit to a specialized instruction may be erased, or in the case I’m dealing with talking to external hardware becomes unreasonably expensive.
convolvatron 9 hours ago [-]
that'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 8 hours ago [-]
You'd probably also be able to use an intrinsic to avoid the pain of inline asm and make it portable.
jonhohle 8 hours ago [-]
Intrinsics 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 7 hours ago [-]
intrinsics 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.
titzer 11 hours ago [-]
It'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.
pjmlp 10 hours ago [-]
There are high level systems languages starting with JOVIAL in 1958 for the SAGE radar system.
C only exists instead of the alternatives, because according to Dennis Ritchie himself it was more fun to create C than using something else, and I quote:
"Although we entertained occasional thoughts about implementing one of the major languages of the time like Fortran, PL/I, or Algol 68, such a project seemed hopelessly large for our resources: much simpler and smaller tools were called for. All these languages influenced our work, but it was more fun to do things on our own."
Quote does not support the claim that the only reason C exists was that it more fun
pjmlp 7 hours ago [-]
You read it your way, I read it my way.
"All these languages influenced our work, but it was more fun to do things on our own"
tikitorch200 2 hours ago [-]
I never really thought much about this quote before now, although presumably I had read it…interesting how humble Dennis Richie was
AnimalMuppet 9 hours ago [-]
That doesn't quite say what you claim. The choice was because the other languages were too large/complicated, not because of fun.
pjmlp 7 hours ago [-]
"...but it was more fun to do things on our own."
lioeters 6 hours ago [-]
"..simpler and smaller.."
jstimpfle 1 hours ago [-]
But I assume that both on compiler and on machine, the evaluation is still conforming to the semantics of the C abstract machine?
p_l 3 hours ago [-]
Unix team developed C partially to regain a bit of the state of the art that they were excluded to earlier, with added constraint of being very small machine so they couldn't just fit a state of the art language without making complex multi pass compiler - not in 32kB of RAM
thin_carapace 11 hours ago [-]
may 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 10 hours ago [-]
It 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).
jstimpfle 55 minutes ago [-]
It isn't actually that flat in the spec, though modern machines' address spaces are. So in a sense it is merely an accident of a specific implementation that you can smash stacks.
thin_carapace 10 hours ago [-]
ah 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
im3w1l 5 hours ago [-]
Well the title of this very post is about the ABI. The ABI provides some guarantees on what the compiler output will be. It guarantees that parameters will be read from certain registers and results will be written to other registers. Most languages do not offer such guarantees.
MisterTea 9 hours ago [-]
[dead]
flohofwoe 11 hours ago [-]
It 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 11 hours ago [-]
Yep, 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...).
wren6991 13 hours ago [-]
Except 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 12 hours 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 2 hours ago [-]
int 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 1 hours ago [-]
I imagine there would also be some cache pressure increase if you inflated all of those ints to 64 bits.
leoc 5 hours ago [-]
No-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.
Thousands of words that boil down to "intmax_t isn't ABI-stable". Who knew? (Everybody.)
aw1621107 13 hours ago [-]
I 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.
The mechanism actually does not solve the problem it claims to solve.
aw1621107 13 hours ago [-]
How so?
pdw 12 hours ago [-]
Because it imagines that no library other than libc has an ABI that depends on intmax_t.
Suppose I have a libfoo that has a public function that takes an intmax_t parameter. Or that has a public struct with an intmax_t field. It will be compiled for a particular definition of intmax_t. If you try to link it with a program that uses a different definition, it will fail.
The article's solution with the "MY_LIBC_NEW_CODE" define cannot work because no existing C code knows about "MY_LIBC_NEW_CODE".
The proposed mechanism is somewhat useful to a library that wants to provide multiple incompatible implementations of a function. (But this is mostly only interesting for libc implementations that need to handle historic incompatibilities between all the various Unix specs. Other libraries can just give their new, incompatible function a new name.) It's useless if you want to make an incompatible change to a type definition.
aw1621107 12 hours ago [-]
> Because it imagines that no library other than libc has an ABI that depends on intmax_t.
I don't get quite the same impression. The sense I get is more that such a change would basically need to happen "bottom-up":
> Some of [the scenarios that aren't fixed by this proposal] are just the normal dependency management issues. If you build a library on top of something else that uses one of the changed types (such as intmax_t or something else), then you can’t really upgrade until your dependents do.
> <snip>
> For those of us in large ecosystems who have to write plugins or play nice with other applications and system libraries, we’re generally the last to get the benefits.
In which case the benefit of the proposal (as far as I understand) is that such bottom-up changes can occur without forcibly breaking other consumers.
flohofwoe 10 hours ago [-]
It was never really possible to compile a library with one C compiler and expect it to link against code produced by another C compiler, unless both compilers happen to agree on specific ABI details that are either defined outside the C standard or not at all.
And to be honest, this sort of compiler-specific ABI interoperability is a non-problem that doesn't need solving, it's at most relevant for software developers of closed source libraries who distribute the libraries as precompiled blobs. But those must be stamped out for different target-triples anyway.
aw1621107 10 hours ago [-]
I'm... a bit confused? I wasn't thinking about inter-compiler interop at all.
flohofwoe 9 hours ago [-]
Compiler interoperability is the only ABI related problem area that's remotely in the scope for the C standard, but IMHO not even that (it's really not a problem that needs solving).
Ultimately any ABI discussions need to happen between CPU and OS vendors, compiler toolchains implement whatever comes out of those discussions.
aw1621107 9 hours ago [-]
> Compiler interoperability is the only ABI related problem area that's remotely in the scope for the C standard
idk, given how ABI impacts the evolution of C I think it's not unreasonable to provide a mechanism by which ABI can be evolved even if it's not specifically for compiler interop.
> Ultimately any ABI discussions need to happen between CPU and OS vendors, compiler toolchains implement whatever comes out of those discussions.
I think part of the article author's reasoning for proposing this feature is that toolchains have implemented something like this feature to try to address ABI issues and that the rest of the ecosystem could benefit from a similar technique.
uecker 9 hours ago [-]
Huh, compiler interoperability is extremely useful. It usually works by ABI groups defining a common ABI for each architecture and compilers then following these ABIs (although not required a compilers that does not is poor)
flohofwoe 7 hours ago [-]
It's useful for closed-source developers who want to sell their libraries as precompiled binary blobs for static linking, but as soon as MSVC is involved that idea of "one library for different compilers" is out the window anyway ;)
For areas like application plugins via DLLs it's the OS ABI that matters.
uecker 5 hours ago [-]
It is also relevant for dynamic linking on all Linux binary distributions.
uecker 12 hours ago [-]
The 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 12 hours ago [-]
I 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.
Indeed, it now admits this. But what problem is this then really solving?
aw1621107 9 hours ago [-]
> But what problem is this then really solving?
Well, I'd presume that's what the paper's "Introduction & Motivation" section is for. Do you take issue with the authors' statements there?
uecker 4 hours ago [-]
Yes, it does not solve the problems described there.
aw1621107 4 hours ago [-]
For lack of the creativity needed for better wording (and hoping I'm not inadvertently going in circles here) - how so?
prussian 10 hours ago [-]
I think the bigger issue is time_t, and I have, in fact wrote comments that literally state the code is "best before Jan 19, 2038." Said code may never be recompiled since it will trigger regulatory certification.
imtringued 2 hours ago [-]
intmax_t is a dumb idea.
Conceptually intmax_t is a generic type of the form intmax_t<T>. Since C does not have generics, the T is chosen by the compiler during compile time.
But this means that the first time you compile any shared library with an intmax_t parameter or return value in one of its functions, you have permanently baked in the type parameter T to whatever the compiler chose it to be at that moment in time.
You cannot retroactively change intmax_t even if you change the symbols, because intmax_t runs into the same problem any generics system does, you cannot retroactively add instantiations for future types that were not explicitly compiled into the dynamic library.
Even if C gets generics and intmax_t would become obsolete either way, because you don't need intmax_t<T>, you can just have T.
intmax_t is only interesting for choosing the T and even then it is only interesting inside function implementations and never in their signatures.
So my conclusion is that intmax_t was a failed attempt at trying to be "clever" with the idea of introducing generics without introducing generics. This is an idea that is so doomed that anyone trying to rescue it, didn't really understand the problem with intmax_t.
pjmlp 13 hours ago [-]
Not really, because many don't even know there isn't such thing as C ABI, rather the OS ABI, when the OS happens to be written in C.
simiones 11 hours ago [-]
Even this is too simplistic. While C compilers normally use the OS ABI, there is nothing that requires them to do this, and other languages don't. Of course, when you need to call functions provided by the OS, you have to do so following the OS ABI; but calls between functions written in your own language, even in different libs, don't need to follow this same rule.
Elder_Baker 2 hours ago [-]
Who cares if C dies. If dies then dies do not deserve to more. It means progress.
flohofwoe 11 hours ago [-]
Should 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 11 hours ago [-]
OS 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.
Sure, 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 48 minutes ago [-]
It'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 10 hours 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.
flohofwoe 9 hours ago [-]
Rust basically would need to specify the interior memory layout of basic types like Result or Option. But to be pedantic, not even C has a standardized memory layout for structs, only some accidential common conventions that work for some types but not others
Decabytes 10 hours ago [-]
One 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 30 minutes ago [-]
That'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.
raytracer1 8 hours ago [-]
I used to an embedded software engineer, but I did not know this abi thing.
hdhcbdb 2 hours ago [-]
It'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
jdw64 14 hours ago [-]
But 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 13 hours 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 13 hours ago [-]
Exactly. 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
AnimalMuppet 9 hours ago [-]
> They say it's 'safe and certified code,' but in reality, it's just legacy code.
It's not just legacy code. Some of it is literally certified, that is, it has gone through a certification process. That is a slow and expensive thing to redo; nobody wants to do it for a change that doesn't add real user value.
xyzsparetimexyz 13 hours ago [-]
Yeah, well, that didn't happen
aw1621107 10 hours ago [-]
Not 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.
Yeah, if you ignore the type libraries and metadata that comes along with it.
solar_quick0p 11 hours ago [-]
Curious. Care to elaborate further?
sylware 11 hours ago [-]
Look at directx.
sylware 11 hours ago [-]
I skimmed the article, is this guy actually advocating for planned obsolescence or did I miss something??
flohofwoe 11 hours ago [-]
He's writing from the perspective of a C and C++ standard contributor, and IIRC esoteric ABI details like this was what he happened to be obsessed with at the time (around 2022) :)
aw1621107 11 hours ago [-]
What makes you think the author is "advocating for planned obsolescence"?
11 hours ago [-]
black_13 5 hours ago [-]
[dead]
CurbStomper 10 hours ago [-]
[dead]
clbrmbr 11 hours ago [-]
I 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:
Dylan16807 2 hours ago [-]
Does 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?
fragmede 11 hours ago [-]
that sounds like a fascinating use case. why were you writing Lua in the first place?
creshal 11 hours ago [-]
Is C even worth saving at this point? Even standards committee members can't consistently write C code that doesn't overflow and segfault.
hn_submit 2 hours ago [-]
C will not die but it will simply fade away.
Rust is the new black and it will supplant C/C++ and almost everything else short of virtual machine languages like Java/C#.
lioeters 8 hours ago [-]
> Is C even worth saving
C doesn't need saving, it will continue to survive on its own and even flourish in niches for decades to come. So will "Java"Script. Worse is Better, respectfully, or at least Old and Simple is Tough as F, living long and prosper. See, C doesn't need you but we need C, apparently for the foreseeable future.
groundzeros2015 9 hours ago [-]
Yes, it’s the most useful programming language you can learn.
11 hours ago [-]
dummydummy1234 11 hours ago [-]
What other cross-language abi do you propose?
pjmlp 10 hours ago [-]
What 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 10 hours ago [-]
Those 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)
wasmperson 8 hours ago [-]
> Those are all RPC mechanisms, not an ABI
I'm not sure about the others, but COM is an ABI. There's a bunch of stuff surrounding it that is RPC-like but the core specification is just binary layouts and calling conventions. It's arguably more cross-language than the C ABI since it lets you generate type-safe bindings for any language, unlike in the latter where you need to parse header files.
> 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
Maybe it would, but I doubt anybody would notice. The whole "everything is a file" concept on unix is basically just this (also X11/Wayland).
pjmlp 9 hours ago [-]
Nope, when you use in-proc calls, which some of them do support, they become an ABI as well.
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.
You're taking the idea of respect too far.
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.
But it is also wrong to reduce a language to what is in the spec.
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.
The point is in C you have greater control of execution and resources, not that it matches the hardware exactly. It’s a spectrum and C is closer on that spectrum than JavaScript.
So I keep re-educating folks that isn't the case.
Every thread has different people reading it, so there is always a first time for many of them.
And if you move to e.g. C# / Java or similar, if you squint, and you try to be a smart-arse, then you could deny that C is closer to the hardware than C#, because C# probably has everything you need to control it, to the same degree that C allows you to. But if you work in these languages for a while, and look at the code that you ended up producing, then again you will absolutely find that it would be ridiculous to not admit that C gives you better control.
And you could even extend this to Rust, because the language encourages you to use high-level prefabricated components. It discourages you from doing low-level things, at least a little bit I think (I'm not a Rust user).
I think what you are doing all the time, is you are being a smart-arse, nothing else. What interesting low-level performant things have you actually programmed lately?
And then coming with such lengthy ad hominem.
Let make a fun exercise for the audience, given your performance remark.
Paste a random C code that I should replicate in whatever language I feel like.
There is one rule.
If the sample code is pure ISO C, then I will only use what is in the standard of whatever language I pick up.
If the sample code makes use of single language extension not part of ISO C, then I will have the freedom to also pick whatever language extensions I feel like.
This is common with C, when interfacing with hardware.
Using C++ as your other comparison point when arguing that C isn't low level is by far the most smartass idea in this thread.
What about you do xxHash? Should be quite basic, not a lot of complicated structures. https://github.com/Cyan4973/xxHash/blob/dev/xxhash.h
Or what about you do an audio or video codec? Or an operating system?
Not going to paste any of my own code, because any non-trivial stuff is hundreds to thousands of lines. But one more example (that I recently did myself): Create a block allocator (power of two blocks) with bookkeeping in shadow memory (administered in individually committed zones representing virtual memory regions of 64 MB (2^26)). Any used memory has bookkeeping support for being sub-allocated at any and all levels up from 64 KB (2^16) to 64 MB (2^26), and even higher (by joining committed regions). Individual blocks are collected (using intrinsic linking, because no memory allocation) in a hierarchy of pools of same-sized chunks that have the same parent, and can be recursively sub-allocated on any smaller chosen power-of-2 level, and finally be consumed in linear fashion (arenas). Blocks are pooled with a moderate retain policy (watermark system) to allow subsystems to almost completely avoid any system calls and avoid inter-thread synchronisation. The memory overhead must be below 1% even though it's totally flexible (as said has metadata for all levels from 64 KB up).
The bookkeeping should function on 32-bit systems (small virtual space, occupancy range from megabytes to 3 GB) as well 64-bit systems (2^48-2^57 bytes of virtual address space, occupancy range from megabytes to hundreds of gigabytes) with reasonable overhead compared to actual usage.
This requires intrusively linked lists, occupancy bitmasks, bit-counting and bit-prefix counting, OS syscall access (virtual memory), pointer arithmetic (alignment needed to address shadow bookkeeping memory) and thread synchronisation. The reference code is >> 95% pure ISO C++11 (could be C99 with few changes), with a little platform code glued in. It works on Windows but it could be ported to Linux in a few hours. It supports a mostly-immediate-mode GUI with hundreds of thousands (maybe millions?) of small variable-sized allocations per second. Allocation has almost completely disappeared from the CPU profile, well below 1% of CPU usage.
What are you even arguing right now? (Btw -ansi compiler flag)
> Smart-arse is comparing C versus JavaScript
I chose JavaScript to make the idea of a spectrum clearer using extremes. I can do C++ if you like. The machine doesn’t care about destructors, move, concepts, initializer lists, virtual methods, launder, or inheritance. You are programming against an abstract model further divorced from how x86 CPUs work.
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.
> 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 :)
> and I hope "no harm meant" was implicit :)
Ad hominen then an apology, mixed signals here or I'm missing something. Maybe sarcasm?
I think many of us throughout the years been reading pjmlp's comments which fits a certain "theme". I don't mind though, it's just text after all, but was hard to keep myself from entering the meta-conversation when the opportunity just sat there. I still don't mean no harm by it, we all have our less agreeable ways of writing our comments, I'm surely guilty of it in some way too.
You can express annoyance at someone's pattern of behavior without it being personal. embedding-shape isn't the only person annoyed by pjmlp's repeated disdain and snark towards people who use C (or Zig or WebAssembly or Rust or...).
No, you're usually the initiator. Usually it's with some off-hand quip about how C programmers don't understand C, or how the people designing WebAssembly are ignorant of COM or the JVM, or how Zig is just Modula-2, etc.
Most threads you participate in aren't filled with snark until you enter them.
Two measures two weights, in C versus other systems languages.
I'm not pjmlp but I can explain this for the case of Rust, where this works a bit like C but with a few interesting differences.
Mainly, in Rust there is not a concept of a "memory object" per se in the runtime semantics. Memory is made of allocations and allocations are made of bytes. Unlike C, bytes are guaranteed to be 8 bits in size. Every byte of memory can hold integer values (0x00 to 0xff), pieces of a pointer or be uninitialized. That means there is nothing like strict aliasing, and therefore no need to have special rules for byte-level access. You can alias any type as any other type, so long as you avoid all the other sources of UB (out-of-bounds access, uninitialized memory access etc.).
The way to practically access this is much the same as in C. You can do things like cast pointers between different types and project a pointer to a struct to a pointer to one of its fields. It should be noted that, unlike with major C implementations, structs do not have a stable, well-defined layout, so if you do manual pointer math you need to put #[repr(C)] on the struct to get C layout rules (which might still yield platform-dependent field offsets, e.g. size_t is not the same size everywhere).
Note also that these are the dynamic rules of Rust, you need to follow these when writing unsafe code to avoid UB. The static rules of safe Rust are much more restrictive and don't allow much at all. It is possible to write unsafe code that exposes safe abstractions for this, one example is the "bytemuck" crate. It provides macros that can parse a type definition to check certain properties (e.g. well-defined layout, no padding) and then provide you with safe functions for byte-level access. Since there is no strict aliasing, for certain types you can also get safe functions for access at other granularities. For example:
can be safely accessed as an array of u32 values (uint32_t in C), but can not, for alignment reasons.BTW: If you use character-pointers, you also do not need to worry about strict-aliasing in C.
Or for something more modern either D or C++ will do.
Examples omitted on request.
Being able to find someone who's made the argument you're rebutting doesn't make it not a straw man. What matters is whether the person you're arguing with is making the argument.
Specifically this:
> When other languages have compiler specific extensions beyond the spec, it is a failure in their design.
Is not a point I've seen anyone here make.
This one of the failures of Linus T. with the linux kernel: he was not able to keep the assembly source code with plain and simple C code you can compile with a small and alternative C compiler (same failure for the glibc devs I think).
I don't blame him, he is already keeping the linux ABI stable, and pulling that off is something.
Which part of that big clause is the part that failed? Because I thought you could still compile Linux with TCC.
Each additional compiler supported by a project means variance in functionality and thus additional work for the project. That work could make the codebase more robust. Or it could be a ton of useless work. Or anything in between. Depends on the context of the project.
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.
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.
Saying inline ASM is no different than a function call is like saying standard control structures are no different from function calls. I suppose from a Smalltalk perspective that could be true, but is that the mental model most programmers use?
I work on a system from the 90s with custom instructions. GNU-as was patched to understand the instructions. They’re used through macros that ultimately expand to inline ASM. Without this, you’d need function inlining, which may or may not be possible with a linked object (it certainly wasn’t standard in the 90s). So now a single instruction turns into stack management, a jump, more stack management and a return. At that point any benefit to a specialized instruction may be erased, or in the case I’m dealing with talking to external hardware becomes unreasonably expensive.
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.
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.
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.
C only exists instead of the alternatives, because according to Dennis Ritchie himself it was more fun to create C than using something else, and I quote:
"Although we entertained occasional thoughts about implementing one of the major languages of the time like Fortran, PL/I, or Algol 68, such a project seemed hopelessly large for our resources: much simpler and smaller tools were called for. All these languages influenced our work, but it was more fun to do things on our own."
From https://www.nokia.com/bell-labs/about/dennis-m-ritchie/chist...
"All these languages influenced our work, but it was more fun to do things on our own"
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).
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...).
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".
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.
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.
- 2023-06-10, 64 points, 16 comments: (https://news.ycombinator.com/item?id=36249253)
- 2022-03-13, 175 points, 129 comments: (https://news.ycombinator.com/item?id=30660528)
> 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
[1]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3913.htm
Suppose I have a libfoo that has a public function that takes an intmax_t parameter. Or that has a public struct with an intmax_t field. It will be compiled for a particular definition of intmax_t. If you try to link it with a program that uses a different definition, it will fail.
The article's solution with the "MY_LIBC_NEW_CODE" define cannot work because no existing C code knows about "MY_LIBC_NEW_CODE".
The proposed mechanism is somewhat useful to a library that wants to provide multiple incompatible implementations of a function. (But this is mostly only interesting for libc implementations that need to handle historic incompatibilities between all the various Unix specs. Other libraries can just give their new, incompatible function a new name.) It's useless if you want to make an incompatible change to a type definition.
I don't get quite the same impression. The sense I get is more that such a change would basically need to happen "bottom-up":
> Some of [the scenarios that aren't fixed by this proposal] are just the normal dependency management issues. If you build a library on top of something else that uses one of the changed types (such as intmax_t or something else), then you can’t really upgrade until your dependents do.
> <snip>
> For those of us in large ecosystems who have to write plugins or play nice with other applications and system libraries, we’re generally the last to get the benefits.
In which case the benefit of the proposal (as far as I understand) is that such bottom-up changes can occur without forcibly breaking other consumers.
And to be honest, this sort of compiler-specific ABI interoperability is a non-problem that doesn't need solving, it's at most relevant for software developers of closed source libraries who distribute the libraries as precompiled blobs. But those must be stamped out for different target-triples anyway.
Ultimately any ABI discussions need to happen between CPU and OS vendors, compiler toolchains implement whatever comes out of those discussions.
idk, given how ABI impacts the evolution of C I think it's not unreasonable to provide a mechanism by which ABI can be evolved even if it's not specifically for compiler interop.
> Ultimately any ABI discussions need to happen between CPU and OS vendors, compiler toolchains implement whatever comes out of those discussions.
I think part of the article author's reasoning for proposing this feature is that toolchains have implemented something like this feature to try to address ABI issues and that the rest of the ecosystem could benefit from a similar technique.
For areas like application plugins via DLLs it's the OS ABI that matters.
> 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#d...
Well, I'd presume that's what the paper's "Introduction & Motivation" section is for. Do you take issue with the authors' statements there?
Conceptually intmax_t is a generic type of the form intmax_t<T>. Since C does not have generics, the T is chosen by the compiler during compile time.
But this means that the first time you compile any shared library with an intmax_t parameter or return value in one of its functions, you have permanently baked in the type parameter T to whatever the compiler chose it to be at that moment in time.
You cannot retroactively change intmax_t even if you change the symbols, because intmax_t runs into the same problem any generics system does, you cannot retroactively add instantiations for future types that were not explicitly compiled into the dynamic library.
Even if C gets generics and intmax_t would become obsolete either way, because you don't need intmax_t<T>, you can just have T.
intmax_t is only interesting for choosing the T and even then it is only interesting inside function implementations and never in their signatures.
So my conclusion is that intmax_t was a failed attempt at trying to be "clever" with the idea of introducing generics without introducing generics. This is an idea that is so doomed that anyone trying to rescue it, didn't really understand the problem with intmax_t.
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.
See https://faultlore.com/blah/c-isnt-a-language/ for more detailed analysis.
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.
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.
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.
In my opinion, that's the correct thing to do anyhow, even for bigger systems
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
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.
It's not just legacy code. Some of it is literally certified, that is, it has gone through a certification process. That is a slow and expensive thing to redo; nobody wants to do it for a change that doesn't add real user value.
[0]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3913.htm
Does it follow the lua spec?
How are tables implemented?
How fast is it compared to normal Lua, luajit interpreter, and luajit jit?
Rust is the new black and it will supplant C/C++ and almost everything else short of virtual machine languages like Java/C#.
C doesn't need saving, it will continue to survive on its own and even flourish in niches for decades to come. So will "Java"Script. Worse is Better, respectfully, or at least Old and Simple is Tough as F, living long and prosper. See, C doesn't need you but we need C, apparently for the foreseeable future.
Especially more relevant when going with microservices, microkernels, serverless, static linking (and still have plugins),...
I'm not sure about the others, but COM is an ABI. There's a bunch of stuff surrounding it that is RPC-like but the core specification is just binary layouts and calling conventions. It's arguably more cross-language than the C ABI since it lets you generate type-safe bindings for any language, unlike in the latter where you need to parse header files.
> 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
Maybe it would, but I doubt anybody would notice. The whole "everything is a file" concept on unix is basically just this (also X11/Wayland).