0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

axxでアセンブルするbrainfuck interpreter[x86_64]

0
Last updated at Posted at 2026-05-13

axxでアセンブルするbrainfuck interpreter for FreeBSD on x86_64です。

パターンファイル

このファイルはAI支援により生成しました。

x86_64.axx
/* ========================================
   x86_64 Pattern File for axx
   v2

   【修正箇所 v1】
   1. MOV d,[b]: ModRM d/b逆転バグ修正 → (d&7)<<3|(b&7)
   2. MOV d,s / XOR / CMP / ADD: REX.R/REX.B を (s|d)&8 から正しく計算
   3. MOV d,!e (imm64): REX.B + opcode下位3bit を d&7 でマスク
   4. INC/DEC: ModRM r/m フィールドを d&7 でマスク
   5. INCB/DECB [b] / CMPB [b],i: r>=8 のとき REX.B(0x41) を条件付き発行

   【修正箇所 v2 — SIB abs32アドレッシング対応】
   [!s\+r] パターン追加:
     s を `.equ symbol::abs32` で定義すると、4バイトフィールドに
     R_X86_64_32 (絶対32bitアドレス) リロケーションが生成される。
     - !s\+ は stopchar='+' で s がレジスタ部を飲み込まないようにする
     - REX.X: index が r8-r15 なら 0x42 を条件付き発行
     - ModRM: mod=00, /opcode, r/m=4(SIB) → 0x04|(/opcode<<3)
     - SIB:   scale=0, index=(r&7), base=5(disp32) → ((r&7)<<3)|5
     - disp32: @@[4,*(s,%%)] → s の値を4バイトLEで展開
   ======================================== */

/* ====================== レジスタ定義 ====================== */
.setsym::RAX::0
.setsym::RCX::1
.setsym::RDX::2
.setsym::RBX::3
.setsym::RSP::4
.setsym::RBP::5
.setsym::RSI::6
.setsym::RDI::7
.setsym::R8::8
.setsym::R9::9
.setsym::R10::10
.setsym::R11::11
.setsym::R12::12
.setsym::R13::13
.setsym::R14::14
.setsym::R15::15

.setsym::EAX::0
.setsym::ECX::1
.setsym::EDX::2
.setsym::EBX::3
.setsym::ESP::4
.setsym::EBP::5
.setsym::ESI::6
.setsym::EDI::7
.setsym::R8D::8
.setsym::R9D::9
.setsym::R10D::10
.setsym::R11D::11
.setsym::R12D::12
.setsym::R13D::13
.setsym::R14D::14
.setsym::R15D::15

.setsym::AL::0
.setsym::CL::1
.setsym::DL::2
.setsym::BL::3

/* ====================== [symbol+reg] SIBアドレッシング ====================== */
/*
   disp32 + index*1 (base なし) アドレッシング。
   s を「symbol: .equ addr::abs32」で定義すると、
   4バイトフィールドに R_X86_64_32 リロケーションが生成される。

   符号化:
     REX  : ;((r&8)?0x42:0)  ← REX.X bit のみ (index が r8-r15 のとき)
     opcode: 0xFE / 0x80 など
     ModRM : mod=00, /op, r/m=4(SIB)
     SIB   : scale=00, index=(r&7), base=101(disp32のみ) → ((r&7)<<3)|5
     disp32: @@[4,*(s,%%)]
*/
INCB  [!s\+r]      ::  ::  ;((r&8)?0x42:0),0xfe,0x04,((r&7)<<3)|5,@@[4,*(s,%%)]
DECB  [!s\+r]      ::  ::  ;((r&8)?0x42:0),0xfe,0x0c,((r&7)<<3)|5,@@[4,*(s,%%)]
CMPB  [!s\+r],!i   ::  ::  ;((r&8)?0x42:0),0x80,0x3c,((r&7)<<3)|5,@@[4,*(s,%%)],(i&0xff)
MOVB  d,[!s\+r]    ::  ::  ;((r&8)?0x42:0),0x8a,0x04|((d&7)<<3),((r&7)<<3)|5,@@[4,*(s,%%)]
MOVB  [!s\+r],d    ::  ::  ;((r&8)?0x42:0),0x88,0x04|((d&7)<<3),((r&7)<<3)|5,@@[4,*(s,%%)]

