I'm so glad that you have linked this article from Chisnall. I can now ask you in what ways is the abstract model of a machine on which assembly language operates on, different from the one on which the C language operates on?
This is the claim which started this thread:
>>C is a portable alternative to assembly language.
C's abstract machine is leaky and full of surprises when UB comes into play for one.
Second when people talk about C being portable Assembly, they mistakenly assume to know what comes out of the compiler's backend.
Third, unless we are speaking about PDP-11 like CPUs, modern CPUs have tons of capabilities not exposed to ISO C.
Finally, since 1958 there are portable alternatives to Assembly in systems programming with JOVIAL being one of the first remarkable ones, yet another thing that C did not invent.
Maybe because is there is no concernable difference between abstract machine models of both languages? You can't do anything more about instruction reordering or cache invalidation at the CPU level with assembly than you can with C.
Have you? I used to write device drivers mostly on ARM architectures and I have newer seen an instruction which would let me precisely control CPU instruction ordering or what it does with cache that I couldn't do in C.
This tool helps as much with C (or even higher level languages) as it does with Assembly. It doesn't change the fact both languages work on the same abstract machine model.
Access to CPU-specific flags like the carry flag. When doing a multiple-precision addition, if the CPU carry bit is inaccessible, then it must instead be compared to the result to see if it overflowed, which takes 3-5 more instructions per limb or worse on modern superscalar processors that are serial in terms of data accesses.
The carry flag is not a specific architectural detail; it is part of the output from the prime example of a basic electronic circuit that is the digital adder.
It is an architectural detail. MIPS for example doesn't have a carry flag. You have to do the exact same procedure in assembly on MIPS you have described in your previous comment when doing multiple word addition.
MIPS along with DEC alpha do not have adder circuits as a design goal was not to have flags. This along with the scenario of multi-precision arithmetic resulted in the implementation of multi-precision arithmetic on MIPS and DEC alpha using a form of branch like instruction with an ALU stage instead of the unavailable instructions dependent on the alternative adder circuits. The choice of having or not having a carry flag alone is not an architectural detail as the choices of [micro]architecture involve how operations are implemented in circuits/hardware. The carry flag specifically has to be considered in the context of its [the adder] circuit when making architecture decisions. The lack of control over flags leading to the inability to compile the same C program to the same assembly operations with same behavior on different hardware demonstrates that a strictly C program is unable to express a consistent portable assembly program across all hardware.
You are basically arguing CF == !CF. On MIPS you literally have to compare your result with an operand in order to establish if there was an overflow. On Arm you just look at the status register.
When I was doing embedded development, I used C as portable assembly in practice and I know I wasn't the only one. Can you do everything an architecture enables you in a portable way? No, of course not.
Not by definition, but by practice as generating assembly is usually done to optimize performance. Languages restricted to generate a common feature set between isomorphic ISAs and bijectively associated assembly languages are at a performance disadvantage compared to the full ISA specific assembly.