An introduction to 3BA

3BINS Assembly is designed to be extremely simple - it has only eight opcodes (with variations between them). The following table describes all fifteen mnemonics provided, alongside their meanings and word, doubleword, and quadword variants.

This tutorial assumes the reader already has a decent grasp on normal assembly and its paradigms. For a more useful assembly tutorial, check out Tutorialspoint: Assembly Programming Tutorial

Instruction Set

Mnemonic Meaning 16 Bit Variant 32 Bit Variant 64 Bit Variant
MMI Move Memory <- Immediate MMIW MMID MMIQ
MOV Move Memory <- Memory MOVW MOVD MOVQ
ADD Add ADDW ADDD ADDQ
SUB Subtract SUBW SUBD SUBQ
OR Bitwise OR ORW ORD ORQ
NOT Bitwise NOT NOTW NOTD NOTQ
INT System Interrupt INTW INTD INTQ
CMP Compare CMPW CMPD CMPQ
JMP Unconditional Jump JMPW JMPD JMPQ
JE Jump if Equal JEW JED JEQ
JNE Jump if Not Equal JNEW JNED JNEQ
JG Jump if Greater JGW JGD JGQ
JL Jump if Less JLW JLD JLQ
JO Jump if Overflow JOW JOD JOQ
JC Jump if Carry JCW JCD JCQ

Missing Instructions

Users may notice the absence of several instructions - namely AND and XOR. These can be expressed as combinations of other operations, therefore they were not included in the instruction set. For example, AND can be expressed as the following:

NOT(OR[NOT(A), NOT(B)])

The above can be expressed in 3BINS as the following, given input locations A and B, and expected output in location A:

.and:
    NOT A
    NOT B
    OR  A, B
    NOT A

Reserved Locations

3BINS has eight bytes of reserved space - from 0x00 to 0x07. The first doubleword, from 0x00 to 0x03, acts as a 32-bit program counter. The second, from 0x04 to 0x07, acts as a FLAGS register for recording the results of arithmetic and comparison instructions.

Assembler

The current assembler is made with customasm. As such, it follows the rules and features of customasm as described in the docs.

12-Bit CPU Definition

; This CPUDEF defines the encoding for a 12-bit mode compatible 3BINS assembler
#cpudef
{
    #bits 8

    MOV {dst}, {src} -> 0b000 @ 0b00000 @ dst[11:0] @ src[11:0]
    MMI {dst}, {imm} -> 0b000 @ 0b00001 @ dst[11:0] @ imm[11:0]
    ADD {dst}, {src} -> 0b001 @ 0b00000 @ dst[11:0] @ src[11:0]
    SUB {dst}, {src} -> 0b010 @ 0b00000 @ dst[11:0] @ src[11:0]
    OR  {dst}, {src} -> 0b011 @ 0b00000 @ dst[11:0] @ src[11:0]
    NOT {dst}        -> 0b100 @ 0b00000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INT {imm}        -> 0b101 @ 0b00000 @ imm[23:0]
    CMP {dst}, {src} -> 0b110 @ 0b00000 @ dst[11:0] @ src[11:0]
    JMP {mem}        -> 0b111 @ 0b00000 @ mem[23:0]
    JE  {mem}        -> 0b111 @ 0b00001 @ mem[23:0]
    JNE {mem}        -> 0b111 @ 0b00010 @ mem[23:0]
    JG  {mem}        -> 0b111 @ 0b00011 @ mem[23:0]
    JL  {mem}        -> 0b111 @ 0b00100 @ mem[23:0]
    JO  {mem}        -> 0b111 @ 0b00101 @ mem[23:0]
    JC  {mem}        -> 0b111 @ 0b00110 @ mem[23:0]
}

16-Bit CPU Definition