/* ====================== 64bit メモリロード ====================== */
/* MOV r64, [r/m64]  (opcode 0x8B)
   REX: W=1, R=(d>=8 → extends reg field), B=(b>=8 → extends r/m field)
   ModRM: mod=00, reg=d (dest), r/m=b (source) */
MOV d,[b]  ::  ::  0x48|((d&8)>>1)|((b&8)>>3),0x8b,((d&7)<<3)|(b&7)

/* ====================== 32bit メモリロード ====================== */
/* MOV r32, [r/m32]  (opcode 0x8B, REX.W省略)
   ';(cond?val:0)' → val が 0 なら byte を省略 */
MOVD d,[b]  ::  ::  ;(((d|b)&8)?(0x40|((d&8)>>1)|((b&8)>>3)):0),0x8b,((d&7)<<3)|(b&7)

/* ====================== 8bit メモリロード ====================== */
MOVB d,[b]  ::  ::  ;(((d|b)&8)?(0x40|((d&8)>>1)|((b&8)>>3)):0),0x8a,((d&7)<<3)|(b&7)

/* ====================== 64bit レジスタ間 MOV ====================== */
/* MOV r/m64, r64  (opcode 0x89)
   reg=s(source), r/m=d(dest)
   REX: W=1, R=(s>=8), B=(d>=8) */
MOV d,s  ::  ::  0x48|((s&8)>>1)|((d&8)>>3),0x89,0xc0|(d&7)|((s&7)<<3)

/* ====================== 64bit 即値 MOV ====================== */
/* MOV r64, imm64  (opcode 0xB8+rd)
   REX: W=1, B=(d>=8 → extends opcode register field)
   opcode = 0xB8 | (d&7) */
MOV d,!e  ::  ::  0x48|((d&8)>>3),0xb8|(d&7),@@[8,*(e,%%)]

/* ====================== 32bit レジスタ間 MOV ====================== */
MOVD d,s  ::  ::  ;(((d|s)&8)?(0x40|((s&8)>>1)|((d&8)>>3)):0),0x89,0xc0|(d&7)|((s&7)<<3)

/* ====================== 32bit 即値 MOV ====================== */
MOVD d,!e  ::  ::  ;((d&8)?(0x41):0),0xb8|(d&7),@@[4,*(e,%%)]

/* ====================== 8bit MOV ====================== */
MOVB d,s  ::  ::  ;(((d|s)&8)?(0x40|((s&8)>>1)|((d&8)>>3)):0),0x88,0xc0|(d&7)|((s&7)<<3)
MOVB d,!e  ::  ::  0xb0|(d&7),e

/* ====================== ADD ====================== */
/* ADD r/m64, r64  (opcode 0x01)
   REX: W=1, R=(s>=8), B=(d>=8) */
ADD d,s  ::  ::  0x48|((s&8)>>1)|((d&8)>>3),0x01,0xc0|(d&7)|((s&7)<<3)

/* ADD r/m64, imm32  (opcode 0x81 /0)
   ModRM: mod=11, /0 → reg=0, r/m=d → 0xC0|(d&7) */
ADD d,!i  ::  ::  0x48|((d&8)>>3),0x81,0xc0|(d&7),@@[4,*(i,%%)]

/* ====================== XOR ====================== */
XOR d,s  ::  ::  0x48|((s&8)>>1)|((d&8)>>3),0x31,0xc0|(d&7)|((s&7)<<3)

