4 ms·
Weird article. Hasn't most code that's been compiled in the last few decades using the x86 frame pointer register (ebp) as a regular register? And C also worke
by flohofwoe 1y ago
Weird article.
Hasn't most code that's been compiled in the last few decades using the x86 frame pointer register (ebp) as a regular register? And C also worked just fine on CPUs that didn't have a dedicated frame pointer.
AFAIK the concepts of the stack and 'subroutine call instructions' existed before C because those concepts are also useful when writing assembly code (subroutines which return to the caller location are about the only way to isolate and share reusable pieces of code, and a stack is useful for deep call hierarchies - for instance some very old CPUs with a dedicated on-chip call-stack or 'return register' only allowed a limited call-depth or even only a single call-depth).
Also it's not like radical approaches in CPU designs are not tried all the time, they just usually fail because hardware and software are not developed in a vacuum, they heavily depend on each other. How much C played a role in that symbiosis can be argued about, but the internal design of modern CPUs doesn't have much in common with their ISA (which is more or less just a thin compatibility wrapper around the actual CPU and really doesn't take up much CPU space).
- tdeck 1y agoIt's because the historical perspective in this article is really lacking, despite that being the premise of the article. Not only does the author seem to believe that C was the first popular high-level language, but the claim that hardware provided stack based CALL and RETURN instructions was not universally true. Many systems had no call/return stack, or supported only a single level of subroutine calls (essentially useless for a high level language but maybe useful for some hand written machine code). FORTRAN compilers often worked by dedicating certain statically allocated RAM locations near a function's machine code for the arguments, return value, and return address for that function. So a function call involved writing to those locations and then performing a branch instruction (not a CALL). This worked absolutely fine if you didn't need recursion. The real benefit of a stack is to support recursion.
- cyberax 1y agoC can work just fine in systems without a stack. A very modern example: eBPF.
- deleted 1y ago[deleted]
- MichaelRo 1y agoI recall starting to program in BASIC on CP/M and ZX-Spectrum machines and they didn't have procedures, only GOTO. Just like assembler, you can use all the JMP you want and not use structured programming and procedures but ... it will all become an unmaintainable mess in short time. Very likely in a number of alternate futures (if not all of them), given the original set of CPU instructions, people would gravitate naturally to C and not some GOTO spaghetti or message passing or object oriented whatever.
- heisenbit 1y agoI remember jumping out of gosub on the Apple ][ and eventually running into an out of memory error as the stack on the 255 byte page $01 overflowed. As math was also done on the stack math functions broke. Simplifying expressions only delayed the inevitable doom. I had to abandon the project and only later understood my first encounter with a memory leak.
- weinzierl 1y agoThe BASICs of the time often had GOSUB which remembered the return address. Also, while the stack not being prominent in the high level language, they very well used one on the assembly level. For example on the C64 (probably other 6502/6510 based systems) it always started at $100 ($ being the old convention of writing hex), right after the zeropage.
- varjag 1y agoGOSUB was definitely there.
- MichaelRo 1y agoYeah, I think it was but I was just starting in programming and GOSUB didn't made much sense to me as it's not a proper structured programming procedure with input and output params but more like a hack with data passed through global variables. And by the time I learned structured programming I already moved to Pascal (HiSoft Pascal : https://www.cpcwiki.eu/index.php/Hisoft_Pascal https://www.cpcwiki.eu/index.php/Hisoft_Pascal ) and there was no turning back to BASIC.
- rep_lodsb 1y agoThe prevailing opinion seems to have shifted towards using the frame pointer for its special purpose, in order to improve debuggability/exception handling? But the article is really quite misinformed. As you say, many mainframe/mini, and some early microprocessor architectures don't have the concept of a stack pointer register at all, neither do "pure" RISCs. I'd argue there is a real "C Stockholm Syndrome" though, particularly with the idea of needing to use a single "calling convention". x86 had - and still has - a return instruction that can also remove arguments from the stack. But C couldn't use it, because historically (before ANSI C), every single function could take a variable number of arguments, and even nowadays function prototypes are optional and come from header files that are simply textually #included into the code. So every function call using this convention had to be followed by "add sp,n" to remove the pushed arguments, instead of performing the same operation as part of the return instruction itself. That's 3 extra bytes for every call that simply wouldn't have to be there if the CPU architecture's features were used properly. And because operating system and critical libraries "must" be written in C, that's just a fundamental physical law or something you see, and we have to interface with them everywhere in our programs, and it's too complicated to keep track of which of the functions we have to call are using this brain-damaged convention, the entire (non-DOS/Windows) ecosystem decided to standardize on it! Probably as a result, Intel and AMD even neglected optimizing this instruction. So now it's a legacy feature that you shouldn't use anymore if you want your code to run fast. Even though a normal RET isn't "RISC-like" either, and you can bet it's handled as efficiently as possible. Obviously x86-64 has more registers now, so most of the time we can get away without pushing anything on the stack. This time it's actually Windows (and UEFI) which has the more braindead calling convention, with every function being required to reserve space on its stack for it's callee's to spill the arguments. Because they might be using varargs and need to access them in memory instead of registers. And the stack pointer alignment nonsense, that is also there in the SysV ABI. See, C compilers like to emit hundreds of vector instructions instead of a single "rep movsb", since it's a bit faster. Because "everything" is written in C, this removed any incentive to improve this crusty legacy instruction, and even when it finally was improved, the vector instructions were still ahead by a few percent. To use the fastest vector instructions, everything needs to be aligned to 16 bytes instead of 8. That can be done with a single "and rsp,-16" that you could place in the prologue of any function using these instructions. But because "everything" uses these instructions, why not make it a required part of the calling convention? So now both SysV and Windows/UEFI mandate that before every call, the stack has to be aligned, so that the call instruction misaligns it, so that the function prologue knows that pushing an odd number of registers (like the frame pointer) will align it again. All to save that single "and rsp,-16" in certain cases.
- rendall 1y agoYou raise fair points about the history and flexibility of hardware design. Stacks and subroutines definitely predate C, and C has run well on many architectures without dedicated frame pointers. Those are valid technical observations. But the article is about conceptual lock-in: how decades of mutual optimization between C-style abstractions and mainstream CPUs have made certain design assumptions feel "natural" or "inevitable." The author’s "Stockholm Syndrome" metaphor is provocative, sure, but it points to that inertia, not to a claim that hardware can’t do anything else. So I'd say your comment addresses implementation details that are historically accurate, while the article is pointing to the sociotechnical feedback loop: how co-evolution between C and hardware subtly shapes what we think of as efficient or possible. In my opinion, your point about radical CPU designs failing actually reinforces the article's argument more than it refutes it. The very reason such designs tend to fail is that the entire ecosystem of compilers, operating systems, and developer habits has been optimized around C-like models of computation. In other words, the co-evolution you describe is precisely the "Stockholm Syndrome" the author means. Hardware and software have adapted to each other so thoroughly that alternatives struggle to survive, not because they are inherently worse, but because they don’t fit the entrenched assumptions of the current hardware–software compact.
- rolandog 1y agoYes! Finally a comment addressing the article. I think it would be nice to explore what the hardware would end up looking like if people had optimized it to mainly run Lisp, FORTRAN, Go, etc. Would it have been better, faster, cheaper, more flexible, etc?
- magicalhippo 1y agoGet yourself a FPGA dev board and get cracking. You can get useful ones for like $50 or less. I've made a couple of simple soft-CPUs for self-designed instruction sets, aling with some compilers. Really fun to try to think of which instructions to include and how it interacts with the compiler.
- flohofwoe 1y agoJust look at the Intel iAPX 432 [1] as example for such an alternative CPU design, this was supposed to be the actual 8080 successor and 8086 was just supposed to be a temporary throwaway solution until the 432 was ready. The rest is history as they say ;) Meanwhile, the C programming model turned out a pretty good fit for very different hardware architectures. All 3D API shader languages have a C heritage for instance, despite GPUs being radically different than traditional CPUs. In the end only three things matter in hardware design: throughput, throughput and throughput ;) [1] https://en.wikipedia.org/wiki/Intel_iAPX_432 https://en.wikipedia.org/wiki/Intel_iAPX_432
- ahoka 1y agoFunny thing is, dedicated call stacks made a return with shadow stacks to mitigate ROP attacks, so now we do this double accounting to check if we are about to return where we should return to.
- weinzierl 1y ago"AFAIK the concepts of the stack and 'subroutine call instructions' existed before C because those concepts are also useful when writing assembly code [..]" The stack principle was patented in 1957 by Friedrich L. Bauer. It was well known before C but in 1957 it was considered noteworthy enough to justify a patent.