Is there (was there ever?) serious programming being done in machine code?

I learned to program in high school on an LGP-21, which was the transistorized version of the LGP-30 in that story. My first programs were in machine code - then I wrote a simple assembler to be able to use labels, not hex addresses, for jumps.
It turns out that my first PhD adviser probably wrote the world’s first assembler. He was a student of von Neumann’s at both Princeton and IAS, and wrote it for the IAS machine. He told me that von Neumann thought it was a waste of time.
Now, to me machine language is a high level language, because I did my PhD research in microprogramming.

Machine instructions were implemented by microcode routines. We had a microprogrammable Lockheed SUE minicomputer (used as the first IMP for the early Internet) and I wrote an emulator for the LGP21 which I ran on the SUE microcode simulator which was originally in PDP-11 assembler language. I rewrote it in Pascal for my research.
And one company, called Nanodata, invented nanocode which you used to emulate microinstructions. Never took off. One of my oral questions was about picocode. I said it was a bad idea which is what they were looking for.
It was all a lot more fun than coding in C or Python.

If you didn’t have a linking loader - like our PDP-11, then symbolic addresses got assigned absolute ones.
One of our PDP-11s had the bootstrap routine in ROM. The other didn’t and we all memorized it and entered it through the front panel switches. Good times!

On our LGP21, the opcode for a Bring instruction was the last four bits of a “B” (non-ASCII). So typing instructions was not that hard. What killed you was when you wanted to insert a new instruction in the code. Since jump addresses were absolute, that meant you had to go and revise all of them. Or, what I did, insert a jump to the new code after the end of the existing code, execute it. and then jump back.
Having an assembler with symbolic destination addresses fixed all that.

The difference is in what ‘programming’ may mean. If code is designed at the assembly level and then manually assembled to produce the machine code that’s not programming, it’s just manual assembling and entry of the object code. If you do consider that to be programming then people do program in machine code.

Ah, right - I agree that is an important distinction.

If a person writes assembly language on paper, without the aid of an assembler program, and translates it manually into machine code, would you call all of that ‘programming in machine code’ or ‘programming in assembly language’? Is there a difference in this context?

How do they create BASIC? Or FORTRAN, COBOL, C++, Python, Spark, or any other computer language for that matter? I assume they don’t write BASIC using BASIC.

You can write BASIC in BASIC. It’s a good measure of the completeness of a language that it can compile itself. Many modern languages have been written in themselves, or other high level languages. Compilers create object code through a series of steps, which at the bottom level is machine code. The steps may include producing assembler code which is assembled externally. It’s a lot more complex than that in a variety of ways, but any complete language can produce the same results as produced by any other language.

The process of bootstrapping languages can be quite easy because you only need the minimal functionality required to keep improving your compiler in the new language.

The punchline of @Morgyn’s linked story about Mel, the Real Programmer, was that he was doing arithmetic on the opcodes themselves, while the program was running, to change them into other opcodes and thus change what the program was doing. That’s something that’s inherent to the machine code level, but not present even at the assembly level.

  1. If your assignment is to print HELLO WORLD on the screen and you only half remember how to do it, THAT’S serious programming.

  2. The Motorola 6809 is a lovely processor for this sort of thing, especially in a Tandy Color Computer .

  3. Of course TI"s Ti-8X and -9X calculators are slow–they are still running Z80 processors, I think in some familes Junior is using Granny’s graphing calculator .

The linking loader, and the idea of the assembler producing relocatable object code as well as linking to system libraries and user libraries is an aspect I was going to mention as well. All very true, but I would note that it all depends on the capabilities of the particular computer. Simpler computers, like the DEC PDP-8, did not have the kind of memory management needed to provide relative addressing, and assembler programs generated machine code with absolute addressing, albeit with the programmer at least being able to use symbolic addresses. Larger computers implemented the features you describe, along with protected and privileged addressing modes, such that user-mode addressing was always relative. And so did later minicomputers. In the minicomputer space, the PDP-11 was the transition point.

You’re quite right in a sense that in more sophisticated computers, with things like macros, library calls that were resolved by a linking loader, and system calls, assembly language began in some ways to resemble a higher level programming language. In some ways! But the vast majority of the code retained that fundamental attribute of all assembly languages: a one-to-one correspondence between the symbolic instructions and the corresponding machine code.