/* ====================== CMP (64bit) ====================== */
/* CMP r/m64, r64  (opcode 0x39) */
CMP d,s  ::  ::  0x48|((s&8)>>1)|((d&8)>>3),0x39,0xc0|(d&7)|((s&7)<<3)

/* CMP r/m64, imm32  (opcode 0x81 /7)
   ModRM: mod=11, /7 → reg=7, r/m=d → 0xF8|(d&7) */
CMP d,!i  ::  ::  0x48|((d&8)>>3),0x81,0xf8|(d&7),@@[4,*(i,%%)]

/* ====================== CMP (32bit) ====================== */
CMPD d,s  ::  ::  ;(((d|s)&8)?(0x40|((s&8)>>1)|((d&8)>>3)):0),0x39,0xc0|(d&7)|((s&7)<<3)
CMPD d,!i  ::  ::  ;((d&8)?(0x41):0),0x81,0xf8|(d&7),@@[4,*(i,%%)]

/* ====================== CMP (8bit) ====================== */
/* CMP BYTE PTR [b], imm8  —  レジスタ間接
   ModRM: mod=00, /7 → reg=7(CMP), r/m=b → 0x38|(b&7) */
CMPB [b],!i  ::  ::  ;((b&8)?0x41:0),0x80,0x38|(b&7),i

/* CMP r/m8, imm8  —  レジスタ直接 */
CMPB d,!i  ::  ::  ;((d&8)?0x41:0),0x80,0xf8|(d&7),i

/* ====================== INC/DEC (64bit) ====================== */
/* INC r/m64  (opcode 0xFF /0)
   REX: W=1, B=(d>=8)
   ModRM: mod=11, /0 → reg=0, r/m=d → 0xC0|(d&7) */
INC d  ::  ::  0x48|((d&8)>>3),0xff,0xc0|(d&7)

/* DEC r/m64  (opcode 0xFF /1)
   ModRM: mod=11, /1 → reg=1, r/m=d → 0xC8|(d&7) */
DEC d  ::  ::  0x48|((d&8)>>3),0xff,0xc8|(d&7)

/* ====================== INCB/DECB (8bit メモリ間接) ====================== */
/* INC BYTE PTR [b]  (opcode 0xFE /0)
   REX: 省略可。b>=8のときREX.B(0x41)を発行
   ModRM: mod=00, /0 → reg=0, r/m=b → 0x00|(b&7) */
INCB [b]  ::  ::  ;((b&8)?0x41:0),0xfe,0x00|(b&7)

/* DEC BYTE PTR [b]  (opcode 0xFE /1) */
DECB [b]  ::  ::  ;((b&8)?0x41:0),0xfe,0x08|(b&7)

/* ====================== LEA ====================== */
/* LEA r64, [RIP+disp32]  —  RIP相対 (命令長 = 7 バイト)
   REX: W=1, R=(d>=8)
   ModRM: mod=00, reg=d, r/m=5(RIP+disp32) → ((d&7)<<3)|5 */
LEA d,[RIP+!a]  ::  ::  0x48|((d&8)>>1),0x8d,((d&7)<<3)|5,@@[4,*(a-($$+7),%%)]

/* ====================== 条件分岐 (32bit 相対、各 6 バイト) ====================== */
JGE !a  ::  ::  0x0f,0x8d,@@[4,*(a-($$+6),%%)]
JL  !a  ::  ::  0x0f,0x8c,@@[4,*(a-($$+6),%%)]
JLE !a  ::  ::  0x0f,0x8e,@@[4,*(a-($$+6),%%)]
JG  !a  ::  ::  0x0f,0x8f,@@[4,*(a-($$+6),%%)]
JE  !a  ::  ::  0x0f,0x84,@@[4,*(a-($$+6),%%)]
JNE !a  ::  ::  0x0f,0x85,@@[4,*(a-($$+6),%%)]
JC  !a  ::  ::  0x0f,0x82,@@[4,*(a-($$+6),%%)]
JNC !a  ::  ::  0x0f,0x83,@@[4,*(a-($$+6),%%)]

