Hacker News new | past | comments | ask | show | jobs | submit login
Even if you can't write assembly, you can read disassembly (wordsandbuttons.online)
291 points by signa11 on April 4, 2023 | hide | past | favorite | 129 comments



I had to do clean room disassembly as my first post uni job, documenting a serial line comms protocol for a guy emulating a hospital information system. Naivety led me to burn a week reverse engineering the BIOS jump table for IO processing and the half byte shuffle to pass data out a serial port (this was a pre msdos system, it wrote the byte as nybbles to the serial UART). The actual data structure framing was easy once I got my wind. Fortran rewrite from the assembly from a memory dump.

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...


But it's the arithmetic shift that really drives 'em insane...


I love the allusion to Rocky Horror.


It's not that hard to write assembly. Tedious is the word I would use. Learn how to express the basic operators of C in assembly (functions and parameters with the stack, do/while/if/for, array and struct indexing) and then write everything in pseudo-C and hand translate it to assembly.

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.


Writing is almost never the issue. It's debugging that is the absolute nightmare, because it is so tedious to reconstruct what happened in terms of low-level things. Without names, data structures, functions, human memory quickly becomes overwhelmed.

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 find debugging assembly a breeze IF you have a full trace of all registers (ie in a simulation)


Well, and you remember what all the registers do.

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.


It seems like having a diagram would help a lot, maybe some kind of 2D AutoCAD with a visual representation and their logical relationships? Or a Verilog for software?


> Writing is almost never the issue. It's debugging that is the absolute nightmare, because it is so tedious to reconstruct what happened in terms of low-level things. Without names, data structures, functions, human memory quickly becomes overwhelmed.

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 :)


Yes, now that LLMs have been invented we can finally write programs in something other than assembly language and then allow computers to convert the program into machine language for us.


It's not that hard to write assembly. It is hard to write good assembly. In order to produce maintainable software you need incredible discipline (and probably some advice what that discipline should be).

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.


I was hired to write assembly (68000) so I didn't know any better. I liked developing the discipline and the knowledge of the machine. I still miss it sometimes. But like another comment said, it's tedious, so I wouldn't go back to it.


Great point. The only time I've been required (rather than for fun/hobby purposes) to write assembly was for a controller where we didn't have a C compiler. Like I hinted, I followed the approach of writing in C (including testing it as much as possible on my desktop in C) and then hand-translating precisely because of what you mention -- by default there's no structure. I basically adopted the structures and conventions of the C language and C compilers and imposed them on my assembly code, since I was familiar with those, and know how to make working software using them. You could use another language's conventions. Or you could veer off into the wild, but there be dragons.


do you have any examples of the flowcharts used for asm writing? i also recall someone using an excel spreadsheet when writing for itanium.


https://www.ibiblio.org/apollo/Documents/GeminiMinnickMathFl... (large PDF)

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_...


Wow, that diagram is absolutely wild! Thanks for sharing. Handwritten flowcharts and most of the code is actually math formulas and matrix computations.


I just read that entire page, amazing stuff.


Just literally draw a flowchart of what your program is supposed to do, then translate that flowchart. Something like each box gets its own label, diamonds are just some variation on `jz some-box-label`, etc.

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.


> Writing optimized assembly is a whole other question.

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.


Meta-comment: I really love the expanding explanation hyperlinks in this article. It's a much better alternative (imo) than say, linking in a new tab, or a pop-up, etc.


Me too, I found that aspect of the site design really cool. Just another perk of hand-made personal sites instead of things like medium.

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.


Done! All the expandables have background colors now. https://github.com/akalenuk/wordsandbuttons/commit/22ef6295c... Thanks for the idea!


Background colors are a great idea, I'll do that!


Whereas I didn't realize they were links, and never thought to click on the oddly worded text below the examples. For others like me, the plain text label in a square box below the listings that says stilted things like "The one on the left does" expands when clicked to give a useful explanatory paragraph. Seems like a good approach, but I could have used a better affordance: a cursor change on hover, underlined blue text, even maybe a tool tip that says "click me for the answer you idiot".


I added a note saying that if you pick one of the answers an explanation will appear. Thanks for the feedback!


