Even if you can't write assembly, you can read disassembly
wordsandbuttons.online
wordsandbuttons.online
I still have the annotated listing 41 years later. It's like the rocky horror picture show: you take a JMP to the left, and turn around...
Writing optimized assembly is a whole other question. But some variant of the above - pseudo-code or flowcharting, followed by close translation into assembly, is how most software in assembly was written, back in the day.
Assuming your program logic is correct to begin with, it's almost mindless. So mindless you could program a computer to do it.
The most complicated assembly I've written recently is a fast Wasm interpreter. The only way I could tackle it effectively was to write the implementation of just one or two bytecodes at a time, test them thoroughly and in isolation. We're talking about writing only a handful of instructions per commit. There's just no way you can debug much more than that at a time. You need to build little bricks of fully-working code and stack them, ratcheting up the progress, so that when something breaks, most of the things you don't need to debug.
I once burned a couple hours on a microcontroller because I had transposed bits 5 & 4 on some register that dictated a certain i/o behavior and caused things to be really weird. One register out of like 11 registers that affected how that i/o operated, one bit transposed, whole thing was inscrutable.
People claim GPT is creative and when I disagree, they say mixing to come up with a new combo is creative and that humans do it for creativity too
While that might be true, what's even better is if GPT could read assembly code, find things that can be fixed and optimized and write new more optimal better code.
Of course this can be done in any language. If we could get GPT to optimize our code libraries to reduce code size, memory footprint etc that would be a huge win for humanity and also show creativity.
In short, it should tackle the "why is hello world app in X language taking up 100MB" problem :)
To a large extent you can consider high level languages opinionated. They have decided how to pass arguments, how to return values and how to lay out memory. The programmer no longer needs to think about these things, and they can't mess them up if they try. Doing this yourself you need to pick to those conventions and stick to them if you want maintainable software.
Of course the benefit of assembly is that it is trivial to break those conventions when there is good reason (optimization for example) but then you will pay the price of more difficult maintenance for that unconventional code.
This is the flight software used in the Gemini spacecraft (1960s).
The actual assembly code did not survive. It was considered sort of incidental. For the designers and coders then, the flowcharts were the program, the real source.
There's more information about the Gemini software and how the programmers worked here: https://www.ibiblio.org/apollo/Gemini.html#Evolution_of_the_...
The problem with assembly isn't that it's too complex, it's that it's way too simple, so it's exceedingly easy to lose track of how your higher-level thought process maps to assembly code. The flowchart serves as an intermediate representation between the two — something you can reason about at a high level, but can almost mindlessly, mechanically translate to assembly.
To be fair, that's the compiler's job; everything from dead-code elimination to loop auto-vectorisation and auto-parallelisation is pretty hard when done manually.
I think it might be even more useful with a faint background color so you could skip ahead to the end of the parenthetical if you figured out what you wanted to a few words in. Overall really cool feature though, which I'm tempted to steal haha.
A side note: I still plan to read your Geometry for Programmers, now that it is close to being finished. All the best!
But not on this page. I thought that the arrows are a bit noisy. I can still add this "toggle back" feature here if you want.
RollerCoaster Tycoon is probably the most complex one, but also Transport Tycoon and Locomotion, all by Chris Sawyer.
Though fun to play, I think it would be a nightmare to be fixing bugs in such mammoth assembly projects..
It didn't. It was crashing and I didn't know how to use a debugger with assembly programs back then.
I combed frantically for the bug. Some hours later, it turned out I was missing a 'ret' in some function..
The assembler didn't complain, of course, and the program happily continued executing whatever was there after reaching the end of the said function.
Or is there an assembly that get rid of this gibberish tradition?
Even more so because the influx of new users of assembly languages that use it more than very occasionally are few and far between.
There has been some attempts. E.g. see [1] for an "assembler" that provides some higher level constructs where there's a simple mapping to underlying machine instructions, but that might go further than what you'd like.
MYARRAY: DEFW 100
ADD R1,R5,R7
MVI R2,R1,MYARRAY
how about: MYARRAY: WORD[100]
R1 = R5+R7
MYARRAY[R2] = R1
Not much harder top parse, and much easier to read. I see a couple of minor issues: needs a tweak to differentiate between (say) ADD and ADC; and assembler generally is small change, we're not putting much effort into it.There are a couple of different ways to understand assembly. Writing it with C-like infix expressions is a superior way to look at the code if you're trying to understand what it's doing on an algorithmic level (there's a reason we don't really hand-write assembly, after all!). But if you're working on assembly-level tooling, usually, you want very clear indications of what instruction you're working with, and "out_operands = opcode in_operands" or "opcode operands" are much, much clearer representations for such work. Most of the people who work with assembly care about the latter, and so the latter representation is more useful for them, and that's why we write assembly the way we do.
Multiplication results are stored in a double-length result register called MRF. The type of inputs is indicated by a modifier word in parentheses, so that multiplication between two signed integers is indicated as MRF = R2 * R3 (SSI); whereas multiplication between a signed and unsigned integer is MRF = R2 * R3 (SUI); There is no popcount, but binary log is written like R2 = LOGB F3; and presumably similar notation could be used for popcount.
Cache access seems to be controlled by mode bits in a control register. Sign extension is supported in only a few instructions, but is indicated by the trailing modifier (SE).
Yes - that's where is falls down :(
A processor does not have a conception of brackets. Infix notation blurs the real number of operations. Processor has registers and your code does not say what you are putting in EAX what in EBX etc.
My example wasn't clear enough - I wasn't proposing full expression compiling. If you think (simplistically) of assembler as a macro processor that maps text mnemonics onto opcodes, I was proposing matching basic expression terms to opcodes (but only simple terms, like "x + y", that map to individual instructions).
Historically, there have been assemblers that do full expression compiling - where an expression compiles to multiple opcodes. But that's going back a way, when people still routinely used assembly. For modern purposes, where good compilers are easily available, something like that just confuses assembly with compilation.
In contrast, the entire MIPS32 specification fits on one sheet of paper[1], and MIPS was used in some very productive and entertaining applications—the Sony PS1, PS2, and PSP, for instance.
ARM is similar, as is RISC-V (although the former has a flag register). I find these RISC assemblies in general more straightforward than x86-64. They are also register-register, load-store architectures that make memory accesses quite explicit.
This doesn't mean x86-64 or CISCs in general are inferior or superior to RISCs. They are two different approaches to the same problem, and as AMD has shown, it is possible to achieve good efficiency on CPUs using CISC ISAs, too.
[1]: https://inst.eecs.berkeley.edu/~cs61c/resources/MIPS_Green_S...
Then again, 6502 specs can probably fit on one sheet of paper too, just as MIPS32 did, and it too has been used in very productive and entertaining applications—and yet it's even more idiosyncratic than x86; I've seen considerably more examples of "an instruction that sets flags; anywhere from 1 to 10 instructions that don't set flags; conditional branch" pattern in 6502 code than in x86.
I guess it's similar to learning functional vs. imperative programming language as your first intro to programming?
P.S. AT&T syntax for x86 is
a) backwards: "subl %eax, %ebx" vs "sub ebx, eax" for "ebx -= eax", ugh;
b) pretends that numbers are weird functions: "subl -32(%ebx,%ecx,4), %eax" vs "sub eax, [ebx+ecx * 4-32]" for "eax -= MEMORY_AS_ARRAY_OF_INT32[ebx+ecx * 4-32]", what the hell, minus thirty two is most definitely not a function with three arguments; neither is it a three dimensional array if we pretend that parens stand for array indexing as they do in FORTRAN.
That's fair. I started out with MIPS32. When I first saw x86 instructions, and a `mov`, my first thought was, 'what the heck? Where are the loads and stores? `mov rax, [rip + 32]` is 3 instructions in MIPS or any similar RISC, this looks weird.'
> P.S. AT&T syntax for x86 is
Oh, yeah, I hate it. x86 instructions can already get pretty long; now you have multiple suffixes just to account for size. The indexing operation is just ridiculous; Intel syntax makes it so much more straightforward.
I learned 6502 assembly as a kid right after basic... and ever since always had enjoyed it. Pretty much until the compilers became better optimizing code than me (not the 6502 one, of course)
The hard part about assembly language is its very limited abstraction. You’re only one step away from the metal. So it takes a lot of work to build a large program and it can be very tricky to get right because you don’t have a compiler or any types to help you. If you know what you’re doing, though, you can write some very small binaries!
You can write about assembly like a poet, at least.
Someone wrote a script that disassembled all the binaries on their Linux machine and counted the number of actual instructions used (mostly by gcc, of course). Only a tiny fraction of the total instructions were used:
http://0x80.pl/notesen/2014-01-01-instruction-utilization.ht...
Just take the top 30 of that chart and learn those. The x86 contains a lot of instructions that no one needs to worry about. Things related to the ancient segment mode or x87 floating point, OS-privileged instructions (context switching, HLT/LIDT/etc.), AMD-only 3DNow, etc.
The ISA is not the hard part anyway. Dealing with the ABI (System V on Linux) to interface with C/C++ code, the kernel syscall interface, position independent code/executable (PIC/PIE) are somewhat bigger concerns and are going to apply to RISC as well.
Modern AMD CPUs don't support it either, so it's entirely dead
AArch64 is more like regular than reduced instruction set computing. Its a very big complicated ISA but also not a complete mess like X86.
RISC-V is reduced by these standards but requires a very real instruction count penalty. The compressed ISA performs it's job admirably, do keep in mind, but I think it remains to be seen if some of the aggressively simple/clean aspects of risc-v end up hurting it.
Here is a bit of code extracted from the Diamonds source code:
\* This is executed each time you die (with some lives left)
maSTART_OVER GOSUBL dsQUICK_DRAW draw the bricks quickly, not disolve
maSET_FLAGS ST=0 6 you can't knock out diamonds yet.
ST=0 7 you have no key
GOSUBL hbDRAW_KEY make sure no key is showing
LCHEX #00001 white brick
GOSUBL srSTO15_C erase mode
GOSUBL emDRAW_BRICK show erase brick
LC(5) BONUS_TIME a good time...
GOSUBL srSTO25_C bonus time
GOSUBL dlDRAW_LIVES draw lives left
GOSUBL srRCL6_C get level addr
C=C-CON A,5 now pointing to X,Y and DIR
D0=C in D0 (pointing to X)
C=0 A
C=DAT0 2 read X
GOSUBL srSTO0_C X
D0=D0+ 2 pointing to Y
C=DAT0 2 read Y
GOSUBL srSTO1_C Y
D0=D0+ 2 pointing to DIR
C=0 A
C=DAT0 1 read Direction
GOSUBL srSTO2_C store it
GOSUB grGET_READY display the GET READY window D0.w = 42
.. to assign the immediate value 42 to the lower 16 bits of the D0 register (equivalent to MOVE.W #42, D0 in M68k assembly). Or: D0 = D0 + 2 (or D0 += 2)
.. just as in your example ("ADD.L #2, D0").You could also combine the asm with accessing variables etc. E.g.:
D0 = x + 2
.. would be ("MOVE.L x, D0; ADD.L #2, D0" if x was a heap variable or "MOVE.L someoffset(SP), D); ADD.L #2, D0" if on the stack. But you could also just insert the assembly directly into the code...I wish I still had the source (it was initially written in assembly, and the gradually converted to itself); it was "interesting" writing a compiler where you could reliably intersperse higher level code with assembly "safely"
But of course that puts pretty hard limits on the compiler (e.g. no optimizations), and you end up doing low level programming just with a different syntax.
That's to say, it looks convenient, and it was nice as a way of bootstrapping a simple compiler, but you're right to question whether the convenience is worth it.
If you expanded all names, that would add cognitive overhead for all users and reduce the useful information per character, and potentially reduce the information that can fit on a screen significantly.
While I'll agree that "cryptic sequences" are a more steep learning slope, they allow for much faster understanding of larger blocks of data/assembly once the "cryptic sequences" themselves are understood.
Terse doesn’t necessarily mean opaque. Even if sticking to three letter words was an absolute requirement, there are plenty of English words that would make a better jobs at conducting the idea without falling into a collection of unpronounceable idiosyncratic terms.
> as the information is spread across less characters that need to be interpreted by the brain.
That’s not how the brain interpret text generally, once reading is fully acquired. Words, or even group of words are taken all together.
Would it be for scriptural compactness alone, it would be far more efficient to use dedicated symbols optimized for that purpose. But if the idea is to optimize readability for an unknown large audience, then this is most likely a rather misguided choice.
>If you expanded all names, that would add cognitive overhead for all users
Well, I disagree. To my mind it would on the contrary significantly reduce cognitive load for those not already familiar with the uselessly abstruse terms, and add nothing to those already accustomed to code constructions in the machine specific language.
>reduce the useful information per character
Once again, this is a very poor metric for human interpretation of textual works.
> potentially reduce the information that can fit on a screen significantly.
So what? Number of signs displayed on the screen is certainly not the limiting factor here. How much juxtaposed signs a human attention can meaningfully handle is far more compelling.
>they allow for much faster understanding of larger blocks of data/assembly once the "cryptic sequences" themselves are understood.
I am not aware of any study that supports such a statement on empirical evidences. Maybe you can point me to such a study and help me change my mind?
Assembly, back in the time of punched cards, had some reasonable rationals for avoiding input sequences longer than strictly necessary. Bu these rational no longer fit the contemporary digital landscape.
(E.g. jump_if_greater would not be that good, jge is too short and unless you actually know what it is, it is hard to decipher, jmp_ge is not a bad compromise)
But apparently, this is not a win-win, but a lose-lose since this notation is hated by both assembly programmers and not assembly programmers :-D
That seems to be a bit tangent to my remark though: this seems to be more about adding syntactic structures, embodied with miscellaneous brackets, and there are still some obfuscating abbreviations like "ldstr".
ldstr "Hello!"
call void [mscorlib]System.Console::Write (string)
And come to that: ((write: "Hello!"))
So `ldstr` does indeed feature in the example but only as a "before" picture.But you're right, it is about adding syntactic features to the existing language.
Otherwise, mnemonics are the way they are because of history, but also practicality. Since you're going to be writing them a lot, you want them to be short. It also doesn't take too long until their meanings are burned into your brain.
Or is there an assembly that get rid of this gibberish tradition?
Look up Randall Hyde's HLA. The famous author of various Asm books thought he could make a better syntax, and... almost no one wanted it.
The real answer is that nobody is supposed to write assembly, and the way the instructions are written is very easy to parse. The only core that doesn't have the "it's ancient and we didn't know better" excuse is RISC-V, IMO.
C
dest = src1 op src2;
Only basic control flow, no structured loops and so forthTen years later, when I met someone from my old job, it turned out the assembly was still used. Probably the only consequential thing I did in all the years I worked there.
(This obviously wouldn't apply to web dev roles.)
SIMD might be too masochistic!
Actually, most of the time I read disassembly to figure out why something crashed. It's a useful skill in many contexts!
(And that's the source: remember the disassembly will lack comments and macros.)
I’ve personally found it incredibly helpful in trying to understand exactly what AVX512 code compilers can be coaxed into generating. Thanks Matt!
What would be the best way for someone today to learn assembly? Which architecture, chip, devkit etc. would be an ok start, without too many complications (init process, toolchain, good documentation, etc.), preferably on linux?
There's really no need to delve into hardware when teaching assembly, as software simulators/interpreters exist; there's QtSPIM[4], a LEGv8 simulator from ARM itself[5], and a RISC-V interpreter by Cornell[6].
[1]: https://www.amazon.com/Computer-Organization-Design-MIPS-Arc...
[2]: https://www.amazon.com/Computer-Organization-Design-ARM-Arch...
[3]: https://www.amazon.com/Computer-Organization-Design-RISC-V-A...
[4]: https://sourceforge.net/projects/spimsimulator/files/
[5]: https://github.com/arm-university/Graphical-Micro-Architectu...
[6]: https://www.cs.cornell.edu/courses/cs3410/2019sp/riscv/inter...
Essential: https://developer.arm.com/documentation/ddi0487/gb/
I agree with the bible on this. There nothing "new", all humans do is "working on existing knowledge".
"There will be things that people will look at and say: 'they are new!', but these were here before us. Since forever."
So, IMO the author did realize that there will be "new" things, the premise is that these things are already part of our world, and the process and mechanism of discovery is repeated. In the human perception (and LLM's ?) there is only "rehashed" knowledge in a way, nothing is really new.
I don’t see how “general purpose computers” have always been here before us — many things can compute (one proper, but not too useful definition of a computer is something that represents a computable function by upon giving it an input in a specific format, it will give an output that can be interpreted as a result. E.g. a sundial is a computer), and none of the primitives are unique (logical gates can be made from many things), but their unique composition into a programmable computer is a novel invention.
It's not impossible they may have known things that we don't.
Ultimately existing AIs can only help you with stuff that’s publicly documented, preferably documented multiple times. Lots of the world is either undocumented, or the documentation only exists in private repositories. Even AI can’t know what it doesn’t know.
i can add X + Y for any values of X and Y. not because i have a gigantic table in my head, but because i know the rules of adding 2 numbers. AI is to some extent doing the same.
the fact that it does increasingly weird unhuman things to me indicates it is infact intelligence. it isnt just parroting the answers for itself. it is using its flawed understanding to attempt to reach an answer. humans are also flawed and come out with stupid answers, but we are used to that and can understand it.
Intelligence = ability to solve novel problems. Requires out-of-the-box thinking. ChatGPT cannot learn and solve problems it has never encountered before, thus it is not intelligent. Also, training != learning.
Intelligence is knowing the rules of addition to apply it to 2 numbers you've never been shown how to add.
I can memorise 1+1. I've never been shown the answer to 47459592271638494 + 3745802297337747488. That for me is a novel problem that would need to be solved.
So the fact that I have knowledge of mathematics and can apply it shows I have intelligence.
Out of the box thinking isn't a prerequisite of intelligence. It's a special case that humans are good at and computers aren't.
If a mouse bumbles around a maze, eventually finds the cheese, and from then on goes straight to the cheese, that would be a sign of intelligence. If a robot does the same, why is that any less intelligent?
T add(T a, T b) {
return a + b;
}
And let’s make T BigInteger for now.The thing is this is basically instinct. At the transistor level a computer knows how to add two numbers together. But then again any kind of AI is going to come down to binary digits so unless, definitionally, a computer can never be intelligent then we have to allow that your example is some kind of intelligence.
Isn't this being disputed for GPT-4 [1]?
As long as people weren't ready to look at human anatomy, they could only observe symptoms and make super wild guesses.
That is what this is. Youre observing a thing in a black box with a screen, and youre reading too much into it if anything in the past is to be believed.
Show a person a cellular automaton and dont explain it to them, leave them with it in a room for a month, see what their theories are. You bet it wont be "theres three rules and a random number generator", even if thats all there is.
Our conception of 'intelligence' is based on us rather than some objective metric.
There's absolutely no reason for an intelligence to be anything like us. We are flawed in a great many respects. We'll parrot things we have no reason to believe in etc, etc,etc.
Im not even sure there's a distinction between intelligence and emergent behaviour. Did we evolve to speak and write complex language or is that emergent behaviour?
For AGI, I would expect it to be teachable during the conversation. That is, to be able to form abstract models of reality and utilize them on the fly. Repeatably, reliably. Like a human with a pencil and a piece of paper can.
We already have models that can be shown multiple pictures of ducks, learn what the essential characteristics of a duck are and identify novel pictures of ducks.
So the AI has been taught. Has formed an abstract model of a duck and can reliably identify ducks.
When does that become AGI? It can't purely be writing proofs because that's niche even for humans.
Further what is intelligence? A professor of mathematics may be able to rattle off a proof. A 3 year old probably not. The 3 year old has amazing learning potential though. We accept that both the professor and the 3 year old display intelligence, but we dont seem to apply the same rules to AI.
Can a part of the human brain be intelligent? Are humans missing a part of their brain, such as through brain damage, intelligent? Such humans may not be as fully capable as humans with an uninjured brain, but (depending on the extent of the damage) we would still consider them intelligent and conscious.
We may think of current AI's like that: as partially functional intelligences.
But in a way they are more, because some of their functions exceed human capacity.
So they are really something new: in some ways less than humans, in some ways more.
https://twitter.com/cHHillee/status/1635790330854526981
https://towardsdatascience.com/the-decontaminated-evaluation...
https://aisnakeoil.substack.com/p/gpt-4-and-professional-ben...
sounds very much like it depends on the definition and the vendor themselve agrees that the current modeling options might not go much further...
A few weeks later I found the exact blog post that ChatGPT copied from, down to the blog article explaining how the exit syscall was required and was intentionally missing from the first step as a pedagogical exercise. But of course ChatGPT won't know that.
If you know assembler, it's much easier and safer writing it yourself. If you don't don't assembler, you're doing stupid and dangerous things and should immediately stop.