/* ====================== 無条件分岐 (32bit 相対、5 バイト) ====================== */
JMP !a  ::  ::  0xe9,@@[4,*(a-($$+5),%%)]

/* ====================== その他 ====================== */
SYSCALL  ::  ::  0x0f,0x05
RET      ::  ::  0xc3
NOP      ::  ::  0x90

/* ====================== データ ====================== */
DB !e  ::  ::  e

brainfuck interpreter ソースファイル

bf.s
; Brainfuck interpreter
; FreeBSD x86_64 / Linux x86_64 (syscall番号を変更)
; Intel syntax (axx)
;
; build (FreeBSD):
;   python3 axx.py x86_64.axx bf.s -o bf.o --osabi FreeBSD && ld -o bf bf.o
;
; build (Linux):
;   syscall番号を Linux 用 (下記参照) に変更してから:
;   python3 axx.py x86_64.axx bf.s -o bf.o && ld -o bf bf.o
;
; ===== テープアクセスの方針 =====
; tape_a: .equ tape::abs32 と定義し、
; x86_64.axx の [!s\+r] パターンが 4バイト disp32 フィールドに
; R_X86_64_32 (絶対32bitアドレス) リロケーションを生成する。
;
; これにより [symbol+register] SIBアドレッシングが使用でき、
; r15 をテープインデックス (0オリジン) として扱える。
;
; レジスタ割り当て:
;   r12 = fd (ファイルオープン時のみ)
;   r13 = プログラム長 (バイト数)
;   r14 = 命令ポインタ (prog_buf インデックス)
;   r15 = テープインデックス (tape[r15] が現在セル)

.global _start

; --- FreeBSD syscall番号 ---
; (Linux: EXIT=60, READ=0, WRITE=1, OPEN=2, CLOSE=3)
SYS_EXIT:  .equ  1
SYS_READ:  .equ  3
SYS_WRITE: .equ  4
SYS_OPEN:  .equ  5
SYS_CLOSE: .equ  6

.section .data
usage:     .ascii "Usage: bf <file>",10
usage_len: .equ $$ - usage
.endsection

.section .bss
tape:     .zero 65536
prog_buf: .zero 1048576
.endsection

; SIB abs32 エイリアス:
;   INCB/DECB/CMPB [tape_a+r15] が R_X86_64_32 リロケーションを生成する
tape_a: .equ tape::abs32

.section .text

_start:
    ; FreeBSD x86_64: rsp → argc, rsp+8 → argv[0], rsp+16 → argv[1]
    mov  r8, rsp
    movd eax, [r8]         ; eax = argc
    cmpd eax, 2
    jge  _open_file

    ; 引数不足: 使い方を stderr に表示して終了
    mov rax, SYS_WRITE
    mov rdi, 2
    lea rsi, [RIP+usage]
    mov rdx, usage_len
    syscall
    mov rax, SYS_EXIT
    mov rdi, 1
    syscall

_open_file:
    mov rax, r8
    add rax, 16            ; &argv[1]
    mov rdi, [rax]         ; argv[1] = ファイル名ポインタ
    mov rax, SYS_OPEN
    xor rsi, rsi           ; O_RDONLY = 0
    xor rdx, rdx
    syscall
    jc  exit_error         ; FreeBSD: CF=1 はシステムコールエラー
                           ; (Linux では: jl exit_error)
    mov r12, rax           ; r12 = fd

    ; ファイルを prog_buf に一括読み込み
    mov rax, SYS_READ
    mov rdi, r12
    mov rsi, prog_buf
    mov rdx, 1048576
    syscall
    mov r13, rax           ; r13 = プログラム長

    mov rax, SYS_CLOSE
    mov rdi, r12
    syscall

    ; 初期化
    xor r14, r14           ; 命令ポインタ = 0
    xor r15, r15           ; テープインデックス = 0