Good point! I'll think what I can do.


It cannot be toggled back to hide the explanation without refreshing the whole page.


Fixed! Every expandable text can now be toggled back. Thanks for bringing this up!


Nice and useful post BTW. Most programmers seem oblivious about what takes place "under the hood".

A side note: I still plan to read your Geometry for Programmers, now that it is close to being finished. All the best!


Yes, I did the foldable thingies in https://wordsandbuttons.online/programmers_guide_to_polynomi... and https://wordsandbuttons.online/if_i_were_to_invent_a_program...

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.


It may be a dying art today, but up until around 20 years ago there were still PC games coming out written entirely in assembly.

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..


This post stirred up some memories from long ago, when I was trying to understand assembly. I had written a program to test my knowledge, not very long, a couple of pages, and when I thought I was finished, I ran it to see how it worked.

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.


Why is assembly locked into an ocean of cryptic sequence of letters that no respectable dictionary will try to pretend to be a word?

Or is there an assembly that get rid of this gibberish tradition?


I don't think we'd lose much by going to "return" instead of "rts" or "ret", "move" instead of "mov" (M68k even uses "move"), and "jump" instead of "jmp". But it used to matter when machines had memory measured in kilobytes, and now it's convention, and those of us who are used to reading assembly tend to recognise most of the common ways of contracting words that tends to be relatively fixed even across CPU families and so it's unattractive to change it.

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.

[1] https://en.wikipedia.org/wiki/High_Level_Assembly


It's all convention. Personally, I'd like more arithmetic convention. Instead of:

  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.


Except it doesn't scale that well to all of the different operations. How do you distinguish between a signed and an unsigned high multiply? What do you do for operations like count-trailing-zeros or popcount, which don't map to standard operator symbols? How do you distinguish between nontemporal loads, atomic loads, sign-extended byte-to-word loads, etc.?

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.


I had a look at the TI SHARC manual at https://www.analog.com/media/en/dsp-documentation/processor-... to see how they did it. It seems to me that the SHARC assembly instructions would tell you the exact variant with the same precision as the traditional cryptic mnemonics.

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).


> Except it doesn't scale that well to all of the different operations. How do you distinguish between a signed and an unsigned high multiply? What do you do for operations like count-trailing-zeros or popcount, which don't map to standard operator symbols? How do you distinguish between nontemporal loads, atomic loads, sign-extended byte-to-word loads, etc.?

Yes - that's where is falls down :(


Well, this is the kind of thing HLA does (also see my other comment in this thread about a compiler I wrote), but in practice there's little demand - most of the people who write asm are used to the conventions, and few people who write asm write much of it.


This is not much easier to read, this is just on much higher level.

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.


To be fair - the processor only has binary opcodes & operands :)

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.


Why not just use C?


Done that on several occasions. There are plenty of projects (and many languages) that compile to C and hand that on to the systems compiler. As an output format, C actually works very well. It hides the fiddly things like register allocation - things that are already done well by gcc and the like.


I feel it's primarily x86-64 assembly that's so ridiculously hard to read, not least owing to the dozens of extensions and two syntaxes (GNU/AT&T versus Intel syntax). Furthermore, it is a register-memory ISA, and has a flags register which is set as instructions are executed, both of which lead to a lot of implicit behaviour that isn't immediately obvious from reading a given listing of x86-64 assembly.

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...


I feel it's mostly the problem of the first impression. My first encounter with assembly was x86 and so it didn't really bother me ("ah, so that's what it is, okay"), and other assemblies were felt to be basically the same, maybe marginally nicer, maybe marginally worse, but that's it. But I do imagine that if your first foray into assembly started with RISC-V, then looking at x86 would absolutely scare you.

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.


> I feel it's mostly the problem of the first impression.

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.


6502 has too many ways to address the memory (incl all the zero page), it will require quite a few pages not a single one. Indeed the lda/ldx/ldx change the flags unlikely 8086's mov.

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)


There are multiple real-world instruction sets that have a few dozen opcodes at most. Learning the mnemonics and operands for one of these ISAs can be done in an afternoon. Much easier than learning a really complicated language like C++ or Rust.

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’re only one step away from the metal.

