Execute Brain****/x86 Assembly

From Rosetta Code

x86_64 Assembly

GAS Syntax for Linux. Called it "Brainkrieg" after Teen Girl Squad. Operating principle is a little like a multi-cycle processor. Uses a jump table. Fixed data memory block.

Implementation:

// Assembly brainf*** interpreter
// Alternative name: "ASSembly brainF*** interpretER"
// Compiles with `gcc -nostdlib brainkrieg.sx -o brainkrieg`
// Usage: ./brainkrieg "filename"
// Return 0 if normal exit
#define SYS_READ    $0
#define SYS_WRITE   $1
#define SYS_OPEN    $2
#define SYS_CLOSE   $3
#define SYS_FSTAT   $5
#define SYS_MMAP    $9
#define SYS_MUNMAP  $11
#define SYS_EXIT    $60

// From experiments:
#define FSIZEOFF    48
#define STATSIZE    144

// From Linux source:
#define RDONLY      $00     
#define PROT_READ   $0x1
#define MAP_PRIVATE $0x02
#define STDIN       $0
#define STDOUT      $1
#define STDERR      $2

#define DEBUGMODE   0

.global _start
.text

.macro ERRCHECK code
    cmpq    $\code, %rax
    je      fs_error
.endm

/* Local stack notes: 
    0: int fd
*/
#define STACKSIZE $4
_start:
    /* Entry Point: */
    // Open:
    movq    RDONLY, %rsi
    // Filename ptr is on stack currently as argv[1]:
    cmpq    $1, (%rsp)          // if argc is 1, error
    jnz     open_file
    jmp     fs_error
    
    open_file:
    movq    16(%rsp), %rdi      // argc(8), argv0(8) => rsp+16. filename
    movq    SYS_OPEN, %rax
    syscall
    ERRCHECK    -1
    subq    STACKSIZE, %rsp           // local stack
    movl    %eax, (%rsp)        // int fd = open(argv[1], RDONLY)
    
    // fstat to get filesize
    fstat:
    movq    $statstruct, %rsi
    movl    (%rsp), %edi        // fd
    movq    SYS_FSTAT, %rax
    syscall                     // fstat(fd, statstruct)
    ERRCHECK    -1
    
    // mmap - don't forget to munmap.
    mmap:
    movq    $0, %r9             // offset
    movq    (%rsp), %r8         // fd
    movq    MAP_PRIVATE, %r10
    movq    PROT_READ, %rdx
    movq    filesize, %rsi
    movq    (%rsp), %rdi        // vmemptr
    movq    SYS_MMAP, %rax
    syscall
    ERRCHECK    -1
    
    // Set up machine:
    boot:
    movq    %rax, head          // head = mmap'd file start
    movq    %rax, inst_ptr      // inst_ptr = head
    addq    filesize, %rax
    movq    %rax, end           // end = head+filesize
    movq    $tape, data_ptr     // data_ptr = tape
    xorq    %rax, %rax
    /* Magic happens here:
        - Fetch symbol
        - Decode symbol
        - Execute opcode
        - Instruction out of range = halt
        - Data out of range = halt
     */
    OS_start:
    fetch:
        movq    inst_ptr, %rbx
        cmpq    end, %rbx
        ja      shutdown            // End of code
        movzbq  (%rbx), %rax
        incq    %rbx
        movq    %rbx, inst_ptr
    decode:
        #if DEBUGMODE
            movb    %al, debugChar
            movq    $1, %rdx
            movq    $debugChar, %rsi
            movq    STDERR, %rdi
            movq    SYS_WRITE, %rax
            syscall
            ERRCHECK    -1
            movzbq  debugChar, %rax
        #endif
        mov     $branch_table, %rcx
        jmp     *(%rcx,%rax,8)
    // execute:
    dp_left:
        cmpq    $tape, data_ptr
        jz      scram
        decq    data_ptr
        jmp     fetch
    dp_right:
        cmpq    $endtape, data_ptr
        jz      scram
        incq    data_ptr
        jmp     fetch
    dec_data:
        movq    data_ptr, %rbx
        decb    (%rbx)
        jmp     fetch
    inc_data:
        movq    data_ptr, %rbx
        incb    (%rbx)
        jmp     fetch
    out_data:
        movq    $1, %rdx
        movq    data_ptr, %rsi
        movq    STDOUT, %rdi
        movq    SYS_WRITE, %rax
        syscall
        ERRCHECK    -1
        jmp     fetch
    in_data:
        movq    $1, %rdx
        movq    data_ptr, %rsi
        movq    STDIN, %rdi
        movq    SYS_READ, %rax
        syscall
        ERRCHECK    -1
        cmpb    $'\n, (%rsi)
        je      fetch
        movq    %rax, junkChar
        jmp     fetch
    brf: // branch if *dp=0
        movq    data_ptr, %rbx
        cmpb    $0, (%rbx)
        jnz     fetch
        matchfwd:
            movq    inst_ptr, %rbx
            movb    (%rbx), %al
            cmpb    $'[, %al
            jne     no_smph
            incq    brack_smph
            cmpq    end, %rbx
            je      scram
            incq    inst_ptr
            jmp     matchfwd
            no_smph:    // no semaphore needed
            cmpb    $'], %al
            je      check_smph
            cmpq    end, %rbx
            je      scram
            incq    inst_ptr
            jmp     matchfwd
            check_smph:     // check if semaphore set
            cmpq    $0, brack_smph
            jz      donematch
            decq    brack_smph
            cmpq    end, %rbx
            je      scram
            incq    inst_ptr
            jmp     matchfwd
    brb: // branch if *dp!=0
        movq    data_ptr, %rbx
        cmpb    $0, (%rbx)
        jz      fetch
        subq    $2, inst_ptr        // Branch taken, scan back.
        matchbwd:
            movq    inst_ptr, %rbx
            movb    (%rbx), %al
            cmpb    $'], %al
            jne     no_smph2
            incq    brack_smph
            cmpq    head, %rbx
            je      scram
            decq    inst_ptr
            jmp     matchbwd
            no_smph2:   // no semaphore needed
            cmpb    $'[, %al
            je      check_smph2
            cmpq    head, %rbx
            je      scram
            decq    inst_ptr
            jmp     matchbwd
            check_smph2:    // check if semaphore set
            cmpq    $0, brack_smph
            jz      donematch2
            decq    brack_smph
            cmpq    head, %rbx
            je      scram
            decq    inst_ptr
            jmp     matchbwd
        donematch:
        incq    inst_ptr
        donematch2:
        jmp     fetch
    
    scram:      // Memory breached
    movq    $0x7f, err_val
    
    shutdown:
    // Consume rest of stdin if we used it.
    cmpb    $0, junkChar
    jz      skip_flush
    flush_stdin:
    movq    SYS_READ, %rax
    movq    STDIN, %rdi 
    movq    $junkChar, %rsi 
    movq    $1, %rdx
    syscall
    cmpq    $0, %rax            // EOF
    jz      skip_flush
    cmpb    $'\n, junkChar
    jne     flush_stdin
    
    // munmap
    skip_flush:
    movq    filesize, %rsi
    movq    head, %rdi
    movq    SYS_MUNMAP, %rax
    syscall                     // munmap(vmemptr, filesize)
    cmpq    $-1, %rax
    je      fs_error
    // close
    movl    (%rsp), %edi
    movq    SYS_CLOSE, %rax
    syscall                     // close(fd)
    ERRCHECK    -1

