Heavier things, like graphics processing were typically written in assembler. Or sound mixing.
Some things like texture mapping you could only write in assembler, because you'd need to use x86 lower/higher half of word (like AL and AH registers) due to register pressure. Spilling to stack could have caused 50%+ slowdown.
486 era you needed assembler to work around quirks like AGI stalls.
On Pentium the reason for assembler was to use FPU efficiently in parallel with normal code (FPU per pixel divide for perspective correction). Of course you also needed to carefully hand optimize for Pentium U and V pipes. If you did it correctly, you could execute up to 2 instructions per clock. If not, you lose up to half of the performance (or even more if you messed up register dependency chains, which were a bit weird sometimes).
One also needs to remember compilers in the nineties were not very amazing at optimization. You could run circles around them by using assembler.
Mind you, I still need to write some things in assembler even on modern x86. But it's pretty little nowadays. SIMD stuff (SSE/AVX) you can mostly do in "almost assembler" with instruction intrinsics, but without needing to worry about instruction scheduling and so on.
>486 era you needed assembler to work around quirks like AGI stalls.
Plus, nobody had a 486 in the 80s (it was released in 1989). People would be lucky to have a 286, but usually just some home computer (Apple II, Spectrum, Commodore 64, Atari ST, Amiga 500, Amstrad CPC, etc).
Yeah, back then assembler was even more pervasive. It was the only way to write something publishable on 8-bit systems. Well, there were some action games (like C64 Beach Head), trivia and adventure games written in BASIC.
You spent a lot of time on 8-bitters getting code and asset size down, so that it'd even fit on the machine in the first place. Forget about luxuries like division and multiply, most advanced math those things could do was little more than adding (add, sub, and, or, xor) two 8 bit numbers together. Even shifts and rotates could only handle 1 bit left or right.
CPU clocks were measured in low single digit MHz. On top of that, 8 bitters were very inefficient — each instruction would take 2-8 clock cycles (6510) or 4-23 cycles (Z80).
On PAL C64 you have 19656 clock cycles per 50Hz screen frame minus 25 bad lines. So you could realistically expect to execute only 5-8k instructions. If you used all the frame time for just copying memory, you'd be able to transfer just about 2 kB. Just scrolling character RAM (ignoring color RAM) took half of the available raster time (yes, I know about VSP tricks, but it wasn't known in the eighties).
16-bit systems allowed some C, but most Amiga and Atari ST games were written in assembler. I'd guess same is true for 286 era, but not sure.
I'm not sure this is correct but: I remember reading or hearing rumours that Psygnosis' Barbarian (for the Amiga and Atari ST) was written in C and that it was so slow because of that.
What I mean is: we perceived it as being slow because of the rumours. Just typical nerd attitude that we still see now.
Sure, some games were written completely in C. I believe significant number were hybrids, performance sensitive parts in assembler and other code in C.
Some games were prototyped in C and optimized afterwards.
For example Amiga Turrican 2 required 33 MHz 68030 CPU during the development phase. Of course the final version ran fine on 7 MHz 68000 Amiga.
Last time I used SSE intrinsics, which was GCC 4.9 I think, I had a lot of trouble with register usage. It looked like it was compiling down to use only one SSE register for everything instead of parralelizing across them.
I tried the same algorithm in godbolt with some clang versions and it was slightly better, using two or three registers, but not by much. So I had to break it into inline assembly.
> It looked like it was compiling down to use only one SSE register for everything instead of parralelizing across them.
Yeah, that's a common problem and leads to nasty dependency stalls. MSVC is horrible in the same way, at least 2015. Haven't tried newer versions yet. Intel's ICC seems to generate good code most of the time.
Yes, it has. I've written a lot of SIMD code and spent a good amount of time reading the compiler assembly output and there has been huge improvement over the last decade.
GCC register allocation wasn't great, then it got better with x86 SSE but still sucked at ARM NEON, and now it seems to be decent with both.
Clang was better at SIMD code before GCC was. It was equally good with SSE and NEON.
In my experience, compilers are much better than humans at instruction scheduling. Especially when using portable vector extensions, you don't have to write the same code twice and then tweak the scheduling for every architecture separately.
> In my experience, compilers are much better than humans at instruction scheduling.
It'd be more accurate to say they're much better than humans when the heuristics or whatever they use works. Sometimes the compiler messes up badly.
The workflow is often to compile and then examine disassembly to see whether the compiler managed to generate something sensible or not.
Other issue is that compiler pattern matching is sometimes not working and generating correct SIMD instruction. Even when data is SIMD width aligned. For example, recently I saw ICC not generating a horizontal add in the most basic scenario imaginable. * shrug *.
Things like this make me question the wisdom of ever using higher level languages. We took the path of abstracting our description of what we want to happen away from processor instructions with the idea that we could write code that could then compile on multiple architectures without changes, but the reality is that we still often need to special case things even without performance considerations, and the farther we abstract the more performance seems to be impacted and the more often we seem to end up jumping through abstraction hoops rather than getting things done.
The minimalist in me wonders if maybe just using some kind of macro system on top of assembler plus a bytecode VM with the ability to drop to native instructions wouldn't ultimately be better.
Things are much harder nowadays due to complexity. From almost impossible to understand CPU cores to massive amounts of third party code to the modern requirements (IoT, ouch!).
Debugging predictable single thread single core system was also child's play compared to distributed networked beasts each running on lots of cores and thousands of threads.
Nineties problems were contained in a small box. Oh, and no internet like today, so needed to order books and magazines. And to use BBS and usenet. Even then, a lot of it was reinventing the wheel again and again.
Modern problems are sometimes nearly uncontained (think software like web browsers, etc.).
Just jumping in to agree here. I've always thought of this way:
In the 90's the code I wrote was more difficult. Today coding is much easier (better languages, tooling, etc..). However the systems I build are many times more complex.
In the 90's we hired the best programmers, today we hire the people who are best at managing ambiguity and complexity.
All of this is very hand-wavy as there are certainly still disciplines where pure programming skill is most important, but those seem to be fewer and fewer every day.
Anyway I envy you guys, now things tend to be too high level and you lose that holistic view of the computing machine.
Back than you felt pretty skilled I am sure, now I feel anybody could do my job, often. Unless you are working for the big 4 or similar, many jobs don't give you that excitement.
Back then assembly language was much more ergonomic than it is now, and the machines were simpler. Many of my favourite games in the 80s were made by teenagers programming in their bedrooms. Check out this 68000 assembly tutorial video from Scoopex, the Amiga demo scene group: https://youtu.be/bqT1jsPyUGw
He gets a simple graphical effect going on the Amiga in only a few lines of assembly. Doing the same thing using DirectX in c++ would take you all day!
This is an interesting line of argument - in what way, and what could be done to improve the ergonomics?
> He gets a simple graphical effect going on the Amiga in only a few lines of assembly. Doing the same thing using DirectX in c++ would take you all day!
This is absolutely true, but in something like ShaderToy you can go back to producing complex pixel-bashing effects with a huge amount of processing power.
It's just the external Tower of Babel from boot to usability has got a lot larger.
My notion of ergonomic mostly comes from programming the Amiga in 68000 and then moving to the PC and being horrified by x86!
In 68k you had 8 32-bit data registers, (d0-d7) and 8 address registers (a0-a7)
If you wanted to access bytes or 16 or 32 bits you could do so like this:
move.w #123,d0 ; move 16bit number into d0
move.b #123,d1 ; move byte into d1
move.l #SOME_ADDRESS,a0; set address reg a0 to point to a memory location.
move.b d1,(a0) ; move contents of d1 to memory location a0 is pointing to.
Nice and easy to work with and remember.
On x86, thanks to its long and convoluted history you have all kinds of doubled up registers which you have to refer to by different names depending on what you are doing, and tons of historical cruft.
On top of the CPU, the old home computers had no historical cruft and it was very easy to talk to the hardware or system firmware; usually you'd just be getting and setting data at fixed memory locations. I can read an Amiga mouse click in one line of 68k. I've no idea how you'd do it on a modern PC or even Java! Modern systems just aren't as integrated, for better and worse.
Assembly language was also part of mainstream programming back then. You'd learn Basic then go straight to assembly if you wanted to do anything serious. So there were computer magazine articles on assembly, childrens books[1]. My first assembler, Devpac, came from a magazine coverdisk with a tutorial from Bullfrog, Peter Molyneaux's old game company[2].
So there were a whole range of cultural and technical reasons for assembly language being much more of a human-useable technology back in the day.
>It's just the external Tower of Babel from boot to usability has got a lot larger.
Yes I agree, I kinda miss being able to see the ground, which is probably why I find retro programming so appealing.
> This is an interesting line of argument - in what way, and what could be done to improve the ergonomics?
Due to the enormous complexity of modern CPUs I'm not sure there's anything that could be done. With the 486 and contemporary (and earlier) uarches you could largely expect the CPU to execute exactly what you wrote so understanding the performance impact of any given bit of assembly was pretty straightforward. Then CPUs started adding features like superscalar, speculative, and out-of-order execution, branch prediction, deep pipelines, register renaming, and multi-level caching that massively complicate modeling the performance of any given code.
For example you may need to explicitly clear an architectural register before reusing it for a new calculation to avoid creating a false dependency in the uarch which would prevent the CPU from executing the calculations in parallel. Knowing when this is necessary can be hard and the rules are usually different between different uarches, even within the same uarch family.
Good assembly programmers who are aware of all this complexity can still beat compilers but they certainly can't do that for the scale of code that compilers routinely generate. Thankfully compilers are generally "good enough" these days and assembly only needs to be hand-written for very hot inner loops for performance-critical code or for cryptographic code where the exact performance characteristics of the code could potentially leak information if they're not handled correctly.
> Back then assembly language was much more ergonomic than it is now, and the machines were simpler.
In a way that's true. I also enjoyed writing assembler back then, especially on 68k.
However, to really extract the last cycle you often ended up generating a block of code at runtime (kind of very basic JIT).
Sometimes self-modifying code provided the final oomph to get something running fast enough. The reasons varied: sometimes it was because of running out of registers, sometimes dynamically changing the performed operation without branch.
Too bad for the later CPUs with instruction prefetching and caching...
Have you looked through the SDL source, though? Sure, I can get a window open and paint lines very easily, I only have to write ~50-150 LOC. However, I've silently added thousands of lines (and at least one dll) to my project. The library only hides some of the complexity (which I love about it), but the complexity still exists.
My point is, if you're allowing arbitrarily deep abstraction you can make almost any task trivial by just using something that basically already does what you're trying to do. The point the parent was making was how, with almost no abstraction at all, you could do graphics on an Amiga in a few lines, but to do the equivalent on a modern system at the lowest reasonable level of abstraction takes a lot more effort. Comparing that to a giant abstraction layer is missing the point.
> you can't use OpenGL without it.
Yes you can. You can't even pretend that coding once against SDL will absolve you of having to deal with platform issues, it just helps a lot.
> and creation of 3D images in bmp is excellent exercise you should try.
I'm a hobbiest game dev who almost exclusively uses software rendering (albeit to a framebuffer that gets pasted onto the screen with OpenGL as the path of least resistance). I've also written image libraries. None of this has anything to do with the parent comment.
I can support the trust issue with C compilers. I did my CS degree in the 80s and every C compiler I used had severe bugs. These weren’t on Unix to be fair, they were to run on PCs or to program embedded systems, but they were awful. I remember one (Aztec C?) crashing out because one of my statements had two lines of whitespace before it instead of one (or maybe it was an extra space in a blank line? I forget), which was fine elsewhere in the program but just caused a problem in that particular context. I first used GCC in the 90s and it was heaven.
In the '80s I used a cross-compiler that ran on DOS and produced code for 68K. We probably ran into a compiler bug monthly on average. We always got great turnaround on our bug reports though - I think it was a one-man shop.
8-bit CPUs instruction sets were really aimed towards assembly programming instead of being compile targets for "high-level languages" like C, and coding tricks are required (not optional) that are not possible in C, like self-modifying code or exact cycle-timing of instruction sequences (e.g. to fit into a video scanline). Some more obscure languages were not that bad (e.g. Forth), but most game programming on 8-bit machines was definitely done in carefully hand-crafted assembly.
This only changed slowly with 16-bit machines like the Amiga or Atari ST, they had more memory, the Motorola 68000 instruction set was more suited for compiled languages, and the custom chips (like copper and blitter) freed the CPU from many graphics tasks. Yet even on those machines the critical parts were usually written in assembly.
Anything with realtime graphics was probably written in assembly. There were game companies whose entire business was porting games between platforms, essentially rewriting the code for each machine.
Slower-moving adventure and RPG games might be in a higher-level language. (IIRC the original Wizardry was written in a VM-based Pascal for Apple II?)
Companies that specialized in adventure games would have their own interpreter and VM — Infocom's ZIL, Sierra's AGI, Lucasfilm's SCUMM. Game developers would write code in a scripting language against that company-standard VM.
Amateur games might be written in BASIC because every computer under the sun shipped with a BASIC interpreter back then.
C wasn't a practical option because decent compilers didn't exist for most non-Unix systems until the end of the 1980s — or if they did, they'd cost an arm and a leg. (I think the retail price for Microsoft's C compiler for DOS was several thousand dollars.)
In the mid 90's teams I led wrote code in assembly for things that were very performance intensive, in particular 3D rendering without 3D cards. There was also a need to write in assembly for the PS2, which had a very complicated and idiosyncratic architecture. In both cases you were really desperate for every bit of performance you could get and you didn't necessarily trust compilers to make optimal code. Since then compilers have improved and CPUs are much smarter about predicting what memory they will need.
Also, Microsoft Visual C became good, but it was not before version 4. I remember watching a team at Activision literally take more than an hour to compile their game. Perhaps they were doing something wrong, but similar teams had much less of a problem when 4.0 came out. You cannot imagine what a drag that is to a team's productivity and creativity.
With a good macro assembler like DevPac on the ST or Amiga, writing assembly was actually not that far off a contemporary high level language. It helped that the 68k had a very programmer-friendly instruction set.
They weren't all in assembly. Lots of games were published in BASIC or Pascal or even custom interpreters like Z-Code. But most arcade games and cartridge titles were coded in assembly back then.
The impact of the gnu manifesto was pretty big. We talked about it, wondered when he'd get a compiler better than the PCC. I think we (leeds uni, 1980s) said years off but actually gcc 0.9? Came out far faster than I expected.
Roll forward 10-15 years. Former compiler writers out of work. Gcc and llvm killed the market. On the other hand we have really good competing duopoly of compilers and lots of upper language support and diverse languages. So.. win some lose some. And it's not like rust and go and Java have to use llvm and gcc. Rust does. Go doesn't always.
Commercial compilers are still produced - IBM with its XL C compiler, for example, and Intel's icc. Both are good. Intel's icc often produces slightly faster code than GCC. And a little back in time, SGI's MIPSPro compilers were extremely good. I'm sure there are a couple more contemporary compilers out there but the IBM and Intel compilers are something we use at work, at least.
> Commercial compilers are still produced - IBM with its XL C compiler, for example, and Intel's icc. Both are good.
We have a few AIX machines and I’ve never heard anyone refer to xlC as a good compiler. It’s an aging compiler with idiosyncratic linker flags/behaviour and no support for modern standards.
I have no idea what you mean by 'no support for modern standards'. It's a modern compiler for modern C (and the newest versions are compatible with code written for modern gcc, with a flag). The only thing about it is it has its own syntax for flags, which aren't exactly difficult, and they're well documented.
It produces better code than gcc in many cases. Which is exactly what you would expect for a compiler written specifically for IBM by IBM. Lots of fine-tuning options for the Power architectures.
You say you have a few AIX machines - which one, and which OS version, and which compiler versions? I'm on Power7 and Power8, with AIX 7.1
Don't look at AIX 4/5.
I were, unfortunately at 3 years old I wasn't interested in computers much, and I didn't have the means to get MIT papers (or any recent papers, or the knowledge that such papers exist for that matter) even when I developed interest in computers.
But, yeah, being born in right time and place would be wonderful.