main_loop:
    cmp r14, r13
    jge exit

    ; 現在の BF 命令を al に読み込む
    mov rax, prog_buf
    add rax, r14
    movb al, [rax]

    cmpb al, '>'
    je   op_inc_ptr
    cmpb al, '<'
    je   op_dec_ptr
    cmpb al, '+'
    je   op_inc_val
    cmpb al, '-'
    je   op_dec_val
    cmpb al, '.'
    je   op_output
    cmpb al, ','
    je   op_input
    cmpb al, '['
    je   op_loop_start
    cmpb al, ']'
    je   op_loop_end

next:
    inc r14
    jmp main_loop

; ===== Brainfuck 命令実装 =====

op_inc_ptr:
    inc r15                ; > : テープポインタを+1
    jmp next

op_dec_ptr:
    dec r15                ; < : テープポインタを-1
    jmp next

op_inc_val:
    incb [tape_a+r15]      ; + : 現在セルをインクリメント [SIB R_X86_64_32]
    jmp next

op_dec_val:
    decb [tape_a+r15]      ; - : 現在セルをデクリメント [SIB R_X86_64_32]
    jmp next

op_output:
    ; . : write(1, &tape[r15], 1)
    ; rsi = tape の絶対アドレス + r15 (インデックス)
    mov rsi, tape_a        ; rsi = tape の絶対アドレス (imm64, R_X86_64_64)
    add rsi, r15
    mov rdx, 1
    mov rax, SYS_WRITE
    mov rdi, 1
    syscall
    jmp next

op_input:
    ; , : read(0, &tape[r15], 1)
    mov rsi, tape_a
    add rsi, r15
    mov rdx, 1
    mov rax, SYS_READ
    xor rdi, rdi
    syscall
    cmp rax, 0
    jle exit               ; EOF またはエラー
    jmp next

op_loop_start:
    ; [ : 現在セルが 0 なら対応する ']' の直後へジャンプ
    cmpb [tape_a+r15], 0   ; [SIB R_X86_64_32]
    jne  next

    mov rcx, 1             ; ネスト深度
.find_end:
    inc r14
    cmp r14, r13
    jge exit
    mov rax, prog_buf
    add rax, r14
    movb al, [rax]
    cmpb al, '['
    je   .inc_depth
    cmpb al, ']'
    je   .dec_depth
    jmp  .find_end
.inc_depth:
    inc rcx
    jmp .find_end
.dec_depth:
    dec rcx
    cmp rcx, 0
    jne .find_end
    jmp next

op_loop_end:
    ; ] : 現在セルが 0 でなければ対応する '[' の直後へ戻る
    cmpb [tape_a+r15], 0   ; [SIB R_X86_64_32]
    je   next

    mov rcx, 1             ; ネスト深度
.find_start:
    cmp r14, 0
    jle exit
    dec r14
    mov rax, prog_buf
    add rax, r14
    movb al, [rax]
    cmpb al, ']'
    je   .inc_depth2
    cmpb al, '['
    je   .dec_depth2
    jmp  .find_start
.inc_depth2:
    inc rcx
    jmp .find_start
.dec_depth2:
    dec rcx
    cmp rcx, 0
    jne .find_start
    jmp next

exit_error:
    mov rax, SYS_EXIT
    mov rdi, 1
    syscall

exit:
    mov rax, SYS_EXIT
    xor rdi, rdi
    syscall

アセンブル and リンク

axx x86_64.axx bf.s -o bf.o
ld bf.o -o bf

実行

./bf mandelbrot.bf|head -4
AAAAAAAAAAAAAAAABBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDEGFFEEEEDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAAAABBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDEEEFGIIGFFEEEDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAAABBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDEEEEFFFI KHGGGHGEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB
AAAAAAAAAAAABBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDEEEEEFFGHIMTKLZOGFEEDDDDDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBBB
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?