I know many arcade games from the 80s were programmed in 68000 assembly. This carried on probably well into the 90s, even though Motorola C compilers existed in the 80s. Why then weren't C compilers used more frequently? Was it an issue with the limitations of ROM storage space and memory, and due to this the coder's need to optimize literally every line of code for speed/size?
11 Answers
I think the question I would ask is why would you program arcade gamers in C back in the 80's.
Firstly, C was not nearly as popular in the world of microprocessor programming as you might imagine back then. It was only one of a number of high level languages that were available (if it was available for your platform).
Secondly, arcade games need performance. For performance you needed assembly. Even C compilers didn't produce code as fast as an assembler in those days.
Thirdly, people tends to use what they know. When arcade machines were eight bit, everybody programmed them in assembler. When faced with a 16 bit machine, the natural thing would be to carry on as before - different assembler, but still assembler.
-
1Comments are not for extended discussion; this conversation has been moved to chat.– Chenmunka ♦Commented Oct 24, 2021 at 9:45
-
There existed playable, albeit simple action games written in interpreted BASICs, running on 1 or 2 MHz microcomputers. Using a compiled language was not out of the question.– KazCommented Oct 24, 2021 at 22:06
-
@Kaz Not out of the question, but rarely done. And, even if it was done, in the 80's, C would not have been the obvious choice it seems today.– JeremyPCommented Oct 25, 2021 at 7:55
Many of the people who programmed arcade games got their start in an era where C compilers existed, but would have been expensive and not very convenient to use. Later in the 1980s, compilers reached the point where they would be practical, but experienced assembly language programmers could be more productive writing assembly code than they would be as neophyte C programmers.
Although today's compilers are much more sophisticated than those of the 1980s and 1990s, much of that sophistication wouldn't have been necessary or even useful on the kinds of processors people were using in that era. Today's processors can execute multiple instructions at once on each core, but certain combinations of instructions cannot execute simultaneously. In order to achieve good performance, it's often necessary that machine code perform operations in a different sequence from specified in the source code. The need to determine when reordering instructions will improve performance without breaking program semantics creates a need for a lot of compiler complexity which offers far less benefit and may even be counter-productive when targeting simpler processors.
-
6Today’s ISAs are also far more complex than those of the 80s; it’s easy to remember all the 6502 or Z80’s instructions, doable for m68k or pre-MMX x86, but I doubt many people would know off-hand all of SSE and AVX etc. Commented Oct 21, 2021 at 18:23
-
1@StephenKitt: Someone who's trying to hand-optimize code for SSE could consult a machine-searchable architecture reference, so one wouldn't have to perfectly memorize all the instructions in order to be able to use them. The complex interactions among instruction timings are apt to be much more difficult for programmers to reason about.– supercatCommented Oct 21, 2021 at 19:25
-
1Why do you so often seem to argue with whatever I write? I’m not saying memorising ISAs is more complex than analysing timing interactions. I’m agreeing with your answer, which I upvoted before even commenting, and saying that on top of what you wrote, ISA complexity also contributes to the complexity of writing assembly language manually. Commented Oct 21, 2021 at 20:26
-
3the Marble Madness example is a perfect illustration of this: the coders were familiar with C so they used it. But the framerate was 30Hz instead of 60 because of the poor code generation. Too bad someone didn't optimize the generated code afterwards to gain speed. Commented Oct 22, 2021 at 12:07
-
1@Jean-FrançoisFabre: Changing a game from 30fps to 60fps doesn't just require making the code faster, but also tweaking the physics so it still "feels" right. I'd expect (cite any sources you have that would say otherwise) that the frame rate was chosen before the physics were dialed in, and would have been kept at 30fps even if the engine could have run at 60.– supercatCommented Oct 22, 2021 at 15:29
[I see that @Mark supplied a substantially identical answer while I was typing this one]
One aspect that other answers do not seem to have touched upon is the issue of tightly controlled predictable and high-resolution timing for I/O devices. The simple in-order CPUs of the time allowed one to precisely determine the execution time for a particular piece of code by adding the cycle counts of the instructions being executed. This required a level of control over the machine code sequence executed that is simply no achievable with code compiled from an high-level language.
For example, various graphics effects required that particular code for manipulating the framebuffer could only run during the horizontal or vertical blanking intervals which have durations in the microsecond range. While I have no first-hand knowledge, I am under the impression that some sound effects, such as the playing of sound samples, likewise required very precise timing that was achieved via cycle counting. Some simple systems used cycle counting for implicit synchronization of video and sound until well into the 1990s.
Part of the reason is timing.
Early- and mid-80s arcade machines didn't have anywhere near the hardware capabilities of modern systems. You couldn't just load an MP3 of background music into RAM and tell the sound card to play it, or compose an image in an offscreen framebuffer and swap it in during the vertical-blanking period. Data had to be delivered to the output devices on a reasonably "just-in-time" basis, which was far easier to do in assembly.
Timing shows up again in getting the most out of the hardware's limited abilities. For example, you could change display parameters in between scanlines, or even mid-scanline, to do things like extra colors or perspective transforms that the hardware is theoretically incapable of doing. But this typically requires cycle-accurate timing, which you can't get out of anything but assembly language.
-
I don't think mid-frame parameter tweaks were common in arcade machines, since switching display modes at hard-wired points on the display was cheap and easy to do. The interesting mid-frame update approach I do know of, however, was the one used by Eugene Jarvis: use a mid-frame interrupt as a signal to draw the top half of the next screen, and a vertical-blank interrupt as a signal to draw the bottom.– supercatCommented Oct 22, 2021 at 15:34
From my perspectives: in the 80's, a lot of hobbyists only had access to Basic and Assembly or machine code. They might have access to Microsoft Pascal or Turbo Pascal, but that was about it. In fact some people feel happy not needing to program in machine code but can use Assembly. There was no "C compiler" available to the general public. Machine code was generally free of charge and available to everybody (such as on the Apple II).
C didn't show up at least to me, until about 1989 in college, and I remember buying the K&R C book, and then about 1 or 1.5 years later, the ANSI C edition of the book came out, and I was asking the question "I just bought the book and now I have to buy the book again?"
Also, during those days, the 6502 was 1MHz, and 68000 at 8MHz, and compared to 4GHz nowadays, the 6502 processor had a speed 1 / 4,000 of a processor nowadays. The 68000 has a 1 / 500 of a processor today. And the storage? It may be 8kb ROM or 16kb ROM in the arcade machine to store the game (it was 2kb ROM x 6 on the Apple II), so we didn't have that much memory to work with. So if we use a C compiler and it was 2.5 times slower than the Assembly code or machine code, and takes up 2 or 3 times the memory space for the compiled code, we may run into problems. During those days, we directly program a processor, instead of writing something to be compiled to run it.
I also remember there was a somewhat steep learning curve to learn C, when we had to learn what is a pointer to a function that returns a pointer to a pointer to a character.
-
3oh thank you... I corrected that in the answer... I forgot the "M"... and then, memory is the one that got increased a million times: 16kb -> 16GB. I remember I got a PC with 8MB of RAM in 1992, and my coworkers asked me, "are you sure you need that much RAM?" Commented Oct 22, 2021 at 12:05
Probably as opinion-based as other answers but the reasons I see:
Back in the 70s, the aim of C was portability.
There was no need of portability in arcade games (well, they didn't care for future home conversions), so no instant benefit of C for that aspect.
On, the other hand the risk of switching from assembly to C was probably considered too high for very few benefits. The drawbacks were more apparent:
- more complex and slower toolchain
- possible compiler bugs
- suboptimal code generation leading to performance issues
- need for a symbolic debugger (whereas one could debug their asm code from raw disassembly on a simple low-level debugger if available)
Plus, once you have your base asm routines for a given processor, you're reusing them over and over and build over it for new games. Switching to another language forces you either to mix asm and C (others problems arise) or rewrite everything.
-
2The kind of "portability" C aimed for was allowing people who knew C and knew things about a target platform like storage formats and memory layouts to be able to use write code for that platform without having to worry about the details of the machine's instruction set. The goal was not particularly to facilitate the writing of portable programs. If the latter were intended, the language would have defined the behavior of many more constructs rather than leaving implementations to process them in whatever fashion would be most useful to their customers.– supercatCommented Oct 22, 2021 at 21:16
-
Agree with @supercat, C - at least the versions available at the time - was not very portable. It was only portable when compared to assembler. In fact, code written for a particular architecture wouldn't necessarily behave the same way for different compilers. For example, a lot of people assumed that pointers and
int
s were the same size. On the Atari ST, this was true for the Mark Williams compiler, but false for the Megamax compiler.– JeremyPCommented Oct 26, 2021 at 7:36 -
maybe not 100% portable but could be portable with a bit of effort, or ported very quickly (at least the functional code) Commented Oct 26, 2021 at 8:06
-
@Jean-FrançoisFabre: Compilers for the Macintosh were configurable as to whether
int
should be 16 or 32 bits. IMHO quality compilers today should be similarly configurable forlong
to allow compatibility with either code that expectlong
to match the size of a pointer, or with code that expectslong
to be the smallest practical value that's at least 32 bits. One wouldn't need to add much to the language to allow everything necessary for most embedded C projects to be expressed entirely in a collection of C source files, with semantics that every build will either...– supercatCommented Oct 26, 2021 at 16:04 -
...build a working program or fail outright, which is IMHO what a real language standard should aim for. The set of programs a compiler can handle is a quality-of-implementation issue, but rejecting any programs it can't handle should be a conformance requirement.– supercatCommented Oct 26, 2021 at 16:05
They were, later on in the decade. Marble Madness for example.
-
7This post could be improved by answering the question Commented Oct 21, 2021 at 15:37
-
2
-
6Yeah, it (kinda) answers the question in the title, but not the (admittedly, different) question(s) in the body of the OP. For example, "Why then weren't C compilers used more frequently?", then the answer can't be simply "they were". And you do not appear to address the doubts about limitations, optimizations, etc etc etc. Commented Oct 21, 2021 at 15:48
-
@AlanB That's a good bit of history for anyone coming across this thread. I checked up, and sure enough the arcade machine uses a Motorola 68010. Not even later in the decade as you mentioned, but probably as early as 1983-84 (the game was released in 1984). Commented Oct 23, 2021 at 10:31
Using C just wasn't an option.
Whilst I wasn't a games developer in the 80s, some of my friends were, and I watched them do it.
At the time, the only tools available in the UK, to mere bedroom programmers, was hand crafted hex values (pen and paper to write assembly, then convert each line to hex, then POKE it in to memory), and later on, a disk based assembler (which took a text file, and compiled it into binary, and stored it back on the disk, not enough memory to store the assembler AND the code in RAM at the same time).
I vaguely remember reading about Andrew Braybrook using an AS/400 to cross compile assembly code and download via a serial port to a C64, but that cost lots of money that most developers didn't have.
One of the things I remember helping with, on a C64 game, was to add NOPs to some code that did video, to 'push' the border colour change so that it appeared 'off-screen'. You just can't do that in C.
-
1I'm guessing most arcade machines weren't programmed by hobbyists, though. Commented Oct 22, 2021 at 14:22
-
@TobySpeight No, but many arcade style games for home computers were programmed by hobbyists in their bedrooms. "The term "arcade game" can refer to an action video game designed to play similarly to an arcade game with frantic, addictive gameplay." en.wikipedia.org/wiki/Arcade_video_game#Action Slightly OT, I did the programming for some commercial pub 'games of chance' targeting the Z80, using my Amstrad CPC664 and an assembler that I ported to it from a ZX Spectrum tape supplied with the "Home Computer Advanced Course" magazine. Commented Oct 24, 2021 at 2:07
-
Ah, I hadn't considered that the question might have been asking about home-computer games. Perhaps that's because I never had a MC68000-based home computer, just a selection of Sinclair Z80-based ones. Commented Oct 24, 2021 at 8:07
Performance
You could code in a high level language (heck, I wrote two educational games in BASIC for Pete's sake) but you would get murdered on performance if you did.
That meant other game companies would write better games than you, and you'd lose market position.
All things being equal, C code will take more ROM space to store, use more RAM for the same data storage, and use more CPU cycles to execute- often audacious or highly varied cycles.
Generally most game programming was pushing the limits of the hardware. For instance, let's suppose I need to animate 60 sprites. However, the hardware only supports 8 sprites. What will I do? Reuse sprites. Scan line 47 is the bottom of the top-most sprite. (I order my sprites by their height on the screen). So on scan line 48, hardware sprite 1 won't be used again until the top of the next frame. So I change hardware sprite 1 to the values for (software) sprite 9. At scan line 51, I rewrite the registers for hardware sprite 2 to make it my sprite 10. And so on.
This kind of "beam riding", common to many games, just doesn't work with high level languages. There just aren't enough CPU cycles (as of the early 80s). You need the speed and predictable timing that comes from running in assembler.
Because, for instance, if you are "late" executing the housekeeping code and aren't there to move the sprite on scan line 48, then that sprite just disappears from the playfield that field, which means in practice, some of your sprites shimmer (absent every other field). See, FPS isn't an optimization like on modern games: FPS is 60 or your game dies.
That said, there is room for high level languages in the game administration and bookkeeping code. For instance if your graphic game is trying to use D&D/d20 rules with complex modeling of stats and combat, that's nice to do in a higher language as long as it doesn't take spuriously long to execute, e.g. stopping for a garbage collection.
As said, ROM and RAM are both precious, and you just can't afford the bloat that comes with compiled code. Keep in mind that code optimizers currently in use today simply did not exist then.
What I mean is, suppose you write
A = (computationally very expensive thing)
B = (computationally expensive thing)
C = (computationally cheap thing)
C = 1
if A or B or C then
print "Hello, world!\n"
(And A B C are never used again) ... a modern compiler will do a lot of optimization there. For instance if it understands the computational loads, it will reorder that to "if C or B or A" - which means it will compute in that order. Since line 3's computation of C is never used before it is replaced, it will delete line 3 altogether. Since 1 is always true, it won't ever compute B or A, so the top 2 lines disappear also. What remains reduces to "if 1", so they are eliminated too. My point is, those kinds of optimizations, which can make up for the bloat of compiled code, did not exist then. And even then, would not realize you were beam-riding, and would optimize for overall runtime, not being ready to go when each scan line starts.
The question seems fairly well answered already, but a couple of bonus considerations are: 1. There was little thought given to portability, since the chosen hardware platform was the only thing you’d ever want to run the program on, and 2. If you had a C compiler you’d need something you could run it on, and that probably wouldn’t be an arcade game; compile->burn EPROM->power up and test gives a slower cycle time than running a ROM-based assembler or hacking machine code by hand.
The biggest issue was that memory was really expensive, and its usage had to be tightly controlled. Game authors would use lots of tricks to save memory.
I don't know anyone who programmed arcade games, but I know some people who programmed dedicated chess computers with 8 bit CPUs (mostly the 6502, the Z80 in a couple of cases). They rarely had more than 4k of RAM to work with. With that, they needed to remember all the game moves and traverse the game tree of legal moves to select a good move. They also had to do a lot of other things that required memory. Every bit of memory was precious and a byte had to be used in multiple ways. They were all experts in bit shifting.
-
-
@GrahamLaight you should make that comment part of the answer.– JeremyPCommented Oct 26, 2021 at 7:39