You can write about assembly like a poet, at least.


Very true, though there are some instruction sets with literally thousands - like the Intel x86-64. Give me RISC any day.


x86 doesn't have thousands. That's overstating it by quite a bit. The assembler I wrote contains 1095 encodings. It's from ~2004 but 100% complete at the time, so sure new instructions have been added since. But those are encodings. SAL has like 8 encodings, SAR has 12, CMP has 14, etc. These encodings are just different ways to use the same opcode but with different source/target parameters (8/16/32-bit immediates or registers, etc.)

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.


>AMD-only 3DNow

Modern AMD CPUs don't support it either, so it's entirely dead


RISC is probably hard to do for a big desktop chip.

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.


The Saturn assembly language (used on HP calculators, like the HP48) is not too difficult to read, compared to x86, Z80 or ARM.

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


I can't say I like that, but I'll note my first compiler on the Amiga started out as a wrapper around pure assembly, and supported similar arithmetic constructs where the asm generated was trivially predictable. E.g. similar to your example, you could write:

   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"


Ah issue is that some (most?) architectures can't do math without messing with registers such as an accumulator, carry flag, negative flag, etc. While it looks convenient, I'm not sure I'd feel very confident about what happens with the CPU state using higher level syntax like that (especially if you want something more than a trivial addition)


The thing is, it's not "higher level syntax" unless you make it higher level. It's the semantics that matters, and as stated in that case it was entirely deterministic which assembly instructions would get emitted.

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.


A terse notation is useful for grokking code, as the information is spread across less characters that need to be interpreted by the brain.

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.


>A terse notation is useful for grokking code

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.


I am not disagreeing with you, but the width of “tokens” we parse does matter — it’s much harder to spot a typo in a long word than in a short one, and one tends to read assembly by pattern matching on the instruction word, so I believe better, but still relatively short names would be the optimum.

(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)


Same reason people use APL inspired languages.


I tried to make a readable assembly once: https://github.com/akalenuk/milasm

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


Interesting project, thanks for sharing.

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".


Ah yes, but the whole point of the project is to go away from this:

    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.


You can just write your own assembler if you wish to. It's much easier than writing a full fledged compiler for a higher language, but that obviously doesn't make it easy.

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.


Because it's its own language.

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.


So, like cp, ls, mv, rm, dd, df, ps, cc, etc.?


Assemblers for DSPs go so far as having arithmetic operators like "+" and "=" in their vocabulary, as well as allowing things like hierarchical braces as syntactic sugar if you want them. But you are intended to write assembly for them, so making it almost C-like has a lot of benefits.

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.


> Or is there an assembly that get rid of this gibberish tradition?

C


C is not all that close to the machine. It mostly works in an analogous way, but there have been plenty of times I've been frustrated by having to write a lot of complex, hard-to-reason-about C when the equivalent assembly is like 4-5 instructions. Types especially introduce a bunch of extraneous garbage that isn't at all related to what the machine does or what the program is trying to accomplish. Then, if you look at the generated code, it's ugly and not easy to follow at all, filled with unnecessary stuff.


Actually I don't recal the name any longer, but TI used to have a DSP Assembler that was pretty simplified C, something like for the expressions

    dest = src1 op src2;
Only basic control flow, no structured loops and so forth


The use of store/recall for memory was eye opening for me in nostalgia posts on old Wang computers.


On my first job, I wrote a Sha-1 implementation in 8088 assembly. The platform was a smart card, but there were a few additional proprietary instructions. So, a real chance to beat the compiler, who didn't know those instructions.

Ten 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.


I don't expect people to write assembly code, but I do expect them to at least understand it. So I've put an 'explain what this code dump does' part in my team's interview process.

(This obviously wouldn't apply to web dev roles.)


Oh, interesting, could you share an example? It could be anything from strlen() to some SIMD optimized super loop of doom, so I'm curious what it is like


It’s nothing too complex really, they’d be expected to know what a memory access looks like and the branching mnemonics. So they’re given the code and explain their understanding of it and maybe try reverse-engineer it back into C/C++ etc.

SIMD might be too masochistic!


reasoning about SIMD code on an interview is a really high ask for a non-specialized position. I'd not even know all of the instructions w/o a reference. OTOH Assembly (w/o the vector instructions) is generally like a very simple C [I'd exclude some of the memory barrier on weaker memory modes as well]