exit:
    movq    SYS_EXIT, %rax
    movzbq  err_val, %rdi
    syscall

fs_error:
    movq    SYS_EXIT, %rax
    movq    $-1, %rdi
    syscall                         // exit(-1)

.data
branch_table:
    .rept   43
    .quad   fetch
    .endr
    
    .quad   inc_data
    .quad   in_data
    .quad   dec_data
    .quad   out_data
    
    .rept   13
    .quad   fetch
    .endr
    
    .quad   dp_left
    .quad   fetch
    .quad   dp_right
    
    .rept   28
    .quad   fetch
    .endr
    
    .quad   brf
    .quad   fetch
    .quad   brb
    
    .rept   162
    .quad   fetch
    .endr

.bss
brack_smph:    // Bracket matching semaphore
    .quad   0

junkChar:       // Also used to check if input was used.
    .byte   0

#if DEBUGMODE
debugChar:
    .byte   0
#endif

err_val:
    .byte   0
    
// fstat:
statstruct:     // This struct is 144 bytes. Only want size (+48)
    .zero FSIZEOFF
    filesize:  // 8 bytes.
    .quad   0
    .zero   STATSIZE-FSIZEOFF+8
    
// Program:
head:
    .quad   0
end:
    .quad   0
inst_ptr:
    .quad   0
    
// Data:
data_ptr:
    .quad   0    
tape:
    .zero   (1<<20)
endtape:
    .zero   1