; This CPUDEF defines the encoding for a 16-bit mode compatible 3BINS assembler
#cpudef
{
    #bits 8

    MOV {dst}, {src} -> 0b000 @ 0b00000 @ dst[11:0] @ src[11:0]
    MMI {dst}, {imm} -> 0b000 @ 0b00001 @ dst[11:0] @ imm[11:0]
    ADD {dst}, {src} -> 0b001 @ 0b00000 @ dst[11:0] @ src[11:0]
    SUB {dst}, {src} -> 0b010 @ 0b00000 @ dst[11:0] @ src[11:0]
    OR  {dst}, {src} -> 0b011 @ 0b00000 @ dst[11:0] @ src[11:0]
    NOT {dst}        -> 0b100 @ 0b00000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INT {imm}        -> 0b101 @ 0b00000 @ imm[23:0]
    CMP {dst}, {src} -> 0b110 @ 0b00000 @ dst[11:0] @ src[11:0]
    JMP {mem}        -> 0b111 @ 0b00000 @ mem[23:0]
    JE  {mem}        -> 0b111 @ 0b00001 @ mem[23:0]
    JNE {mem}        -> 0b111 @ 0b00010 @ mem[23:0]
    JG  {mem}        -> 0b111 @ 0b00011 @ mem[23:0]
    JL  {mem}        -> 0b111 @ 0b00100 @ mem[23:0]
    JO  {mem}        -> 0b111 @ 0b00101 @ mem[23:0]
    JC  {mem}        -> 0b111 @ 0b00110 @ mem[23:0]

    MOVW {dst}, {src} -> 0b000 @ 0b10000 @ dst[11:0] @ src[11:0]
    MMIW {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0]  @ imm[15:0]]
    ADDW {dst}, {src} -> 0b001 @ 0b10000 @ dst[11:0] @ src[11:0]
    SUBW {dst}, {src} -> 0b010 @ 0b10000 @ dst[11:0] @ src[11:0]
    ORW  {dst}, {src} -> 0b011 @ 0b10000 @ dst[11:0] @ src[11:0]
    NOTW {dst}        -> 0b100 @ 0b10000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INTW {imm}        -> 0b101 @ 0b10000 @ imm[23:0]
    CMPW {dst}, {src} -> 0b110 @ 0b10000 @ dst[11:0] @ src[11:0]
    JMPW {mem}        -> 0b111 @ 0b10000 @ mem[23:0]
    JEW  {mem}        -> 0b111 @ 0b10001 @ mem[23:0]
    JNEW {mem}        -> 0b111 @ 0b10010 @ mem[23:0]
    JGW  {mem}        -> 0b111 @ 0b10011 @ mem[23:0]
    JLW  {mem}        -> 0b111 @ 0b10100 @ mem[23:0]
    JOW  {mem}        -> 0b111 @ 0b10101 @ mem[23:0]
    JCW  {mem}        -> 0b111 @ 0b10110 @ mem[23:0]
}

32-Bit CPU Definition