Not just modern ones. LISP is one of the oldest languages, and at least some implementations were bootstrapped that way.

But I would argue that in the real world you need more than Turing completeness to make it practical to write a language in itself – that’s only a theoretical consideration. In reality you need fairly powerful symbol-processing capabilities, because the essence of what a compiler does is parse the source statements and perform complex transformations that ultimately spit out assembler code. A language like FORTRAN, which is focused on mathematical computations, would be a poor tool for writing a compiler. For many practical reasons, back in the day almost all compilers were written in assembler.

LISP was a notable exception in at least a few implementations, because as a language focused on AI applications, its ability to do complex symbol manipulation was excellent. By the same token, it was also a difficult language to compile, and the natural implementation was as an interpreter, which suffered from being slow. But in an elegant example of bootstrapping, one of the early MIT implementations of LISP was first written as an interpreter, and then a LISP compiler was written in LISP. Its first task, needless to say, was to compile itself, producing a fast native object-code compiler! I’ve long thought that that was just supremely elegant!

Bootstrapping a compiler used to be one of the basic tricks taught in compiler construction. The goal being to write the compiler in its own language. This was sort of a sincerity test of the new language. The paradigm was represented in T blocks, each representing a compiler. The cross of the T represented input language->output language and the stem, the implementing language. You could stack Ts so that you could present a compiler written in one language with a working compiler for that language. Eventually you could stack them up in a manner in which you could compile a compiler using itself. Compilers need not always emit machine code. Cross compilers emit code in a different language.
You could craft a very basic compiler in another existing language, and bring the stacked Ts artifice up to the point where the “proper” full capable compiler was able to compile itself running compiled code it itself had compiled.
This is less a thing now. But a neat trick.
Nowadays systems like LLVM and the gnu compiler collection use intermediate abstract machine representations as the target, and that subsystem is responsible for the heavy lifting into machine code. LLVM managing a pretty clean break between the two.

The first major application program I used was dBase running on an Apple II using CP/M as the OS back in 1980. At the time, dBase was advertised as “Written in Assembly Language” to tout its speed.

Of course, the fact that compilers are themselves compiled in compilers opens up the possibility of them containing code that was never in any of the source code, but inherited directly from the parent compiler. Like, say, a backdoor password that’ll let Kernigan into your system.

In the 80’s my job entailed writing programs in Z80 assembly language for embedded systems where the code would be burned into ROMs (read only memory) chips. Of course, when developing the code it is written to RAM and not ROM. When a bug was found in the program, often I would put a jump instruction at that location that went to an area of unused memory where I would enter correct code directly as hex numbers and then jump back and continue with the rest of the program. I knew all of the common machine instructions and had a cheat sheet for ones I didn’t know. One of the fun jobs I had was writing a BASIC interpreter. It was especially interesting to see how useful the push down stack is to recursively evaluate arithmetic expressions.

Count me among those that would consider writing in Assembler to be “real” programming of machine code. I mean, there’s a one-to-one mapping of assembler instructions to machine code, it’s just the latter is more typing.
It would be like saying that real writing involves smearing ink on to a page using your fingers, and that the convenience of using a pen makes it not writing any more.

I did exactly the same thing as Mel did for a tic-tac-toe program which wouldn’t fit in the 4K of memory I had. I changed a bunch of adds to subtracts and vice versa. This was in 1968, long before I read the story.
You can’t do it if your linker/assembler is going to move code around, since you don’t have access to the addresses of your instructions. You can do it fine in assembler if you have more or less fixed addresses.
And of course you can’t do it at all in modern architectures which keep data and program spaces separate.

They ran Pascal code on a Multics system when I was in grad school by translating the Pascal programs into PL/1. I got the Jensen/Wirth compiler to compile itself for the first time. The biggest problem was decoding their awful one and two letter variable names, and dealing with the fact that portable to them meant 60 bit words.

4900796

Related thread from several years ago: If this thread interests you, then this older one will too:

I don’t understand your comparison. If you write some code in any language, and then someone else types it into file to compile and execute are they programming? Were you programming if you didn’t enter the code into the computer some how? Were you both programming? If you both were programming why were you both doing such different things?