-
Notifications
You must be signed in to change notification settings - Fork 3
/
compile.s
356 lines (288 loc) · 7.08 KB
/
compile.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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
@ Copyright (c) 2009, Gerard Lledo Vives <[email protected]>
@ This program is open source. For license terms, see the LICENSE file.
.include "assembler.asi"
.include "regdefs.asi"
.include "syscalls.asi"
.include "lib.asi"
.section .rodata
.align 2
.LCTERMIN_IF_ELSE:
.asciz "ELSE"
.LCTERMIN_IF_THEN:
.asciz "THEN"
.ascii "\x00"
.LCTERMIN_DO_LOOP:
.asciz "LOOP"
.asciz "I"
.ascii "\x00"
.text
.align 2
.global compile
.global compile_entry
.global compile_exit
.global compile_if @ NAME: "IF"
.global compile_do @ NAME: "DO"
@ This shouldn't be necessary if we would assemble ourselves, but lets allow
@ the system assembler do it for us: we are lazy.
helpers_add_r0_1:
add r0, #1
helpers_cmp_r0_r1:
cmp r0, r1
helpers_cmp_r0_0:
cmp r0, #0
helpers_ldr_r0_vsp:
ldr r0, [vsp, #-4]!
helpers_ldr_r1_vsp:
ldr r1, [vsp, #-4]!
helpers_str_r0_vsp:
str r0, [vsp], #4
@ Compiles a load operation of r1 on r0
@ r0 is moved to next available location
compile_load32:
mov r2, r1
@ Set r0 to zero
GMOV I, #0, #0
@ LSB
ands r3, r2, #0xff
beq .Lcompile_load32_BYTE_2
GORR #0, #0, r3, #0
.Lcompile_load32_BYTE_2:
ror r2, #8
ands r3, r2, #0xff
beq .Lcompile_load32_BYTE_3
GORR #0, #0, r3, #0xC
.Lcompile_load32_BYTE_3:
ror r2, #8
ands r3, r2, #0xff
beq .Lcompile_load32_BYTE_4
GORR #0, #0, r3, #0x8
.Lcompile_load32_BYTE_4: @ aka, MSB
ror r2, #8
ands r3, r2, #0xff
beq .Lcompile_load32_end
GORR #0, #0, r3, #0x4
.Lcompile_load32_end:
bx lr
execmem_get:
ldr r0, .Lcompilation_pointer
ldr r0, [r0]
cmp r0, #0
bne .Lexecmem_get_done
@ TODO: We have no mmap memory management. That means that after our
@ page (4K) runs out, well get a SIGSEGV. Fix that later: if you get
@ to write 4K of useful code right now, you shouldn't be using this
@ forth implementation anyway.
push {r1, r2, r3, r4, r5}
mmap2 #0, #4096, #0x7, #0x22, #-1, #0
pop {r1, r2, r3, r4, r5}
.Lexecmem_get_done:
bx lr
execmem_store:
ldr r1, .Lcompilation_pointer
str r0, [r1]
bx lr
@ This function is the only one that needs to return the entry point of the
@ compiled code. The rest work trough the execmem iface.
compile_entry:
push {lr}
bl execmem_get
GPUSH #0x40, #0x0
bl execmem_store
@ Our return value
sub r0, #4
pop {lr}
bx lr
compile_exit:
push {lr}
bl execmem_get
GPOP #0x40, #0x0
GBX #14
bl execmem_store
pop {lr}
bx lr
@ The meat of the colon opearator. No other compilation subroutine can be
@ called from outside this.
@ Input: bfp (global), r0 (compilation terminator)
@ Output: r0 (pointer to termination string)
compile:
push {lr}
@ Move delimiter somewhere else
mov r8, r0
@ Save initial exec point in case we need to backup
bl execmem_get
push {r0}
@ There are 4 valid tokens that can be found: interpretable symbols,
@ compilable symbols, immediates and terminators. Watch out, this is
@ the meat:
@ 1. Check if the token is a symbol, if its not, jump to 3.
@ 2. If it's interpretable jump to 6, if not (compilable) jump to 7.
@ 3. Is it a delimiter? then *finish gracefuly*. Otherwise, continue.
@ 4. Parse a number. If it fails, *finish on error*; ow continue.
@ 5. Emit immediate loading code and go back to 1.
@ 6. Emit code to jump to subroutine and go back to 1.
@ 7. Run the code emiting subroutine (comp token) and go back to 1.
.Lcompile_restart:
strtok #0x20
mov r6, r1 @ Save token length for immediate parsing
bl symtable_restart
bl symtable_lookup
beq .Lcompile_lookup_failed
@ symtable_lookup found something, is it interpretated or compiled?
push {r0}
bl symtable_getflag_interp
pop {r0}
bne .Lcompile_interpretation
beq .Lcompile_compilation
.Lcompile_lookup_failed:
@ We look now for terminators. Note the terminators structure. It's
@ a NULL-terminated list of NULL-terminated strings.
mov r1, r8
.Lcompile_terminator_restart:
@ Try terminator
bl strcmp
moveq r0, r1
beq .Lcompile_end
@ Terminator failed, chomp it
.Lcompile_eating_terminator:
ldrb r2, [r1], #1
cmp r2, #0
bne .Lcompile_eating_terminator
@ Any other delimiter to test?
ldrb r2, [r1]
cmp r2, #0
beq .Lcompile_terminators_failed
b .Lcompile_terminator_restart
.Lcompile_terminators_failed:
@ Try immediate
mov r1, r6
bl parse_num
mov r1, r0
beq .Lcompile_end_fail
@ Immediate success
bl execmem_get
bl compile_load32
ldr r1, helpers_str_r0_vsp
str r1, [r0], #4
bl execmem_store
b .Lcompile_restart
.Lcompile_interpretation:
bl symtable_get_fun
mov r1, r0
bl execmem_get
bl compile_load32
@ r0 is finally constructed, branch and link
GMOV R, #14, #15
GBX #0
bl execmem_store
b .Lcompile_restart
.Lcompile_compilation:
bl symtable_get_fun
push {r8}
mov lr, pc
bx r0
pop {r8}
b .Lcompile_restart
@ If we have failed, we restore the execmem pointer
.Lcompile_end_fail:
pop {r0}
bl execmem_store
push {r0}
.Lcompile_end:
pop {lr} @ We don't care about this value
pop {lr}
bx lr
compile_if:
push {lr}
bl execmem_get
ldr r1, helpers_ldr_r0_vsp
str r1, [r0], #4
ldr r1, helpers_cmp_r0_0
str r1, [r0], #4
@ TO FILL: IF-FALSE Entry
mov r4, r0
add r0, #4 @ Space for branch instruction
bl execmem_store
ldr r0, .LTERMIN_IF_ELSE
push {r4}
bl compile
mov r6, r0
pop {r4}
bl execmem_get
@ TO FILL: IF-TRUE Exit
mov r5, r0
add r0, #4 @ Space for branch instruction
@ FILL: IF-FALSE Entry
GB EQ, r4, r0
@ Was it IF .. THEN or IF .. ELSE .. THEN ?
ldr r2, .LTERMIN_IF_THEN
cmp r2, r6
@ If it was a THEN, then we dont need to fill a branch to skip the
@ FALSE part, it doesn't exist at all
@ Note that we have left a space for a branch instruction that won't be
@ filled. That's fine, as the branch that skip the IF .. THEN part
@ expects it to be there, and 0x0 is just a nop on ARM.
@ Of course, this should be optimized in the future, but seems minor
@ right now.
bleq execmem_store
beq .Lcompile_if_short
ldr r0, .LTERMIN_IF_THEN
push {r5}
bl compile
pop {r5}
bl execmem_get
@ FILL: IF-TRUE Exit
@ Note we don't need to execmem_store, as the space was already alloc
GB AL, r5, r0
.Lcompile_if_short:
pop {lr}
bx lr
.Lcompilation_pointer:
.local compilation_pointer
.word compilation_pointer
.lcomm compilation_pointer, 4
compile_do:
push {lr}
@ $r0 gets the starting point and $r1 the end. The cmp is not done
@ until the end though.
bl execmem_get
ldr r1, helpers_ldr_r0_vsp
str r1, [r0], #4
ldr r1, helpers_ldr_r1_vsp
str r1, [r0], #4
mov r2, r0 @ This is where we jump back from LOOP
GPUSH #0x0, #0x3
bl execmem_store
push {r2}
.Lcompile_do_compile:
ldr r0, .LTERMIN_DO_LOOP
bl compile
ldr r1, .LTERMIN_DO_LOOP
cmp r1, r0
beq .Lcompile_do_done
@ No other terminator defined other than I
bl execmem_get
ldr r1, helpers_str_r0_vsp
str r1, [r0], #4
bl execmem_store
b .Lcompile_do_compile
.Lcompile_do_done:
pop {r2}
@ Pop the counters, add 1 to the starting point and compare
bl execmem_get
GPOP #0x0, #0x3
ldr r1, helpers_add_r0_1
str r1, [r0], #4
ldr r1, helpers_cmp_r0_r1
str r1, [r0], #4
GB NE, r0, r2
bl execmem_store
pop {lr}
bx lr
.align 2
.LTERMIN_IF_ELSE:
.word .LCTERMIN_IF_ELSE
.LTERMIN_IF_THEN:
.word .LCTERMIN_IF_THEN
.LTERMIN_DO_LOOP:
.word .LCTERMIN_DO_LOOP
.LTERMIN_DO_I: