💾 Archived View for thrig.me › blog › 2023 › 11 › 16 › assembly.gmi captured on 2024-05-10 at 11:15:22. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-12-28)

-=-=-=-=-=-=-

Assembly

One debate is whether "web assembly" (WASM) is assembly at all. It strikes me more as a "web virtual machine" or webvm (VM), given there is a bytecode and implementations that apparently interpret rather than compile. WASM uses a verb (assemble) for what should be a noun (virtual machine)? Naming things is hard.

    MOV [BP], AL        ; actually a copy

Maybe "assembly" was picked for marketing reasons? There could be positive connotations—fast! no bloat!—versus negatives with a "virtual machine": how long is that Java VM going to take to get itself up off disk? (Upwards of 45 seconds, in one case.) Anyways, lots of things compile to a virtual machine with opcodes and what. It's a pretty common pattern.

    $ perl -MO=Concise,-exec -E 'for my $i (1..5) { say $i }'
    1  <0> enter v
    2  <;> nextstate(main 2 -e:1) v:%,us,{,fea=15
    3  <0> pushmark s
    4  <{body}gt; const(IV 1) s
    5  <{body}gt; const(IV 5) s
    6  <{> enteriter(next->b last->e redo->7)[$i:3,6] vKS/LVINTRO
    c  <0> iter s
    d  <|> and(other->7) vK/1
    7      <;> nextstate(main 5 -e:1) v:%,us,fea=15
    8      <0> pushmark s
    9      <0> padsv[$i:3,6] s
    a      <@> say vK
    b      <0> unstack v
               goto c
    e  <2> leaveloop vK/2
    f  <@> leave[1 ref] vKP/REFC
    -e syntax OK

https://metacpan.org/pod/B::Concise

So what is assembly? Some expect there to be not much difference between the assembly and the underlying machine code. Humans tend to be bad at memorizing and using numbers such as 884600 correctly. One obvious solution would be to have various symbols that correspond, more or less, with the numbers or really bit patterns involved.

For the following you'll need NASM installed, which can usually be found in a ports or package system.

    $ cat simple.asm
    BITS 16
    mov [bp], al
    $ nasm simple.asm
    $ ndisasm -b 16 simple
    00000000  884600            mov [bp+0x0],al

Notice how the input "mov [bp], al" is not exactly the same as what was disassembled "mov [bp+0x0],al". Pretty close. Sometimes this input to output matching can be a lot worse depending on how complicated the assembler is, or maybe the underlying machine will need to unroll some instruction it needs to do four times because there is not native means to repeat the instruction four times. In this case the assembly input "FOO 4" might turn into "FOO;FOO;FOO;FOO" or really the bit pattern that FOO corresponds to four times. Maybe 1010000000 1010000000 1010000000 1010000000 or something like that. (And who knows what modern CPU do behind the scenes with branch prediction and whatnot.)

    00000000  884600            mov [bp+0x0],al
    00000003  886600            mov [bp+0x0],ah
    00000006  894600            mov [bp+0x0],ax
    00000009  885E00            mov [bp+0x0],bl
    0000000C  887E00            mov [bp+0x0],bh
    0000000F  895E00            mov [bp+0x0],bx

Assembly can greatly benefit from good comments. Why is AL being copied into whatever it is BP points at, or why is FOO called four times. Maybe multiple FOO busy wait for some bit of hardware to finish something, and there no better instruction for that than FOO, because it has the fewest side-effects. That sort of information is good to put in a comment.

Here's some maybe confusing instructions.

    $ ndisasm jmpjmp
    00000000  31C0              xor ax,ax
    00000002  0403              add al,0x3
    00000004  EB0E              jmp short 0x14
    00000006  7468              jz 0x70
    00000008  6973206973        imul si,[bp+di+0x20],word 0x7369
    0000000D  206120            and [bx+di+0x20],ah
    00000010  7465              jz 0x77
    00000012  7374              jnc 0x88
    00000014  2C01              sub al,0x1
    00000016  EBE8              jmp short 0x0

What is going on here? Nothing much, but there's a string in the middle of the code that the disassembler treats as operations. There may be utility in valid assembly that also is printable ASCII. (Usually it's for nefarious purposes.)

    $ hexdump -C jmpjmp
    00000000  31 c0 04 03 eb 0e 74 68  69 73 20 69 73 20 61 20  |1.....this is a |
    00000010  74 65 73 74 2c 01 eb e8                           |test,...|
    00000018
    $ cat jmpjmp.asm
    BITS 16
    foo:
            xor ax, ax
            add al, 3
            jmp bar
    db 'this is a test'
    bar:
            sub al, 1
            jmp foo

jmpjmp.asm

Note how the labels "foo" and "bar" (good for humans) got turned into numbers (good for computers) for the JMP. Humans can deal with the numbers, especially if the system is less complicated than a lot of modern CPUs are, and the system is designed for humans to deal with the assembly, which a lot of modern CPUs aren't.

Speaking of modern CPUs, here's an Intel whoopsie.

https://lock.cmpxchg8b.com/reptar.html

And since this is a computer programming posting, a factorial example is an obligatory gesture.

    $ cat asm.lisp
    (defun factorial (n)
      (declare (optimize (speed 3) (safety 0) (debug 0)))
      (loop for result = 1 then (* result i)
            for i from 2 to n
            finally (return result)))
    (disassemble #'factorial)
    $ sbcl --script asm.lisp
    ; disassembly for FACTORIAL
    ; Size: 113 bytes. Origin: #x224AB067                         ; FACTORIAL
    ; 67:       B817010020       MOV EAX, #x20000117              ; NIL
    ; 6C:       488955F8         MOV [RBP-8], RDX
    ; 70:       BB04000000       MOV EBX, 4
    ; 75:       B802000000       MOV EAX, 2
    ; 7A:       EB36             JMP L1
    ; 7C:       0F1F4000         NOP
    ; 80: L0:   48895DF0         MOV [RBP-16], RBX
    ; 84:       488BD0           MOV RDX, RAX
    ; 87:       488BFB           MOV RDI, RBX
    ; 8A:       FF142558060020   CALL [#x20000658]                ; #x21A00FF0: GENERIC-*
    ; 91:       488B5DF0         MOV RBX, [RBP-16]
    ; 95:       488BC2           MOV RAX, RDX
    ; 98:       488945E8         MOV [RBP-24], RAX
    ; 9C:       BF02000000       MOV EDI, 2
    ; A1:       488BD3           MOV RDX, RBX
    ; A4:       FF142548060020   CALL [#x20000648]                ; #x21A00F10: GENERIC-+
    ; AB:       488B45E8         MOV RAX, [RBP-24]
    ; AF:       488BDA           MOV RBX, RDX
    ; B2: L1:   48895DF0         MOV [RBP-16], RBX
    ; B6:       488945E8         MOV [RBP-24], RAX
    ; BA:       488B7DF8         MOV RDI, [RBP-8]
    ; BE:       488BD3           MOV RDX, RBX
    ; C1:       FF142570060020   CALL [#x20000670]                ; #x21A01110: GENERIC->
    ; C8:       488B45E8         MOV RAX, [RBP-24]
    ; CC:       488B5DF0         MOV RBX, [RBP-16]
    ; D0:       7EAE             JLE L0
    ; D2:       488BD0           MOV RDX, RAX
    ; D5:       C9               LEAVE
    ; D6:       F8               CLC
    ; D7:       C3               RET

asm.lisp

It might be handy to annotate the disassembly in SBCL?

    #+SBCL
    (setf sb-ext:*disassemble-annotate* t)

Assembly Downsides

Some knowledge of assembly and how the computer goes about things might be good to know. However, assembly is not without downsides, notably the general lack of a stdlib: where is strlen? or how do you print the contents of a register as a number? signed, or unsigned? At least some bloat might be good to have, to avoid having to write it all yourself. Maybe start with a well-commented operating system and build off of that?

https://mikeos.sourceforge.net/

Higher level languages have been blamed for a five-fold increase in programming productivity. This means both good and bad code is produced that much more rapidly. Whether this is a net benefit remains to be seen.

https://82mhz.net/posts/2023/11/why-whatsapp-is-making-me-anxious/

Another downside is assembly tends not to be portable. Got an ARM? All that AMD64 assembly you wrote might not be so useful now. Hence the Linux kernel being 2% assembly and the rest not: for speed, and little portability shims. This is probably the same problem that unikernels have: difficult to setup, expertise required, hard to get speed gains.

tags #asm #lisp #perl