> Most of the time we read disassembly only to answer one simple question: does the compiler do what we expect it to do?

Actually, most of the time I read disassembly to figure out why something crashed. It's a useful skill in many contexts!


Most of the time I read disassembly only to figure out what kind of user-hostile BS Apple is pulling lately.


Usually my reverse-engineering tools give me pseudocode


Well, at least 30 years ago I could write assembly. But a 188 KB driver for a camera sensor is something that is still too big for me to use as a basis for reverse engineering.


It's all fun and games until you single-step into something like strchr: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86...

(And that's the source: remember the disassembly will lack comments and macros.)


Given the popularity of Compiler Explorer there must be lots of people out there reading assembly language part time!

I’ve personally found it incredibly helpful in trying to understand exactly what AVX512 code compilers can be coaxed into generating. Thanks Matt!


There's some flag you can give the JVM which will make it dump out the assembly for JITted code. That is an enlightening read. There is all sorts of weird stuff going on in there to do with safepoints, dynamic deoptimisation, etc.


You can also use JITwatch to do it from a GUI, similarly to what godbolt does (showing corresponding byte code)!


I had a few year of expericene with AVR assembly (x86 was a pain)... but getting younger people to learn it is a pain.

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?


If you just want to teach assembly (assuming this includes the datapath/control units, the CPU pipeline, hazards, etc) today, you can't go wrong with Patterson and Hennessy's Computer Organisation and Design. Pick your version: MIPS[1], ARM[2], or RISC-V[3].

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...


At my school we had to write a base64 encoder/decoder in x86 assembly last year. Then we moved on and wrote a small shell in C. We had a lot of theory but the practical parts are what made it work imo. When working on the C stuff i often used compiler explorer to see which assembly is generated.


ARMv8 and RISC-V are both pretty easy and not full of ugliness like x86. Binutils as is good enough and pretty much available anywhere, but gcc or clang will work just fine too. GDB and LLDB work fine for debugging for a hobbyist like me, but I'm sure there are some IDEs that can give a nicer experience.

Essential: https://developer.arm.com/documentation/ddi0487/gb/

https://riscv.org/technical/specifications/


Good principle. Writing assembly is painful and very rarely necessary. Automated RE tools like Ghidra can do a lot, but there have been more circumstances where I've benefited from reading assembly than writing it. Especially on "embedded" systems, or little bits of reverse engineering of existing systems.


I like Binary Ninja for it's IL output, at least you get the option to zoom in and out depending on how fine you need to step through a section.


I began my journey learing AArch64 assembly by writing little C programs on my Raspberry Pi and examining the compiler-generated assembly. This gave me some vital clues, not only in terms of the instructions to use, but also conventions like how the stack is handled and how system calls are done (on Linux, anyway).


Really liked this post and overall the entire site’s content seems great; especially like the simple way of expanding additional detail — though maybe that could be done with more semantic <details> and <summary> tags rather than <div>? — but I really wish it was more mobile friendly… might be the arbitrary initial-scale value, but not sure at a glance… on my iPhone, at least, the page loads with a good portion of the content overflowing offscreen and I need to manually pinch to zoom and drag all over to read the content.


Back when I had to use MSVC++ instead of stepping through using the debugger I would just open the dissambler and find the JMP instruction, saved a ton of time when dealing with templated C++.


This is about 90% of how I use Common Lisp’s DISASSEMBLE function: checking to see if my optimization declarations are having the desired effect.


AI will do this for us, no?


not necessarily. also need to stop calling it AI because it is not. It works on existing knowledge. Anything new or unfamiliar it will not be able to help you.


Machine learning and AI have been used interchangeably for a while now, probably because the latter has better marketing value.


"What has been, is what will be, and what has been done is what will be done, and there is nothing new under the sun."

I agree with the bible on this. There nothing "new", all humans do is "working on existing knowledge".


How does that level set with scientific discoveries and paradigm shifts? Knowledge is scaffolded upon itself - that doesn't mean it is not reaching new heights. The author of that quote wouldn't recognize germ theory or understand powered flight and interstellar probes.


the quote continues (loose translation):

"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.


That doesn’t take into account emergent properties, though.

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.


Keep in mind that we don't really know who wrote the Bible.

It's not impossible they may have known things that we don't.


Sure, but how much of that existing knowledge has been written down and published somewhere so it can be scrapped and fed into an AI?

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.


The user with the Twitter handle tianqi observed domain expertise can find the limit to A.I. knowledge by stepping enough of a few prompts.


discovery and curiosity is essential for knowledge, questions is the mother of all knowledge. coming up with answers is easy, but knowing what questions to ask is what will broaden our minds. If we kept working on existing knowledge we wouldn't have evolved.


i kind of disagree.

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.


Knowledge != intelligence. So many people tend to conveniently forget this fact these days. You know how addition works, this fact alone does not make you intelligent.

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.


Knowledge would be memorising a big table of additions.

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?


So did I just write an intelligent program:

  T add(T a, T b) {
    return a + b;
  }
And let’s make T BigInteger for now.


For some value of intelligent yes.

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.


There is nothing new or unfamiliar required to generate an explanation of what a dozen assembly instructions do, of all the tasks in the world this is one that definitely should be doable by a glorified pattern-recognizer.


> Anything new or unfamiliar it will not be able to help you.

Isn't this being disputed for GPT-4 [1]?

[1] https://arxiv.org/abs/2303.12712


Sorry, but you can get emergent behavior that LOOKS intelligent anywhere, and the only way you cant know is if all you get is a single way to observe it.

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.


If there is such emergent behavior that looks intelligent, how can you tell it apart from human intelligence? Devising a test that can answer this is an area of active research. Let's say for instance that it can come up with a novel mathematical proof that was not part of its training data - would you accept that as evidence towards AGI?


I'm pretty sure engineers at OpenAI working on GPT-4 could very much demystify this entire discussion, but they choose not to. They didn't find out how to do AGI, they found a way to get a lot of people very very excited by employing a veil of secrecy and mystery. Its a program. You can see what it does, and there is a possibility to see what it was trained on. "Open"AI just chooses to hide that. If it was really that smart, OpenAI would have no issue publishing some more informationn on it.


Agreed.

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?


I would accept that as a proof of AI, not AGI. Provided it can do it repeatably and reliably. Hallucinating a proof that's 90% time wrong is just a parrot.

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.


If I showed an AI a picture of a duck. They could remember that.

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.


Another analogy is not to a child but to a part of the brain.

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.


Equally there’s evidence that GPT-4 only excels on tests that happened to appear in it train data (presumably via accidental contamination). As has been demonstrated by it strong ability to solve tests written before its data cutoff date (Sep 2021), but struggles with equivalent tests written after that date.

https://twitter.com/cHHillee/status/1635790330854526981

https://towardsdatascience.com/the-decontaminated-evaluation...

https://aisnakeoil.substack.com/p/gpt-4-and-professional-ben...


> we discuss the challenges ahead for advancing towards deeper and more comprehensive versions of AGI, including the possible need for pursuing a new paradigm that moves beyond next-word prediction

sounds very much like it depends on the definition and the vendor themselve agrees that the current modeling options might not go much further...


Instead of "AI," I am thinking we should call it "last year's internet."


A discrete cosine transform of last year's internet.


Not all of last year's internet of original ideas have had Monte Carlo complexity paths fuzzed and rebased for something ummm-imaginably different using A.I.


Don’t assert your personal philosophical opinion of something as nebulous as “intelligence” as absolute fact in an attempt to nerd snipe someone on Hacker News. Utterly cringey and a weird thing to be pedantic about given that you very obviously knew what OP was talking about.


I tried asking ChatGPT to do this a couple months ago and the results were not very impressive.


I asked it for Hello World and it got that wrong. It left out the exit syscall required at the end, which meant the CPU would continue executing until it hit a seg fault.

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.


Yes. But you will have to disassembly the AI to see if it does this correctly :-P




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: