When you ask what portable assembler is, there's actually three different things you could mean:
1. C is portable assembler because every statement maps pretty directly to assembly. Obviously, compiler optimizations tend to make this completely not true (and most of the rants directed towards compiler developers are precisely because they're eschewing naive mappings).
2. You can represent every (reasonable) assembly listing using C code. Well, except that C has no notion of SIMD value (yes, there's extensions to add vector support). Until C11, it couldn't describe memory ordering. Even with compiler extensions, C still doesn't describe traps very well--any trap, in fact, is undefined behavior.
3. Every assembly instruction can have its semantics described using C code more or less natively. Again, here C has an abstract machine that doesn't correspond very well to hardware. There's no notion of things such as flag registers, and control registers are minimal (largely limited to floating-point control state). Vectors and traps are completely ignored in the model. Even more noticeably, C assigns types to values, whereas processor definitions assigns types to operations instead of values.
Note here that I didn't need to invoke undefined behavior to show how C fares poorly as portable assembler. The problem isn't that C has undefined behavior; it's that a lot of machine semantics just don't correspond well to the abstract semantics of C (or indeed most languages).
When you ask what portable assembler is, there's actually three different things you could mean:
1. C is portable assembler because every statement maps pretty directly to assembly. Obviously, compiler optimizations tend to make this completely not true (and most of the rants directed towards compiler developers are precisely because they're eschewing naive mappings).
2. You can represent every (reasonable) assembly listing using C code. Well, except that C has no notion of SIMD value (yes, there's extensions to add vector support). Until C11, it couldn't describe memory ordering. Even with compiler extensions, C still doesn't describe traps very well--any trap, in fact, is undefined behavior.
3. Every assembly instruction can have its semantics described using C code more or less natively. Again, here C has an abstract machine that doesn't correspond very well to hardware. There's no notion of things such as flag registers, and control registers are minimal (largely limited to floating-point control state). Vectors and traps are completely ignored in the model. Even more noticeably, C assigns types to values, whereas processor definitions assigns types to operations instead of values.
Note here that I didn't need to invoke undefined behavior to show how C fares poorly as portable assembler. The problem isn't that C has undefined behavior; it's that a lot of machine semantics just don't correspond well to the abstract semantics of C (or indeed most languages).