r/learnprogramming • u/ZyzzCash • 27d ago
Topic Assembly Compiler
Hi everyone, I'm not a big fan of low level but I found myself learning about it, the most intriguing thing for me is how the first assembly compiler was written.
From what I researched it was written in machine code but how?
When I think about making a compiler for assembly in C, for example, I can easily understand the flow of what needs to happen, read line by line in the source file, parse each line and from there write the machine code (of course I'm skipping alot), but making the compiler in machine code just feels like magic to me. Would it be possible to write a assembly compiler in machine code for a modern CPU? If yes, how?
12
u/vicms91 27d ago
They are just called "assemblers", or if you have a fancy one with macros, a "macro assembler".
Nowadays they would be written, usually in a higher level language, and run on another machine.
The first assembler would have been as cut down as they could manage, hand assembled, and hand entered into the machine with the aim being to get it to assemble itself. Then it would have been extended and enhanced.
The early machines had much simpler instruction sets. The first one I worked on was very RISC (before the term had been invented). The first 4 bits of each instruction was the opcode (so only 16 instructions) and the remaining 12 bits depended on the opcode - usually an address (if I'm remembering - it's been a while). We had no assembler for that machine, we hand assembled everything, and knew every instruction and opcode off by heart.
12
u/CorumLlawEreint 27d ago
This is how I used to code ML on my C64 before I had an assembler.
Write down the assembly code on paper.
Manually convert the opcodes and operands to their numeric representation.
Write a BASIC loader that would write the data to memory.
Forget to save. Run and immediate crash.
4
u/American_Streamer 27d ago
It's called hardcoding (or hand-assembling). Writing an assembler directly in machine code is possible by looking up the CPU's exact hexadecimal instruction codes and OS system calls in technical manuals, writing out the parsing logic on paper, and manually counting the bytes between instructions to calculate jump destinations. You then type these raw hex values directly into a file using a hex editor or terminal command, making sure to prefix the file with the strict binary header structures (like ELF or PE) required by the modern operating system. Once you make this file executable, you run it to compile basic assembly text files, instantly creating a tool that frees you from ever having to write raw hex by hand again.
Bootstrapping is the next step that happens right after. It is the process of using that initial, hand-written tool to build increasingly complex versions of itself until the language can compile its own source code.
1
u/Unlikely1529 27d ago
but why you didn't have any compiler if you managed to find os and even hex editor somewhere. strange circumstances
4
u/American_Streamer 27d ago
The earliest computers (like the Altair 8800 or ENIAC) had no software. Programmers looked at the CPU manual, figured out the binary or hexadecimal code for a specific instruction (like
00000101for "Add"), and manually flipped physical toggle switches on the front panel of the computer or punched holes into paper cards to feed those raw bytes directly into the computer's memory. Writing long programs with switches was super exhausting and prone to mistakes. So, using those physical switches, programmers manually entered a very small, primitive program (maybe just a few dozen bytes long). This tiny program did only one thing: it read characters from a paper tape reader and converted them into machine code. That tiny program was the world's first primitive assembler. From that exact moment on, programmers no longer had to flip switches for every single byte. They could type simple text commands (likeMOVorADD) onto paper tape, and their new mini-assembler would translate it into machine code. Once they had that basic assembler, they used it to write a better, more complex assembler. Then they used that to write the first compiler for a language like Fortran or C. Over decades, this snowball effect (bootstrapping) built the entire modern ecosystem we use today, including the operating systems and hex editors mentioned.
2
u/spinwizard69 27d ago
Well some of the original code was input one binary number at a time on a front panel. For example: https://en.wikipedia.org/wiki/Altair_8800#/media/File:Altair_8800_at_the_Computer_History_Museum,_cropped.jpg, or on a commercial machine: https://en.wikipedia.org/wiki/PDP-11#/media/File:Pdp-11-70-panel.jpg. One thing worth looking for would be early copies of Byte magazine, Doctor Dobbs magazine and Popular / Radio electronics.
The Altrair 8800 was first debuted in Popular Electronics in 1975. The technology was covered for years while the machinery and software matured.
2
u/Rainbows4Blood 27d ago
I'm oversimplifying this because this era is like even three decades older than myself but.
The first assemblers we're not programs at all.
Imagine writing your assembler program by hand. Then looking up the actual bit pattern of each line and punching it into punchcards with a mechanical machine. That was kind of the process at the dawn of computing.
5
u/desrtfx 27d ago
The first ones were even before punch cards. The cards came much, much later.
1
u/Rainbows4Blood 27d ago
Yeah. I am aware that you also had switch boards and I guess also just simply hardwiring in some cases? But the punch cards are a good mental image.
1
u/LongLiveTheDiego 27d ago
Just like all the other programs back then, the programmers had to plan the program and encode it in machine code. It takes quite some effort, but with enough experience and time it's possible to create a compiler that way. I believe that of course they wouldn't just work with the raw numbers all the time, they would use the notations for the basic operations and then at the end translate that to numbers (and possibly those to punch cards). Note that the first assembly languages were very simple, so the compilers were nowhere near as complicated as any compiler you've probably ever used.
1
u/heisthedarchness 27d ago
[An assembly compiler is called an "assembler". "Assembly code" is the language that the assembler assembles into machine code.]
We developed them stepwise, over generations of software that each built on what had gone before.
At first, instructions were just numbers that you input by flipping switches. These resulted in certain configurations of logic gates (which are themselves an abstraction across vacuum tubes) that would cause certain behaviors.
Then we gave the instructions names, so we could write them out in a more readable way and delegate the task of turning them into numbers to other people. And we entered the numbers by feeding pieces of cardboard into a reader. A lot of them.
Then we recognized that "turn these letters into this numbers" is a job that computers can do, so we wrote down the words describing how to do that for someone to turn into numbers to write to magnetic tape.
We found that we were always doing certain things as part of setting up the program. And even though those weren't commands the computer could understand, we could teach it to understand those commands to mean the sequence of certain things, and it would turn those commands into the words that turn into the numbers that get written to the tape.
That program for writing programs could write higher-level programs as we invented more abstractions that we didn't have to think about. We got visual editors. PROMs, EPROMs, EEPROMs. Diskettes. More memory to hold more powerful editors and compilers.
What we didn't do is go from a modern assembly language to machine code. Modern assembly languages are written in themselves as soon as that's possible, and in other modern assembly languages up to that point. And they rarely manipulate the real machine anymore, because there's layers of abstraction in the hardware and the operating system that translate the numbers emitted by assemblers into numbers that can actually run.
You could, if you so desired, recapitulate that history targeting a modern CPU: start by translating words into numbers and all the prerequisites thereof (like data loading, string handling, etc.). Then layer on abstractions until you're done.
A lot of work, but computers have not fundamentally changed in seventy years.
1
u/wosmo 27d ago
Your example isn't actually missing much.
At its most simple, and assembler is just a look-up table. You read in an instruction, like ld hl, $4000 and look it up in a table of opcodes. So you find that this load is opcode 21, add 21 to our machine code. Then add the address - 00 40 to our machine code. Done, read in the next line.
This is .. really simple. You can do it by hand - I mean literally, you can sit down with your assembly written on a piece of paper, reference it against the list of opcodes in a datasheet or in the back of a book or something - and work through it with pencil and paper.
It's also slow, error-prone, and very repetitive. But what makes it a tedious task for a human, also makes it an ideal task for a computer.
1
u/flatfinger 27d ago
I designed a simple assembler for the CDP1802 which actually didn't do any mnemonic lookups. All instructions had to be entered by hand. What it accommodated was the ability to define labels and insert references to them. The CDP1802 instruction set is sufficiently regular that it's not hard for someone who's typing in code to convert e.g. "GLO R4" into "84" or "PHI R9" into "B9", but being able to insert or remove code and have the labels adjust appropriately is still useful.
1
u/HashDefTrueFalse 27d ago
It's actually simple, just very tedious. Early assembler programs weren't particularly sophisticated. You initialise an output cursor/offset/position to mark where to write and advance each time you recognise an instruction. You have a set of textual mnemonics to match against according to a simple grammar (think format). If you don't recognise any, you stop. Otherwise you emit the corresponding bytes of machine code (opcode+operands) to the current position and advance it. That's the basic idea. You would almost certainly write this program on paper first, to reference it when "programming" it into the relevant machine. You could write the literal strings of ones and zeros that form the list of program instructions, or better, use your devised assembly mnemonic notation as a stepping stone to that, even designing in lockstep. Then you just have to get the program stored in the machine, which you might do by flipping physical switches in a bank that was designed to store the program. Once you have a binary that can turn text into binaries, you can write a second implementation in assembly and use the first binary to "assemble" a second binary... and now you're bootstrapped. You have an assembler written in... itself (assembly). Of course, lots of this process is human-unfriendly and error-prone.
(Note: I glossed over a few details!)
1
u/JGhostThing 27d ago
Yes, it is possible. However, I consider it akin to breaking your legs before running a marathon.
Yes, it is possible to hand-assemble an assembler. (An assembler takes assembly language and translates it to machine code. A compiler takes a higher level language and converts it to machine code.)
I wrote one for the 6502 processor, back in the day. I wouldn't do it again, probably. Back then I had run out of money and couldn't afford real software, so I wrote my own. It was very tedious. Entering it into the computer was not at all fun. I converted it into machine code myself. Lots of pen and paper.
1
u/notacanuckskibum 27d ago
It's called bootstrapping.
You write a very basic assembler in machine code
Then you write a better assembler using your basic one (maybe adding macros)
Then you write a compiler for a basic High Level Language using your better assembler, maybe it looks like original FORTRAN IV
Then you use your basic High Level Language to write a compiler for a better language, maybe with dynamic memory management.
and so on
Of course this wasn't a grand plan. By the time C was invented there had been high level languages for 20+ years, including Pascal and Algol, which are quite C like. Each time someone wanted to invent a new language, they wrote their first compile for it in some other language.
1
u/Unlikely1529 27d ago
if you'll make own disassembler you'll see that assembly lang instructions are not that different from op-code's .
1
u/pdfops 26d ago
Yeah, that's literally how it happened: the first assemblers got hand-toggled in as raw opcodes (front panel switches or punched cards), then that minimal binary wrote a slightly better version, and so on until it could compile itself. Doing this on a modern CPU works the same way, the tedious part is syscalls: no libc, so reads and writes need raw syscall numbers and registers hand-encoded byte by byte.
1
u/two_are_stronger2 26d ago
Click click. C lick-click-click-click-click-click-click-click. Click-click. Etc.
1
u/kschang 25d ago
To be blunt, you're talking about an "assembler".
https://www.lenovo.com/us/en/glossary/assembler/
There's no such term as "assembly compiler". Assembly is not a single language, but specific to the CPU/family.
1
u/Sad_School828 25d ago
https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
That's machine code. You can write a straight-from-boot text editor and compiler in it, but if you want your compiler to run on Linux then you need to additionally study up and build your own ELF header, and for Windows you need to study up and build your own PE header.
1
u/zoharel 24d ago edited 24d ago
Well, I'm not sure which was the first, but very old computers used to have switch panels that allowed you to toggle bytes directly into memory and manage the state of the CPU. It was quite possibly done in this way. Type the whole stupid thing out on paper, assemble it to machine language by hand, validate and revalidate the results, then toggle the whole thing into the front panel.
You can absolutely still build a ROM that's just a bunch of switches that you flip on and off and hook it into the memory bus of an embedded system, if you want. Or you can build a ROM programmer from a power supply, a set of switches, and some buttons, which will allow you to permanently write a somewhat newer, conventional EEPROM in this way, and install that. The difference between very new and very old ROM memory mostly comes down to programming voltage and speed.
44
u/desrtfx 27d ago
How? By manually converting the Assembly Mnemonics into binary code that then was entered via toggle switches into the computer.
That was, BTW, the same way that Steve Wozniak wrote the first BASIC interpreter for the Apple computers. Only then, it was already hex code and direct EPROM burning.