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