I wouldn’t even go that far. But if you are working out your program in text, and then finish up by entering the equivalent machine codes numerically then you aren’t programming in machine code. If you can simply think of the instruction names and enter the numeric codes for them on the fly then you are programming in machine code. But you won’t get very far before you miscalculate an address even if your memory of the op codes is solid. Very small programs could be considered serious I suppose, but nothing of any size has been done that way.
Yes, but sometimes, they’re also thinking “This is the number A0F5”, or whatever, and “If I add 3 to it, it’ll become the number A0F8, which has a completely different meaning”.
Slightly tangential, but back in the day, when MS DOS included EDLIN and DEBUG, and before cover CD’s, magazines published DEBUG scripts as a way for readers to get small utilities.
People would copy the text without ever knowing ‘this means jump’ or ‘this means increment’.
Well thinking in machine code will go something like this. (ISA made up on the fly. )
Ok, next move the whatsit count from register 4 to the local value in the call frame. Ok, I need a 4 byte move, so that’s 10011, I need register source so That’s addressing mode 000, register 4 is 100, to destination as stack relative, so that’s addressing mode 010 with the stack pointer so 0001, and I have six bits of range for offset and the target is 9 words down from SP, so that is 001001. So my instruction is:
100110001000100001001001. (Roughly)
If you were coding on an x86 it would be worse. On a simple RISC it would be easier, but need more instructions. But this is the game.
I have coded with this mindset, but usually because I was checking the output of code I wrote that did this exact same thing. Making up a one instruction patch is another reasonable use case. The insight programmers have is that any laborious rote skill like this is best encapsulated in code itself. It is much less error prone and faster. That encapsulation is usually called an assembler.
I should add for the sake of completeness that in some instruction formats, the target address can also be represented as an offset from the current location. I mention this for the benefit of those who may be trying to relate this to the example PDP-11 assembly listing I posted and may not be familiar with the PDP-11 instruction set, because the PDP-11 conditional and unconditional Branch instructions work this way. The target of the Branch is encoded in the low-order byte, and interpreted as a signed 7-bit positive or negative offset. This allows Branch instructions to be efficiently encoded in a single 16-bit word, at the expense of having limited range. For transfers outside this range, the PDP-11 had the double-word JMP and JSR (Jump to Subroutine) instructions.
The PDP-11 was a really beautiful and elegant architecture, and was largely due to Gordon Bell, a brilliant computing pioneer at DEC. Its only real problem was that the 64KB virtual address space was a bit small. Of course, increasing a machine’s virtual address space ultimately amounts to a huge architectural change. The PDP-11 did, in fact, evolve in this way, the result being the stellar VAX/VMS line of systems (VAX = “Virtual Address eXtension”). Bell had a major hand in that, too, by that time as VP of Engineering at DEC. The VAX was a 32-bit system with a greatly enhanced instruction set and address space, and its operating system, VMS, was a full-fledged virtual memory system. The larger VAXen were really more mainframes than minicomputers.
The Intel 8086 instruction &H90 is, depending on context, either ‘NOP’ or ‘XCHG AX AX’. In the XCHG instructions, AX is zero.
It doesn’t take much to figure out how to memorize a few machine codes and enter them into memory. Try writing some significant code where you need addresses. You need absolute and relative addresses for branching. How do you keep track of those as your code increases in size? How do you calculate them for forward references? And when you have to insert some code into what you have already written to get it to work right how do you change all the addresses you used previously?
If you do this you will soon be creating your own manual paper assembler, and hopefully realize what a waste of time the effort has been, except perhaps it would help those who can’t understand the difference between assembly language and machine code finally figure it out.
I have met Gordon once, you can add true gentleman to the list.
Two seminal papers that anyone with any interest in the subject are
What we have learned from the PDP-11
Retrospective: what have we learned from the PDP-11—what we have learned from VAX and Alpha
http://course.ece.cmu.edu/~ece447/s13/lib/exe/fetch.php?media=p6-bell.pdf
I’m not sure what you’re on about here. No one has denied that an assembler is of huge benefit to the programmer. I don’t think anyone has argued that writing directly in machine object code is a feasible way to write any non-trivial program. Speaking for myself, all I’ve been saying is that writing in assembler is writing in the language of the machine, in the sense that it’s explicitly a sequence of machine instructions, as distinctly opposed to writing abstract statements in a problem-oriented language. The PDP-11 code example I provided should make the nature of assembly programming explicitly clear, including the work the assembler does in resolving memory addresses (or in this case, mostly the offsets of Branch instructions).
What systems did you learn on to notate hex with an ampersand and a capital H?
It appears we are devolving into a fruitless semantic discussion on what programming is. The position that you must solely think in terms of op codes is needlessly restrictive. It would also imply that no-one ever programs in C or Python or whatever, because programs are designed in a more abstract functional manner, and are subsequently implemented in the programming language.
To my mind you should not require that the design of the program is done in the language itself. It is writing out the code that is programming.
We can debate whether the intermediate step of using the alphabetic op codes instead of the numerical values is to be considered relevant in light of the OP. That is just semantics. But calculation of relative or absolute jumps to my mind is simply programming, and in terms of the OP that is done at the level of machine code: t is the same effort regardless of whether you think of JMP or #C3. (incidentally, that also shows where, if you are manually writing out a program in assembly, you still need to think in terms of the actual length of the instructions if you want to program a relative jump).
It’s been done. Look up "microprogrammed FORTH machine. Here’s an example
http://www.aholme.co.uk/Mk1/Architecture.htm
Link didn’t embed. I found a few others also. Back in the good old days language directed architectures were very popular research subjects.
And if we want to go from the sublime to the ridiculous, consider writing machine code on a VLIW machine like the Itanic.* You have to pack opcodes together into one word, like horizontal microprogramming. I’d say pretty much impossible to do by hand. And in fact VLIW is directly descended from horizontal microprogramming.
- Mike Flynn’s name for the Itanium, which the Register uses also. I spent the worst five quarters of my life on that damn project, so I get to use it too.
Another area of ambiguity. Microcode was used both for machine language instructions which sat in a ROM and for microinstructions used to implement the machine language instructions - among other things. It also usually sat in a ROM, but some machines had writable control stores that let you re-microprogram it.
There were assemblers for microcode also. I wrote one.
I certainly don’t want to get into defining computer here, but a computer certainly includes instructions and logic to do Adds and subtracts, at least. Very early machines like the IBM1620 sometimes had subroutines to implement multiplication. A chip with just logic gates is an ASIC, for instance.
And not very many of the transistors in a modern microprocessor implement instructions any more. Most of them are in the cache and cache hardware and in managing the pipeline.
Microsoft. I never liked the FORTRAN / COBOL / c notation of ‘x’ – it just doesn’t say hexadecimal unless you already know what it means, the Borland notation $ is equally meaningless and says “string”, and using a & prefix notation is more obvious than using the suffix notation xxxH, which was common with assemblers. So when writing for non-programmers, I prefer &H. It’s comparatively clear and comparatively obvious.
The disadvantage is that it’s not obvious to the internet. But most of the people I write for wouldn’t automatically think of using the internet to explain 0x90. That just looks like a mistake.
I have to disagree with you. Non-programmers don’t know or care about your dollar signs and ampersands. However, they are more likely to be familiar with the notation 9016, 2208, etc.
I can’t say I entirely disagree with you, but MS has had a lot of people using their scripting languages, and it’s not a small population.
For actual numeric values, addresses, offsets, sure.
For instructions, I really doubt it.
Whilst copying, pasting and running code you don’t understand does technically count as ‘programming’, I don’t really feel it’s in the spirit of the question being addressed in this thread.