; This CPUDEF defines the encoding for a 32-bit mode compatible 3BINS assembler
#cpudef
{
    #bits 8

    MOV {dst}, {src} -> 0b000 @ 0b00000 @ dst[11:0] @ src[11:0]
    MMI {dst}, {imm} -> 0b000 @ 0b00001 @ dst[11:0] @ imm[11:0]
    ADD {dst}, {src} -> 0b001 @ 0b00000 @ dst[11:0] @ src[11:0]
    SUB {dst}, {src} -> 0b010 @ 0b00000 @ dst[11:0] @ src[11:0]
    OR  {dst}, {src} -> 0b011 @ 0b00000 @ dst[11:0] @ src[11:0]
    NOT {dst}        -> 0b100 @ 0b00000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INT {imm}        -> 0b101 @ 0b00000 @ imm[23:0]
    CMP {dst}, {src} -> 0b110 @ 0b00000 @ dst[11:0] @ src[11:0]
    JMP {mem}        -> 0b111 @ 0b00000 @ mem[23:0]
    JE  {mem}        -> 0b111 @ 0b00001 @ mem[23:0]
    JNE {mem}        -> 0b111 @ 0b00010 @ mem[23:0]
    JG  {mem}        -> 0b111 @ 0b00011 @ mem[23:0]
    JL  {mem}        -> 0b111 @ 0b00100 @ mem[23:0]
    JO  {mem}        -> 0b111 @ 0b00101 @ mem[23:0]
    JC  {mem}        -> 0b111 @ 0b00110 @ mem[23:0]

    MOVW {dst}, {src} -> 0b000 @ 0b10000 @ dst[11:0] @ src[11:0]
    MMIW {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
    ADDW {dst}, {src} -> 0b001 @ 0b10000 @ dst[11:0] @ src[11:0]
    SUBW {dst}, {src} -> 0b010 @ 0b10000 @ dst[11:0] @ src[11:0]
    ORW  {dst}, {src} -> 0b011 @ 0b10000 @ dst[11:0] @ src[11:0]
    NOTW {dst}        -> 0b100 @ 0b10000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INTW {imm}        -> 0b101 @ 0b10000 @ imm[23:0]
    CMPW {dst}, {src} -> 0b110 @ 0b10000 @ dst[11:0] @ src[11:0]
    JMPW {mem}        -> 0b111 @ 0b10000 @ mem[23:0]
    JEW  {mem}        -> 0b111 @ 0b10001 @ mem[23:0]
    JNEW {mem}        -> 0b111 @ 0b10010 @ mem[23:0]
    JGW  {mem}        -> 0b111 @ 0b10011 @ mem[23:0]
    JLW  {mem}        -> 0b111 @ 0b10100 @ mem[23:0]
    JOW  {mem}        -> 0b111 @ 0b10101 @ mem[23:0]
    JCW  {mem}        -> 0b111 @ 0b10110 @ mem[23:0]

    MOVD {dst}, {src} -> 0b000 @ 0b01000 @ dst[11:0] @ src[11:0]
    MMID {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
    ADDD {dst}, {src} -> 0b001 @ 0b01000 @ dst[11:0] @ src[11:0]
    SUBD {dst}, {src} -> 0b010 @ 0b01000 @ dst[11:0] @ src[11:0]
    ORD  {dst}, {src} -> 0b011 @ 0b01000 @ dst[11:0] @ src[11:0]
    NOTD {dst}        -> 0b100 @ 0b01000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INTD {imm}        -> 0b101 @ 0b01000 @ imm[23:0]
    CMPD {dst}, {src} -> 0b110 @ 0b01000 @ dst[11:0] @ src[11:0]
    JMPD {mem}        -> 0b111 @ 0b01000 @ mem[23:0]
    JED  {mem}        -> 0b111 @ 0b01001 @ mem[23:0]
    JNED {mem}        -> 0b111 @ 0b01010 @ mem[23:0]
    JGD  {mem}        -> 0b111 @ 0b01011 @ mem[23:0]
    JLD  {mem}        -> 0b111 @ 0b01100 @ mem[23:0]
    JOD  {mem}        -> 0b111 @ 0b01101 @ mem[23:0]
    JCD  {mem}        -> 0b111 @ 0b01110 @ mem[23:0]
}

64-Bit CPU Definition

; This CPUDEF defines the encoding for a 64-bit mode compatible 3BINS assembler
#cpudef
{
    #bits 8

    MOV {dst}, {src} -> 0b000 @ 0b00000 @ dst[11:0] @ src[11:0]
    MMI {dst}, {imm} -> 0b000 @ 0b00001 @ dst[11:0] @ imm[11:0]
    ADD {dst}, {src} -> 0b001 @ 0b00000 @ dst[11:0] @ src[11:0]
    SUB {dst}, {src} -> 0b010 @ 0b00000 @ dst[11:0] @ src[11:0]
    OR  {dst}, {src} -> 0b011 @ 0b00000 @ dst[11:0] @ src[11:0]
    NOT {dst}        -> 0b100 @ 0b00000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INT {imm}        -> 0b101 @ 0b00000 @ imm[23:0]
    CMP {dst}, {src} -> 0b110 @ 0b00000 @ dst[11:0] @ src[11:0]
    JMP {mem}        -> 0b111 @ 0b00000 @ mem[23:0]
    JE  {mem}        -> 0b111 @ 0b00001 @ mem[23:0]
    JNE {mem}        -> 0b111 @ 0b00010 @ mem[23:0]
    JG  {mem}        -> 0b111 @ 0b00011 @ mem[23:0]
    JL  {mem}        -> 0b111 @ 0b00100 @ mem[23:0]
    JO  {mem}        -> 0b111 @ 0b00101 @ mem[23:0]
    JC  {mem}        -> 0b111 @ 0b00110 @ mem[23:0]

    MOVW {dst}, {src} -> 0b000 @ 0b10000 @ dst[11:0] @ src[11:0]
    MMIW {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
    ADDW {dst}, {src} -> 0b001 @ 0b10000 @ dst[11:0] @ src[11:0]
    SUBW {dst}, {src} -> 0b010 @ 0b10000 @ dst[11:0] @ src[11:0]
    ORW  {dst}, {src} -> 0b011 @ 0b10000 @ dst[11:0] @ src[11:0]
    NOTW {dst}        -> 0b100 @ 0b10000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INTW {imm}        -> 0b101 @ 0b10000 @ imm[23:0]
    CMPW {dst}, {src} -> 0b110 @ 0b10000 @ dst[11:0] @ src[11:0]
    JMPW {mem}        -> 0b111 @ 0b10000 @ mem[23:0]
    JEW  {mem}        -> 0b111 @ 0b10001 @ mem[23:0]
    JNEW {mem}        -> 0b111 @ 0b10010 @ mem[23:0]
    JGW  {mem}        -> 0b111 @ 0b10011 @ mem[23:0]
    JLW  {mem}        -> 0b111 @ 0b10100 @ mem[23:0]
    JOW  {mem}        -> 0b111 @ 0b10101 @ mem[23:0]
    JCW  {mem}        -> 0b111 @ 0b10110 @ mem[23:0]

    MOVD {dst}, {src} -> 0b000 @ 0b01000 @ dst[11:0] @ src[11:0]
    MMID {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
    ADDD {dst}, {src} -> 0b001 @ 0b01000 @ dst[11:0] @ src[11:0]
    SUBD {dst}, {src} -> 0b010 @ 0b01000 @ dst[11:0] @ src[11:0]
    ORD  {dst}, {src} -> 0b011 @ 0b01000 @ dst[11:0] @ src[11:0]
    NOTD {dst}        -> 0b100 @ 0b01000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INTD {imm}        -> 0b101 @ 0b01000 @ imm[23:0]
    CMPD {dst}, {src} -> 0b110 @ 0b01000 @ dst[11:0] @ src[11:0]
    JMPD {mem}        -> 0b111 @ 0b01000 @ mem[23:0]
    JED  {mem}        -> 0b111 @ 0b01001 @ mem[23:0]
    JNED {mem}        -> 0b111 @ 0b01010 @ mem[23:0]
    JGD  {mem}        -> 0b111 @ 0b01011 @ mem[23:0]
    JLD  {mem}        -> 0b111 @ 0b01100 @ mem[23:0]
    JOD  {mem}        -> 0b111 @ 0b01101 @ mem[23:0]
    JCD  {mem}        -> 0b111 @ 0b01110 @ mem[23:0]

    MOVQ {dst}, {src} -> 0b000 @ 0b11000 @ dst[11:0] @ src[11:0]
    MMIQ {dst}, {imm} -> 0b000 @ 0b10001 @ dst[7:0] @ imm[15:0]
    ADDQ {dst}, {src} -> 0b001 @ 0b11000 @ dst[11:0] @ src[11:0]
    SUBQ {dst}, {src} -> 0b010 @ 0b11000 @ dst[11:0] @ src[11:0]
    ORQ  {dst}, {src} -> 0b011 @ 0b11000 @ dst[11:0] @ src[11:0]
    NOTQ {dst}        -> 0b100 @ 0b11000 @ dst[11:0] @ 0b0000 @ 0b00000000
    INTQ {imm}        -> 0b101 @ 0b11000 @ imm[23:0]
    CMPQ {dst}, {src} -> 0b110 @ 0b11000 @ dst[11:0] @ src[11:0]
    JMPQ {mem}        -> 0b111 @ 0b11000 @ mem[23:0]
    JEQ  {mem}        -> 0b111 @ 0b11001 @ mem[23:0]
    JNEQ {mem}        -> 0b111 @ 0b11010 @ mem[23:0]
    JGQ  {mem}        -> 0b111 @ 0b11011 @ mem[23:0]
    JLQ  {mem}        -> 0b111 @ 0b11100 @ mem[23:0]
    JOQ  {mem}        -> 0b111 @ 0b11101 @ mem[23:0]
    JCQ  {mem}        -> 0b111 @ 0b11110 @ mem[23:0]
}

Crash Course

Following is a crash course source code that is also a tutorial:

; Comments begin with a semicolon and are ignored by the compiler.

myvar1 = 0x77           ; Constants are defined as above
myvar2 = myvar1 + 0x11

start:             ; Labels mark memory addresses. 
                    ; Start has special meaning as the program entry point.

    MMI 0x08, 32    ; Move the immediate decimal value 32 into memory 
                    ; at address 0x08
    MOV 0x0C, 0x08  ; Move (Copy) the value at address 0x08 into 0x0C

    MOV 0x08, myvar1 ; Move the value of the constant myvar1 into 0x08.

    JMP jumphere    ; JMP jumps unconditionally to the provided label

skipme:            ; This code will be skipped by the JMP above.

    NOT 0x08        ; NOT inverts all the bits in a doubleword
    NOT 0x0C
    OR  0x08, 0x0C  ; OR performs a bitwise OR on the two operands and stores 
                    ; the result in the left operand.
    NOT 0x08        ; This implicitly leaves the result in location 0x08

jumphere:
    MMI 0x10, 5
    MMI 0x14, 10
    ADD 0x10, 0x14  ; Adds 5 to 10, and stores the result in 0x10
                    ; Every arithmetic and bitwise operation changes
                    ; a set of flags which is tested by certain jumps

    MMI 0xFE, 0
    CMP 0x10, 0xFE  ; Compare the value at 0x10 (15) with the value at 0xFE (0)
    JE  jumphere    ; this will never trigger, because 5+10 is not equal to zero

    SUB 0x10, 0x14  ; This evaluates as 0x10(15) - 0x14(10), and stores the result in 0x10

    CMP 0x10, 0x14  ; This compares the values in 0x10 and 0x14 and sets flags

    JL  loops       ; This will trigger because the CMP found that the values 
                    ; of 0x10 < 0x14

func:              ; This function computes (a+b)-c. It expects a, b, and c  
                    ; to be found at 0x18, 0x1C, and 0x1F, respectively. It 
                    ; will leave the output at 0x24, and leave the inputs untouched.
                    ; Finally, it expects a return address at 0x28.

    MOV 0xE0, 0x18  ; Copy the values into temporary memory so they dont
    MOV 0xE4, 0x1C  ; get clobbered.
    MOV 0xE8, 0x1F

    ADD 0xE0, 0xE4  ; Do the computation
    SUB 0xE0, 0xE8

    MOV 0x24, 0xE0  ; Move the result into the expected location.
    JMP 0x28        ; JMP-ing to a memory address takes the value at that addr
                    ; as the jump destination.

loops:
    MMI 0x20, 5
    MMI 0x24, 1
    .loop:              ; Local Labels begin with a . and are only valid in the scope of the outer label.
        SUB 0x20, 0x24  ; Decrement the counter.

        MMI 0xF0, 0
        CMP 0x20, 0xF0
        JE  .loopEnd        ; If the counter is zero, skip to the end of the loop. Otherwise,
        JMP .loop           ; Restart loop
    .loopEnd:

    MMI 0x28, ret       ; Set return address

ret:

    MMI 0x18, 5         ; Set up input data.
    MMI 0x1C, 8         
    MMI 0x1F, 2

    JMP func            ; Jump to the subroutine

This assembles to:

(Hexdump)

 00 | 01 00 80 20  00 00 c0 08  00 00 80 77  e0 00 00 20 | ... .......w...  |
 10 | 80 00 80 00  80 00 c0 00  60 00 80 0c  80 00 80 00 | ........`....... |
 20 | 01 01 00 05  01 01 40 0a  20 01 00 14  01 0f e0 00 | [email protected]  ....... |
 30 | c0 01 00 fe  e1 00 00 20  40 01 00 14  c0 01 00 14 | ....... @....... |
 40 | e4 00 00 60  00 0e 00 18  00 0e 40 1c  00 0e 80 1f | ...`[email protected] |
 50 | 20 0e 00 e4  40 0e 00 e8  00 02 40 e0  e0 00 00 28 |  [email protected]@....( |
 60 | 01 02 00 05  01 02 40 01  40 02 00 24  01 0f 00 00 | [email protected]@..$.... |
 70 | c0 02 00 f0  e1 00 00 7c  e0 00 00 68  01 02 80 80 | ...........h.... |
 80 | 01 01 80 05  01 01 c0 08  01 01 f0 02  e0 00 00 44 | ...............D |

(Raw)

010080200000c00800008077e0000020800080008000c0006000800c80008000010100050101400a20010014010fe000c00100fee100002040010014c0010014e4000060000e0018000e401c000e801f200e00e4400e00e8000240e0e0000028010200050102400140020024010f0000c00200f0e100007ce000006801028080010180050101c0080101f002e0000044

Setting up a Stack

Since 3BINS by default has no stack, it has no way of passing information to and from subroutines and system calls besides pre-defined memory locations. Of course, that method gets messy quickly, since there are no established standards. As such, any 3BINS-compatible environment provides a CPU interrupt to establish a stack, if the running program requires one.

To define a stack, call CPU interrupt 0xC2. The interrupt expects an array of 32-bit wide parameters stored beginning at 0x08 (the third doubleword) in memory. The parameters are as follows:

  • An integer value representing the stack width, in bits.
  • A pointer to the location of the stack pointer, initialized to the base of the stack.
    • In 12, 16, and 32-bit mode, this will be interpreted as an immediate value representing the start address of the stack.
    • In 64-bit mode, this will be interpreted as a 32-bit wide pointer to a 64-bit value representing the start address of the stack
  • The address to push and pop to
    • This behaves the same as the previous parameter.

Afterwards, CPU interrupts 0xD0 and 0xD1 will push and pop to the specified location, respectively. The stack grows downwards in memory.