Difference between revisions of "Getting Started"
(→.COM file defaults) |
|||
(51 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
− | You' | + | == Words of warning == |
+ | |||
+ | Sizecoding assumes a basic level of assembler knowledge. You should have at least a few regular (non-optimized) assembler programs under your belt before you attempt sizecoding. Also, don't assume sizecoding is ''normal'' -- shaving bytes is a black art that should be kept far, far away from normal programming targets. People sizecode for fun, not profit! | ||
+ | |||
+ | == Tools and Workflows == | ||
+ | |||
+ | Most people use NASM (it's free) and DOSBox for testing. A simple workflow: | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | nasm main.asm -fbin -o main.com | ||
+ | ndisasm -o100h main.com | ||
+ | dir main.com | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Some crazy sizecoders like writing tinyprogs on very old hardware. For that, a86 is a good choice for an assembler, as it assembles source directly to a .COM file in one pass. Even on the first 8086 systems, you can use debug.com to check the assembly and size: | ||
+ | |||
+ | <syntaxhighlight> | ||
+ | a86 main.asm | ||
+ | debug main.com | ||
+ | dir main.com | ||
+ | </syntaxhighlight> | ||
== Know your environment == | == Know your environment == | ||
+ | |||
+ | === A simple graphics mode === | ||
+ | |||
+ | Most sizecoders choose to write to mode 13h, a chunky 320x200 graphics mode located at segment A000:0000. Each byte is a pixel, and the graphics buffer is linear, so it is extremely easy to program for. Because it is contained to a single segment, you can be sloppy, as overwriting or underwriting the offset value won't damage anything. The [https://en.wikipedia.org/wiki/Video_Graphics_Array#/media/File:VGA_palette_with_black_borders.svg default palette] leaves a little to be desired, so if you have room, you may want to change it. | ||
=== .COM file defaults === | === .COM file defaults === | ||
+ | |||
+ | Knowing what register values are initialized at program start can save you the trouble of having to set them in your code. On most (but not all) DOS environments, the following registers have these default values: | ||
+ | |||
+ | <pre> | ||
+ | AX=0000 | ||
+ | BX=0000 | ||
+ | CX=00FF | ||
+ | DX=CS | ||
+ | SI=0100 | ||
+ | DI=FFFE | ||
+ | BP=09xx | ||
+ | SP=FFFC (DOS child process) or FFFE (the default) | ||
+ | |||
+ | flags=xx02 | ||
+ | </pre> | ||
+ | |||
+ | (For a full list of values per specific version of DOS, consult http://www.fysnet.net/yourhelp.htm) | ||
+ | |||
+ | Because .COM files only support 64K executables, <code>DS</code>, <code>ES</code>, and <code>SS</code> are all set to the same value as <code>CS</code>. The rest can't be counted on for any specific value, except that <code>BP</code> is mostly 09??h so you can usually count on the high byte being <code>09h</code>. | ||
+ | |||
+ | Usually, the top stack contains <code>0000h</code>. That allows for using the one byte instruction <code>RET</code> to exit your program, since at location <code>CS:0000h</code> there is the value <code>20CDh</code> (= <code>int 20h</code>). See [https://en.wikipedia.org/wiki/Program_Segment_Prefix Program Segment Prefix]. | ||
+ | |||
+ | Different DOS versions may differ slightly in the above. A full chart exists at http://www.fysnet.net/yourhelp.htm to help you check if your specific target has different defaults than the standard settings above. | ||
+ | |||
+ | === Boot sector defaults === | ||
+ | |||
+ | Boot sector tinyprogs are occasionally explored, but the BIOS changes every register value as it executes before the boot sequence, so there's not much to count on other than what occurs directly before execution of the boot sector: | ||
+ | * The boot sector is loaded at 0000:7C00 | ||
+ | * DL holds the drive number that was booted from, so if booted from a floppy disk in drive A:, it will be 00 | ||
+ | * The stack pointer is 512 bytes beyond the end of the boot sector, so SP is likely 7E00h | ||
+ | |||
+ | This is why most sizecoders target .COM files, and is also why [https://github.com/nanochess/Toledo-Atomchess Toledo Atomchess] is 9 bytes larger if loaded from boot sector than from a .COM file -- it has to spend bytes setting things up like a .COM file for the rest of the code to work. | ||
+ | |||
+ | If you still want to target boot sectors, your reward is 510 bytes available for program code (the last two bytes must be 0x55, 0xAA to be [http://thestarman.pcministry.com/asm/mbr/PartTables.htm recognized as a boot sector]). | ||
=== 1-byte opcodes === | === 1-byte opcodes === | ||
+ | The 80x86 family was originally a [https://en.wikipedia.org/wiki/Complex_instruction_set_computing CISC] design, a design philosophy that attempts to create instructions that perform multiple steps. As such, there are some single instructions that perform complex actions, such as the string opcodes (<code>LODS</code>, <code>MOVS</code>, <code>CMPS</code>, and <code>SCAS</code>). In sizecoding, you are trying to perform as much work in as little space as possible, so it is helpful to know (or memorize!) every 1-byte instruction in the 80x86 family. Here's a handy chart (segments and prefixes omitted): | ||
− | == | + | {| class="wikitable sortable" |
+ | |- | ||
+ | ! Opcode !! Mnemonic !! Arch !! Description !! Notes | ||
+ | |- | ||
+ | | 37 || AAA || || ASCII adjust AL (carry into AH) after addition | ||
+ | |- | ||
+ | |3F||AAS || ||ASCII adjust AL (borrow from AH) after subtraction | ||
+ | |- | ||
+ | |98||CBW || ||Convert byte into word (AH = top bit of AL) | ||
+ | |- | ||
+ | |99||CDQ ||80386+||Convert dword to qword (EDX = top bit of EAX) | ||
+ | |- | ||
+ | |F8||CLC || ||Clear carry flag | ||
+ | |- | ||
+ | |FC||CLD || ||Clear direction flag so SI and DI will increment | ||
+ | |- | ||
+ | |FA||CLI || ||Clear interrupt enable flag; interrupts disabled | ||
+ | |- | ||
+ | |F5||CMC || ||Complement carry flag | ||
+ | |- | ||
+ | |A6||CMPS mb,mb || ||Compare bytes [SI] - ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |A7||CMPS mv,mv || ||Compare words [SI] - ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |A6||CMPSB || ||Compare bytes DS:[SI] - ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |A7||CMPSD ||80386+||Compare dwords DS:[SI] - ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |A7||CMPSW || ||Compare words DS:[SI] - ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |99||CWD || ||Convert word to doubleword (DX = top bit of AX) | ||
+ | |- | ||
+ | |98||CWDE ||80386+||Sign-extend word AX to doubleword EAX | ||
+ | |- | ||
+ | |27||DAA || ||Decimal adjust AL after addition | ||
+ | |- | ||
+ | |2F||DAS || ||Decimal adjust AL after subtraction | ||
+ | |- | ||
+ | |F4||HLT || ||Halt||Resumes operation if an interrupt occurs; could use this for pacing effects that run too fast | ||
+ | |- | ||
+ | |EC||IN AL,DX || ||Input byte from port DX into AL | ||
+ | |- | ||
+ | |ED||IN eAX,DX || ||Input word from port DX into eAX | ||
+ | |- | ||
+ | |6C||INS rmb,DX ||80186+||Input byte from port DX into [DI], advance DI | ||
+ | |- | ||
+ | |6D||INS rmv,DX ||80186+||Input word from port DX into [DI], advance DI | ||
+ | |- | ||
+ | |6C||INSB ||80186+||Input byte from port DX into ES:[DI], advance DI | ||
+ | |- | ||
+ | |6D||INSD ||80386+||Input dword from port DX into ES:[DI], advance DI | ||
+ | |- | ||
+ | |6D||INSW ||80186+||Input word from port DX into ES:[DI], advance DI | ||
+ | |- | ||
+ | |CC||INT 3 || ||Interrupt 3 (trap to debugger)||If performing very many CALLs to a single procedure, could make it INT 3 | ||
+ | |- | ||
+ | |CE||INTO || ||Interrupt 4 if overflow flag is 1 | ||
+ | |- | ||
+ | |CF||IRET || ||Interrupt return (far return and pop flags) | ||
+ | |- | ||
+ | |CF||IRETD ||80386+||Interrupt return (pop EIP, ECS, Eflags) | ||
+ | |- | ||
+ | |9F||LAHF || ||Load: AH = flags SF ZF xx AF xx PF xx CF | ||
+ | |- | ||
+ | |C9||LEAVE ||80186+||Set SP to BP, then POP BP (reverses previous ENTER) | ||
+ | |- | ||
+ | |AC||LODS mb || ||Load byte [SI] into AL, advance SI | ||
+ | |- | ||
+ | |AD||LODS mv || ||Load word [SI] into eAX, advance SI | ||
+ | |- | ||
+ | |AC||LODSB || ||Load byte [SI] into AL, advance SI | ||
+ | |- | ||
+ | |AD||LODSD ||80386+||Load dword [SI] into EAX, advance SI | ||
+ | |- | ||
+ | |AD||LODSW || ||Load word [SI] into AX, advance SI | ||
+ | |- | ||
+ | |A4||MOVS mb,mb || ||Move byte [SI] to ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |A5||MOVS mv,mv || ||Move word [SI] to ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |A4||MOVSB || ||Move byte DS:[SI] to ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |A5||MOVSD ||80386+||Move dword DS:[SI] to ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |A5||MOVSW || ||Move word DS:[SI] to ES:[DI], advance SI,DI | ||
+ | |- | ||
+ | |90||NOP || ||No Operation | ||
+ | |- | ||
+ | |EE||OUT DX,AL || ||Output byte AL to port number DX | ||
+ | |- | ||
+ | |EF||OUT DX,eAX || ||Output word eAX to port number DX | ||
+ | |- | ||
+ | |6E||OUTS DX,rmb ||80186+||Output byte [SI] to port number DX, advance SI | ||
+ | |- | ||
+ | |6F||OUTS DX,rmv ||80186+||Output word [SI] to port number DX, advance SI | ||
+ | |- | ||
+ | |6E||OUTSB ||80186+||Output byte DS:[SI] to port number DX, advance SI | ||
+ | |- | ||
+ | |6F||OUTSD ||80386+||Output dword DS:[SI] to port number DX, advance SI | ||
+ | |- | ||
+ | |6F||OUTSW ||80186+||Output word DS:[SI] to port number DX, advance SI | ||
+ | |- | ||
+ | |1F||POP DS || ||Set DS to top of stack, increment SP by 2 | ||
+ | |- | ||
+ | |07||POP ES || ||Set ES to top of stack, increment SP by 2 | ||
+ | |- | ||
+ | |17||POP SS || ||Set SS to top of stack, increment SP by 2 | ||
+ | |- | ||
+ | |61||POPA ||80186+||Pop DI,SI,BP,x ,BX,DX,CX,AX (SP value is ignored) | ||
+ | |- | ||
+ | |61||POPAD ||80386+||Pop EDI,ESI,EBP,x,EBX,EDX,ECX,EAX (ESP ign.) | ||
+ | |- | ||
+ | |9D||POPF || ||Set flags register to top of stack, increment SP by 2 | ||
+ | |- | ||
+ | |9D||POPFD ||80386+||Set eflags reg to top of stack, incr SP by 2 | ||
+ | |- | ||
+ | |0E||PUSH CS || ||Set [SP-2] to CS, then decrement SP by 2 | ||
+ | |- | ||
+ | |1E||PUSH DS || ||Set [SP-2] to DS, then decrement SP by 2 | ||
+ | |- | ||
+ | |06||PUSH ES || ||Set [SP-2] to ES, then decrement SP by 2 | ||
+ | |- | ||
+ | |16||PUSH SS || ||Set [SP-2] to SS, then decrement SP by 2 | ||
+ | |- | ||
+ | |60||PUSHA ||80186+||Push AX,CX,DX,BX,original SP,BP,SI,DI | ||
+ | |- | ||
+ | |60||PUSHAD ||80386+||Push EAX,ECX,EDX,EBX,original ESP,EBP,ESI,EDI | ||
+ | |- | ||
+ | |9C||PUSHF || ||Set [SP-2] to flags register, then decrement SP by 2 | ||
+ | |- | ||
+ | |9C||PUSHFD ||80386+||Set [SP-4] to eflags reg, then decr SP by 4 | ||
+ | |- | ||
+ | |C3||RET || ||Return to caller (near or far, depending on PROC) | ||
+ | |- | ||
+ | |CB||RETF || ||Return to far caller (pop offset, then seg) | ||
+ | |- | ||
+ | |C3||RETN || ||Return to near caller (pop offset only) | ||
+ | |- | ||
+ | |9E||SAHF || ||Store AH into flags SF ZF xx AF xx PF xx CF | ||
+ | |- | ||
+ | |AE||SCAS mb || ||Compare bytes AL - ES:[DI], advance DI | ||
+ | |- | ||
+ | |AF||SCAS mv || ||Compare words eAX - ES:[DI], advance DI | ||
+ | |- | ||
+ | |AE||SCASB || ||Compare bytes AL - ES:[DI], advance DI | ||
+ | |- | ||
+ | |AF||SCASD ||80386+||Compare dwords EAX - ES:[DI], advance DI | ||
+ | |- | ||
+ | |AF||SCASW || ||Compare words AX - ES:[DI], advance DI | ||
+ | |- | ||
+ | |36||SS || ||Use SS segment for the following memory reference | ||
+ | |- | ||
+ | |F9||STC || ||Set carry flag | ||
+ | |- | ||
+ | |FD||STD || ||Set direction flag so SI and DI will decrement | ||
+ | |- | ||
+ | |FB||STI || ||Set interrupt enable flag, interrupts enabled | ||
+ | |- | ||
+ | |AA||STOS mb || ||Store AL to byte [DI], advance DI | ||
+ | |- | ||
+ | |AB||STOS mv || ||Store eAX to word [DI], advance DI | ||
+ | |- | ||
+ | |AA||STOSB || ||Store AL to byte ES:[DI], advance DI | ||
+ | |- | ||
+ | |AB||STOSD ||80386+||Store EAX to dword ES:[DI], advance DI | ||
+ | |- | ||
+ | |AB||STOSW || ||Store AX to word ES:[DI], advance DI | ||
+ | |- | ||
+ | |9B||WAIT || ||Wait until floating-point operation is completed | ||
+ | |- | ||
+ | |D7||XLAT || ||Set AL to memory byte DS:[BX + unsigned AL] | ||
+ | |- | ||
+ | |||
+ | |} | ||
+ | |||
+ | Additionally, there are accumulator-optimized (AX) forms of opcodes that are one byte, such as <code>DEC AX</code>, <code>INC AX</code>, and <code>XCHG reg,AX</code>. | ||
+ | |||
+ | == A sample framework == | ||
+ | |||
+ | Want to just dive in and see what happens? Here's a skeleton that sets up Mode 13h, loops until a keypress is detected, then exits. Although it assembles to only 19 bytes, do not consider this fully and finally optimized; your program's structure may allow some of this to be reduced further. But it's a good place to start: | ||
+ | |||
+ | <syntaxhighlight lang=nasm> | ||
+ | org 100h ;specify .COM file | ||
+ | |||
+ | start: | ||
+ | mov al,13h ;AX=0000 at program start | ||
+ | int 10h ;init mode 13h | ||
+ | push word 0A000h ;Requires 80186 or higher to PUSH IMMED | ||
+ | pop es ;ES now points to mode 13h screen segment | ||
+ | |||
+ | mainloop: | ||
+ | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
+ | ;This is where you do your mega-amazing tiny program. | ||
+ | ;Write 8-bit values to A000:0000 to draw some pixels. | ||
+ | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
+ | |||
+ | ;Check for user wanting to leave by pressing ESC | ||
+ | in al,60h ;read whatever is at keyboard port; looking for ESC which is #1 | ||
+ | dec al ;if ESC, AL now 0 | ||
+ | jnz mainloop ;fall through if 0, jump otherwise | ||
+ | mov al,03 ;AX=0000 due to mainloop exit condition | ||
+ | int 10h ;Switch back to text mode as a convenience | ||
+ | ret ;.COM files can exit with RET | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | === How to think like a sizecoder === | ||
+ | |||
+ | This example framework can be shrunk! Think a little bit about how you might do it, then check out the example below: | ||
+ | |||
+ | <syntaxhighlight lang=nasm> | ||
+ | org 100h ;specify .COM file | ||
+ | |||
+ | start: | ||
+ | mov al,13h ;AX=0000 at program start | ||
+ | int 10h ;init mode 13h | ||
+ | les bx,[bx] ;contains 0x9FFF at program start; close enough to 0xA000 ;-) | ||
+ | |||
+ | mainloop: | ||
+ | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
+ | ;This is where you do your mega-amazing tiny program. | ||
+ | ;Write 8-bit values to ES:xxxx to draw some pixels. | ||
+ | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||
+ | |||
+ | ;Check for user wanting to leave by pressing ESC | ||
+ | in al,60h ;read whatever is at keyboard port; looking for ESC which is #1 | ||
+ | dec al ;if ESC, AL now 0 | ||
+ | jnz mainloop ;fall through if 0, jump otherwise | ||
+ | ret ;.COM files can exit with RET | ||
+ | ;Don't care if we set text mode, user can just MODE CO80 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | By casually disregarding the user experience :-) and fudging the start of video memory, our basic framework is now 13 bytes. | ||
== Where to go from here? == | == Where to go from here? == | ||
− | [[Tips]] | + | [[Output#Outputting_in_Textmode_.2880x25.29| Hello World! console output]] shows how to output text. |
+ | |||
+ | [[Techniques|Tips, Tricks, and Techniques]] can help you with ideas on optimizing your next production, or help you design while you're writing it. | ||
− | [[Case Studies]] | + | Some [[Case Studies]] are provided that illustrate and explain some of the choices made when sizecoding. |
− | [[Resources]] | + | Can't find what you need? Check our list of [[Resources|external resources]]. |
Latest revision as of 16:14, 28 June 2022
Contents
Words of warning
Sizecoding assumes a basic level of assembler knowledge. You should have at least a few regular (non-optimized) assembler programs under your belt before you attempt sizecoding. Also, don't assume sizecoding is normal -- shaving bytes is a black art that should be kept far, far away from normal programming targets. People sizecode for fun, not profit!
Tools and Workflows
Most people use NASM (it's free) and DOSBox for testing. A simple workflow:
nasm main.asm -fbin -o main.com
ndisasm -o100h main.com
dir main.com
Some crazy sizecoders like writing tinyprogs on very old hardware. For that, a86 is a good choice for an assembler, as it assembles source directly to a .COM file in one pass. Even on the first 8086 systems, you can use debug.com to check the assembly and size:
a86 main.asm
debug main.com
dir main.com
Know your environment
A simple graphics mode
Most sizecoders choose to write to mode 13h, a chunky 320x200 graphics mode located at segment A000:0000. Each byte is a pixel, and the graphics buffer is linear, so it is extremely easy to program for. Because it is contained to a single segment, you can be sloppy, as overwriting or underwriting the offset value won't damage anything. The default palette leaves a little to be desired, so if you have room, you may want to change it.
.COM file defaults
Knowing what register values are initialized at program start can save you the trouble of having to set them in your code. On most (but not all) DOS environments, the following registers have these default values:
AX=0000 BX=0000 CX=00FF DX=CS SI=0100 DI=FFFE BP=09xx SP=FFFC (DOS child process) or FFFE (the default) flags=xx02
(For a full list of values per specific version of DOS, consult http://www.fysnet.net/yourhelp.htm)
Because .COM files only support 64K executables, DS
, ES
, and SS
are all set to the same value as CS
. The rest can't be counted on for any specific value, except that BP
is mostly 09??h so you can usually count on the high byte being 09h
.
Usually, the top stack contains 0000h
. That allows for using the one byte instruction RET
to exit your program, since at location CS:0000h
there is the value 20CDh
(= int 20h
). See Program Segment Prefix.
Different DOS versions may differ slightly in the above. A full chart exists at http://www.fysnet.net/yourhelp.htm to help you check if your specific target has different defaults than the standard settings above.
Boot sector defaults
Boot sector tinyprogs are occasionally explored, but the BIOS changes every register value as it executes before the boot sequence, so there's not much to count on other than what occurs directly before execution of the boot sector:
- The boot sector is loaded at 0000:7C00
- DL holds the drive number that was booted from, so if booted from a floppy disk in drive A:, it will be 00
- The stack pointer is 512 bytes beyond the end of the boot sector, so SP is likely 7E00h
This is why most sizecoders target .COM files, and is also why Toledo Atomchess is 9 bytes larger if loaded from boot sector than from a .COM file -- it has to spend bytes setting things up like a .COM file for the rest of the code to work.
If you still want to target boot sectors, your reward is 510 bytes available for program code (the last two bytes must be 0x55, 0xAA to be recognized as a boot sector).
1-byte opcodes
The 80x86 family was originally a CISC design, a design philosophy that attempts to create instructions that perform multiple steps. As such, there are some single instructions that perform complex actions, such as the string opcodes (LODS
, MOVS
, CMPS
, and SCAS
). In sizecoding, you are trying to perform as much work in as little space as possible, so it is helpful to know (or memorize!) every 1-byte instruction in the 80x86 family. Here's a handy chart (segments and prefixes omitted):
Opcode | Mnemonic | Arch | Description | Notes |
---|---|---|---|---|
37 | AAA | ASCII adjust AL (carry into AH) after addition | ||
3F | AAS | ASCII adjust AL (borrow from AH) after subtraction | ||
98 | CBW | Convert byte into word (AH = top bit of AL) | ||
99 | CDQ | 80386+ | Convert dword to qword (EDX = top bit of EAX) | |
F8 | CLC | Clear carry flag | ||
FC | CLD | Clear direction flag so SI and DI will increment | ||
FA | CLI | Clear interrupt enable flag; interrupts disabled | ||
F5 | CMC | Complement carry flag | ||
A6 | CMPS mb,mb | Compare bytes [SI] - ES:[DI], advance SI,DI | ||
A7 | CMPS mv,mv | Compare words [SI] - ES:[DI], advance SI,DI | ||
A6 | CMPSB | Compare bytes DS:[SI] - ES:[DI], advance SI,DI | ||
A7 | CMPSD | 80386+ | Compare dwords DS:[SI] - ES:[DI], advance SI,DI | |
A7 | CMPSW | Compare words DS:[SI] - ES:[DI], advance SI,DI | ||
99 | CWD | Convert word to doubleword (DX = top bit of AX) | ||
98 | CWDE | 80386+ | Sign-extend word AX to doubleword EAX | |
27 | DAA | Decimal adjust AL after addition | ||
2F | DAS | Decimal adjust AL after subtraction | ||
F4 | HLT | Halt | Resumes operation if an interrupt occurs; could use this for pacing effects that run too fast | |
EC | IN AL,DX | Input byte from port DX into AL | ||
ED | IN eAX,DX | Input word from port DX into eAX | ||
6C | INS rmb,DX | 80186+ | Input byte from port DX into [DI], advance DI | |
6D | INS rmv,DX | 80186+ | Input word from port DX into [DI], advance DI | |
6C | INSB | 80186+ | Input byte from port DX into ES:[DI], advance DI | |
6D | INSD | 80386+ | Input dword from port DX into ES:[DI], advance DI | |
6D | INSW | 80186+ | Input word from port DX into ES:[DI], advance DI | |
CC | INT 3 | Interrupt 3 (trap to debugger) | If performing very many CALLs to a single procedure, could make it INT 3 | |
CE | INTO | Interrupt 4 if overflow flag is 1 | ||
CF | IRET | Interrupt return (far return and pop flags) | ||
CF | IRETD | 80386+ | Interrupt return (pop EIP, ECS, Eflags) | |
9F | LAHF | Load: AH = flags SF ZF xx AF xx PF xx CF | ||
C9 | LEAVE | 80186+ | Set SP to BP, then POP BP (reverses previous ENTER) | |
AC | LODS mb | Load byte [SI] into AL, advance SI | ||
AD | LODS mv | Load word [SI] into eAX, advance SI | ||
AC | LODSB | Load byte [SI] into AL, advance SI | ||
AD | LODSD | 80386+ | Load dword [SI] into EAX, advance SI | |
AD | LODSW | Load word [SI] into AX, advance SI | ||
A4 | MOVS mb,mb | Move byte [SI] to ES:[DI], advance SI,DI | ||
A5 | MOVS mv,mv | Move word [SI] to ES:[DI], advance SI,DI | ||
A4 | MOVSB | Move byte DS:[SI] to ES:[DI], advance SI,DI | ||
A5 | MOVSD | 80386+ | Move dword DS:[SI] to ES:[DI], advance SI,DI | |
A5 | MOVSW | Move word DS:[SI] to ES:[DI], advance SI,DI | ||
90 | NOP | No Operation | ||
EE | OUT DX,AL | Output byte AL to port number DX | ||
EF | OUT DX,eAX | Output word eAX to port number DX | ||
6E | OUTS DX,rmb | 80186+ | Output byte [SI] to port number DX, advance SI | |
6F | OUTS DX,rmv | 80186+ | Output word [SI] to port number DX, advance SI | |
6E | OUTSB | 80186+ | Output byte DS:[SI] to port number DX, advance SI | |
6F | OUTSD | 80386+ | Output dword DS:[SI] to port number DX, advance SI | |
6F | OUTSW | 80186+ | Output word DS:[SI] to port number DX, advance SI | |
1F | POP DS | Set DS to top of stack, increment SP by 2 | ||
07 | POP ES | Set ES to top of stack, increment SP by 2 | ||
17 | POP SS | Set SS to top of stack, increment SP by 2 | ||
61 | POPA | 80186+ | Pop DI,SI,BP,x ,BX,DX,CX,AX (SP value is ignored) | |
61 | POPAD | 80386+ | Pop EDI,ESI,EBP,x,EBX,EDX,ECX,EAX (ESP ign.) | |
9D | POPF | Set flags register to top of stack, increment SP by 2 | ||
9D | POPFD | 80386+ | Set eflags reg to top of stack, incr SP by 2 | |
0E | PUSH CS | Set [SP-2] to CS, then decrement SP by 2 | ||
1E | PUSH DS | Set [SP-2] to DS, then decrement SP by 2 | ||
06 | PUSH ES | Set [SP-2] to ES, then decrement SP by 2 | ||
16 | PUSH SS | Set [SP-2] to SS, then decrement SP by 2 | ||
60 | PUSHA | 80186+ | Push AX,CX,DX,BX,original SP,BP,SI,DI | |
60 | PUSHAD | 80386+ | Push EAX,ECX,EDX,EBX,original ESP,EBP,ESI,EDI | |
9C | PUSHF | Set [SP-2] to flags register, then decrement SP by 2 | ||
9C | PUSHFD | 80386+ | Set [SP-4] to eflags reg, then decr SP by 4 | |
C3 | RET | Return to caller (near or far, depending on PROC) | ||
CB | RETF | Return to far caller (pop offset, then seg) | ||
C3 | RETN | Return to near caller (pop offset only) | ||
9E | SAHF | Store AH into flags SF ZF xx AF xx PF xx CF | ||
AE | SCAS mb | Compare bytes AL - ES:[DI], advance DI | ||
AF | SCAS mv | Compare words eAX - ES:[DI], advance DI | ||
AE | SCASB | Compare bytes AL - ES:[DI], advance DI | ||
AF | SCASD | 80386+ | Compare dwords EAX - ES:[DI], advance DI | |
AF | SCASW | Compare words AX - ES:[DI], advance DI | ||
36 | SS | Use SS segment for the following memory reference | ||
F9 | STC | Set carry flag | ||
FD | STD | Set direction flag so SI and DI will decrement | ||
FB | STI | Set interrupt enable flag, interrupts enabled | ||
AA | STOS mb | Store AL to byte [DI], advance DI | ||
AB | STOS mv | Store eAX to word [DI], advance DI | ||
AA | STOSB | Store AL to byte ES:[DI], advance DI | ||
AB | STOSD | 80386+ | Store EAX to dword ES:[DI], advance DI | |
AB | STOSW | Store AX to word ES:[DI], advance DI | ||
9B | WAIT | Wait until floating-point operation is completed | ||
D7 | XLAT | Set AL to memory byte DS:[BX + unsigned AL] |
Additionally, there are accumulator-optimized (AX) forms of opcodes that are one byte, such as DEC AX
, INC AX
, and XCHG reg,AX
.
A sample framework
Want to just dive in and see what happens? Here's a skeleton that sets up Mode 13h, loops until a keypress is detected, then exits. Although it assembles to only 19 bytes, do not consider this fully and finally optimized; your program's structure may allow some of this to be reduced further. But it's a good place to start:
org 100h ;specify .COM file
start:
mov al,13h ;AX=0000 at program start
int 10h ;init mode 13h
push word 0A000h ;Requires 80186 or higher to PUSH IMMED
pop es ;ES now points to mode 13h screen segment
mainloop:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;This is where you do your mega-amazing tiny program.
;Write 8-bit values to A000:0000 to draw some pixels.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Check for user wanting to leave by pressing ESC
in al,60h ;read whatever is at keyboard port; looking for ESC which is #1
dec al ;if ESC, AL now 0
jnz mainloop ;fall through if 0, jump otherwise
mov al,03 ;AX=0000 due to mainloop exit condition
int 10h ;Switch back to text mode as a convenience
ret ;.COM files can exit with RET
How to think like a sizecoder
This example framework can be shrunk! Think a little bit about how you might do it, then check out the example below:
org 100h ;specify .COM file
start:
mov al,13h ;AX=0000 at program start
int 10h ;init mode 13h
les bx,[bx] ;contains 0x9FFF at program start; close enough to 0xA000 ;-)
mainloop:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;This is where you do your mega-amazing tiny program.
;Write 8-bit values to ES:xxxx to draw some pixels.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;Check for user wanting to leave by pressing ESC
in al,60h ;read whatever is at keyboard port; looking for ESC which is #1
dec al ;if ESC, AL now 0
jnz mainloop ;fall through if 0, jump otherwise
ret ;.COM files can exit with RET
;Don't care if we set text mode, user can just MODE CO80
By casually disregarding the user experience :-) and fudging the start of video memory, our basic framework is now 13 bytes.
Where to go from here?
Hello World! console output shows how to output text.
Tips, Tricks, and Techniques can help you with ideas on optimizing your next production, or help you design while you're writing it.
Some Case Studies are provided that illustrate and explain some of the choices made when sizecoding.
Can't find what you need? Check our list of external resources.