💾 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
⬅️ Previous capture (2023-12-28)
-=-=-=-=-=-=-
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
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
It might be handy to annotate the disassembly in SBCL?
#+SBCL (setf sb-ext:*disassemble-annotate* t)
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