-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbf.S
264 lines (235 loc) · 4.86 KB
/
bf.S
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
/**
* Copyright (C) 2024 markx86
* License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>
*
* Usage: bf [INPUT-FILE]
* Where INPUT-FILE is the path to a file containing a brainfuck program.
* If no argument is provided or if INPUT-FILE is -, the program is read
* from standard input.
* The maximum size of the program is 32KB.
*/
.section .text, "ax"
.equ MAX_PROG , (32 << 9)
.equ MAX_CELLS, (32 << 9)
.equ SYS_read , 0
.equ SYS_write, 1
.equ SYS_open , 2
.equ SYS_close, 3
.equ SYS_exit , 60
.global _start
_start:
/* check if a file was provided */
pop %rcx /* argc */
/* if (argc >= 2) */
cmp $2, %rcx
jl 2f
pop %rdi /* argv[0] */
pop %rdi /* argv[1] */
/* check if argv[1] == "-" */
mov (%rdi), %rdx
cmp $'-', %dl
jne 1f
cmp $0, %dh
jne 1f
xor %edi, %edi
jmp 2f
1:
/* rsi is already at 0 == O_RDONLY */
mov $SYS_open, %al
syscall
cmp $0, %eax
jl do_exit
mov %eax, %edi
2:
/* read in the program */
mov $SYS_read, %rax
/* rdi already holds the fd from which we should read the program */
mov $prog, %rsi
mov $MAX_PROG, %rdx
syscall
add %rsi, %rax
push %rsi
push %rax
/* 8(%rsp) is the start of the program */
/* 0(%rsp) is the end of the program */
/* if we read the program from a file, it's about time we close that fd */
test %rdi, %rdi
jz 1f
mov $SYS_close, %eax
syscall
1:
/* init vm */
/* %rsi already points to `prog` so no need to mov it in there again */
mov $cells, %rdi
/* zero out cells */
mov %rdi, %rdx
mov $MAX_CELLS, %ecx
shr $3, %ecx
xor %eax, %eax
rep stosq
mov %rdx, %rdi
exec_loop:
/* compute index in jump table */
/* switch(*ip) */
xor %edx, %edx
mov $jump_table, %rbx
xor %eax, %eax
mov (%rsi), %al
shl $4, %ax
shr $4, %al
xor %ah, %al
test $1, %ah
jnz 1f
and $7, %al
1:
xor %ah, %ah
lea (%rbx,%rax,), %rbx
mov (%rbx), %al
add $switch_begin, %rax
jmp *%rax
switch_begin:
/* case ',' */
io_inp:
mov $SYS_read, %rax
jmp do_io
/* case '.' */
io_out:
mov $SYS_write, %rax
inc %dl
jmp do_io
/* case '>' */
incr_ptr:
inc %rdi
jmp clamp_ptr
/* case '<' */
decr_ptr:
dec %rdi
jmp clamp_ptr
/* case '+' */
incr_val:
incb (%rdi)
jmp switch_done
/* case '-' */
decr_val:
decb (%rdi)
jmp switch_done
/* case '[' */
skip_fwd:
mov (%rdi), %cl
test %cl, %cl
jnz switch_done
/* level = 0 */
skip_fwd_loop:
/* while (++ip < &prog[prog_size]) */
inc %rsi
cmp 0(%rsp), %rsi
jge switch_done
mov (%rsi), %al
/* if (*ip == '[') */
cmp $'[', %al
jne 1f
inc %edx
jmp 2f
1:
/* else if (*ip == ']') */
cmp $']', %al
jne 2f
/* if (level == 0) */
test %edx, %edx
jz switch_done
/* else */
dec %edx
2:
jmp skip_fwd_loop
/* case ']' */
skip_bwd:
mov (%rdi), %cl
test %cl, %cl
jz switch_done
/* level = 0 */
skip_bwd_loop:
/* while (--ip >= &prog[0]) */
dec %rsi
cmp 8(%rsp), %rsi
jl switch_done
mov (%rsi), %al
/* if (*ip == ']') */
cmp $']', %al
jne 1f
inc %edx
jmp 2f
1:
/* else if (*ip == '[') */
cmp $'[', %al
jne 2f
/* if (level == 0) */
test %edx, %edx
jz switch_done
/* else */
dec %edx
2:
jmp skip_bwd_loop
do_io:
push %rsi
mov %rdi, %rsi
mov %edx, %edi
xor %edx, %edx
inc %dl
syscall
mov %rsi, %rdi
pop %rsi
jmp switch_done
def_case:
mov (%rsi), %al
movzx %al, %rax
jmp do_exit
clamp_ptr:
/* check that the pointer to the cells is within bounds
and if not, adjust it */
mov %rdi, %rax
sub $cells, %rax
jl 1f
cmp $MAX_CELLS, %rax
jl switch_done
sub $MAX_CELLS, %rdi
jmp switch_done
1:
add $MAX_CELLS, %rdi
switch_done:
/* increment the program counter %rsi and check
if we just executed the last instruction of the program */
inc %rsi
mov %rsi, %rax
cmp 0(%rsp), %rax
jl exec_loop
mov $0, %rdi
do_exit:
mov $SYS_exit, %rax
syscall
.section .rodata, "a"
/* Yes, I know this jump table has conflicts.
No, I don't care.
It uses less bytes than the one generated by gcc,
I win, bye bye */
jump_table:
.byte (def_case - switch_begin) /* 0 -> \0 */
.byte (incr_val - switch_begin) /* 1 -> + */
.byte (switch_done - switch_begin) /* 2 -> SP */
.byte (def_case - switch_begin) /* 3 -> */
.byte (io_out - switch_begin) /* 4 -> . */
.byte (def_case - switch_begin) /* 5 -> */
.byte (io_inp - switch_begin) /* 6 -> , */
.byte (decr_val - switch_begin) /* 7 -> - */
.byte (skip_bwd - switch_begin) /* 8 -> ] */
.byte (def_case - switch_begin) /* 9 -> */
.byte (switch_done - switch_begin) /* a -> \n */
.byte (def_case - switch_begin) /* b -> */
.byte (def_case - switch_begin) /* c -> */
.byte (incr_ptr - switch_begin) /* d -> > */
.byte (skip_fwd - switch_begin) /* e -> [ */
.byte (decr_ptr - switch_begin) /* f -> < */
.section .bss, "aw"
prog:
.skip MAX_PROG
cells:
.skip MAX_CELLS