https://github.com/akkartik/mu/blob/master/apps/calls.subx
  1 # Function calls in a single line.
  2 #
  3 # To run (on Linux):
  4 #   $ ./ntranslate 0*.subx apps/subx-common.subx apps/calls.subx
  5 #   $ mv a.elf apps/calls
  6 #   $ chmod +x apps/calls
  7 #
  8 # Example 1:
  9 #   $ echo '(foo %eax)'                         |  apps/calls
 10 #   # . (foo %eax)                                      # output has comments
 11 #   ff 6/subop/push %eax                                # push
 12 #   e8/call foo/disp32                                  # call
 13 #   81 0/subop/add %esp 4/imm32                         # undo push
 14 #
 15 # Example 2:
 16 #   $ echo '(foo Var1 *(eax + 4) "blah")'       |  apps/calls
 17 #   # . (foo Var1 *(eax + 4) "blah")
 18 #   68/push "blah"/imm32
 19 #   ff 6/subop/push *(eax + 4)                          # push args in..
 20 #   68/push Var1/imm32                                  # ..reverse order
 21 #   e8/call foo/disp32
 22 #   81 0/subop/add %esp 4/imm32                         # undo pushes
 23 #
 24 # Calls always begin with '(' as the first non-whitespace on a line.
 25 
 26 == code
 27 
 28 Entry:  # run tests if necessary, convert stdin if not
 29     # . prolog
 30     89/<- %ebp 4/r32/esp
 31 
 32     # initialize heap
 33     # . Heap = new-segment(Heap-size)
 34     # . . push args
 35     68/push Heap/imm32
 36     ff 6/subop/push *Heap-size
 37     # . . call
 38     e8/call new-segment/disp32
 39     # . . discard args
 40     81 0/subop/add %esp 8/imm32
 41 
 42     # - if argc > 1 and argv[1] == "test", then return run_tests()
 43     # if (argc <= 1) goto run-main
 44     81 7/subop/compare *ebp 1/imm32
 45     7e/jump-if-lesser-or-equal $run-main/disp8
 46     # if (!kernel-string-equal?(argv[1], "test")) goto run-main
 47     # . eax = kernel-string-equal?(argv[1], "test")
 48     # . . push args
 49     68/push "test"/imm32
 50     ff 6/subop/push *(ebp+8)
 51     # . . call
 52     e8/call kernel-string-equal?/disp32
 53     # . . discard args
 54     81 0/subop/add %esp 8/imm32
 55     # . if (eax == 0) goto run-main
 56     3d/compare-eax-and 0/imm32
 57     74/jump-if-equal $run-main/disp8
 58     # run-tests()
 59     e8/call run-tests/disp32
 60     # syscall(exit, *Num-test-failures)
 61     8b/-> *Num-test-failures 3/r32/ebx
 62     eb/jump $main:end/disp8
 63 $run-main:
 64     # - otherwise convert stdin
 65     # convert(Stdin, Stdout)
 66     # . . push args
 67     68/push Stdout/imm32
 68     68/push Stdin/imm32
 69     # . . call
 70     e8/call convert/disp32
 71     # . . discard args
 72     81 0/subop/add %esp 8/imm32
 73     # syscall(exit, 0)
 74     bb/copy-to-ebx 0/imm32
 75 $main:end:
 76     b8/copy-to-eax 1/imm32/exit
 77     cd/syscall 0x80/imm8
 78 
 79 convert:  # in : (address buffered-file), out : (address buffered-file) -> <void>
 80     # pseudocode:
 81     #   var line = new-stream(512, 1)
 82     #   var words : (address stream slice) = new-stream(16, 8)  # at most function name and 15 args
 83     #   while true
 84     #     clear-stream(line)
 85     #     read-line-buffered(in, line)
 86     #     if (line->write == 0) break                           # end of file
 87     #     skip-chars-matching-whitespace(line)
 88     #     if line->data[line->read] != '('
 89     #       write-stream-data(out, line)
 90     #       continue
 91     #     # emit comment
 92     #     write-buffered(out, "# . ")
 93     #     write-stream-data(out, line)
 94     #     # emit code
 95     #     ++line->read  # skip '('
 96     #     clear-stream(words)
 97     #     words = parse-line(line)
 98     #     emit-call(out, words)
 99     #   flush(out)
100     #
101     # . prolog
102     55/push-ebp
103     89/<- %ebp 4/r32/esp
104     # . save registers
105     # var line/ecx : (address stream byte) = stream(512)
106     81 5/subop/subtract %esp 0x200/imm32
107     68/push 0x200/imm32/length
108     68/push 0/imm32/read
109     68/push 0/imm32/write
110     89/<- %ecx 4/r32/esp
111     # var words/edx : (address stream slice) = stream(16, 8)
112     81 5/subop/subtract %esp 0x80/imm32
113     68/push 0x80/imm32/length
114     68/push 0/imm32/read
115     68/push 0/imm32/write
116     89/<- %edx 4/r32/esp
117 $convert:loop:
118     # clear-stream(line)
119     # . . push args
120     51/push-ecx
121     # . . call
122     e8/call clear-stream/disp32
123     # . . discard args
124     81 0/subop/add %esp 4/imm32
125     # read-line-buffered(in, line)
126     # . . push args
127     51/push-ecx
128     ff 6/subop/push *(ebp+8)
129     # . . call
130     e8/call read-line-buffered/disp32
131     # . . discard args
132     81 0/subop/add %esp 8/imm32
133 $convert:check0:
134     # if (line->write == 0) break
135     81 7/subop/compare *ecx 0/imm32
136     0f 84/jump-if-equal $convert:break/disp32
137     # TODO
138     e9/jump $convert:loop/disp32
139 $convert:break:
140     # flush(out)
141     # . . push args
142     ff 6/subop/push *(ebp+0xc)
143     # . . call
144     e8/call flush/disp32
145     # . . discard args
146     81 0/subop/add %esp 4/imm32
147 $convert:end:
148     # . reclaim locals
149     81 0/subop/add %esp 0x298/imm32  # 0x20c + 0x8c
150     # . restore registers
151     # . epilog
152     89/<- %esp 5/r32/ebp
153     5d/pop-to-ebp
154     c3/return
155 
156 parse-line:  # line : (address stream byte), words : (address stream slice)
157     # pseudocode:
158     #   var word-slice : (address slice)
159     #   while true
160     #     word-slice = next-word-string-or-expression-without-metadata(line)
161     #     if slice-empty?(word-slice)
162     #       break                                 # end of line
163     #     write-int(words, word-slice->start)
164     #     write-int(words, word-slice->end)
165     #
166     # . prolog
167     55/push-ebp
168     89/<- %ebp 4/r32/esp
169     # . save registers
170 $parse-line:end:
171     # . reclaim locals
172     # . restore registers
173     # . epilog
174     89/<- %esp 5/r32/ebp
175     5d/pop-to-ebp
176     c3/return
177 
178 emit-call:  # out : (address buffered-file), words : (address stream slice)
179     # pseudocode:
180     #   if (words->write < 8) abort
181     #   curr = &words->data[words->write-8]
182     #   min = words->data
183     #   # emit pushes
184     #   while true
185     #     if (curr <= min) break
186     #     if *curr in '%' '*'
187     #       write-buffered(out, "ff 6/subop/push ")
188     #       write-slice-buffered(out, curr)
189     #       write-buffered(out, "/imm32\n")
190     #     else
191     #       write-buffered(out, "68/push ")
192     #       write-slice-buffered(out, curr)
193     #       write-buffered(out, "/imm32\n")
194     #     curr -= 8
195     #   # emit call
196     #   write-buffered(out, "e8/call ")
197     #   write-slice-buffered(out, curr)
198     #   write-buffered(out, "/disp32\n")
199     #   # emit pops
200     #   write-buffered(out, "81 0/subop/add %esp ")
201     #   print-int32-buffered(out, words->write >> 1 - 4)
202     #   write-buffered(out, "/imm32\n")
203     #
204     # . prolog
205     55/push-ebp
206     89/<- %ebp 4/r32/esp
207     # . save registers
208 $emit-call:end:
209     # . reclaim locals
210     # . restore registers
211     # . epilog
212     89/<- %esp 5/r32/ebp
213     5d/pop-to-ebp
214     c3/return
215 
216 next-word-string-or-expression-without-metadata:  # line : (address stream), word-slice : (address slice)
217     # pseudocode:
218     #   skip-chars-matching(line, ' ')
219     #   if line->read >= line->write              # end of line
220     #     out = {0, 0}
221     #     return
222     #   out->start = &line->data[line->read]
223     #   if line->data[line->read] == '#'          # comment
224     #     out.end = &line->data[line->write]      # skip to end of line
225     #     return
226     #   if line->data[line->read] == '"'          # string literal
227     #     skip-string(line)
228     #     out.end = &line->data[line->read]         # no metadata
229     #     return
230     #   if line->data[line->read] == '*'          # expression
231     #     if line->data[line->read + 1] == ' '
232     #       abort
233     #     if line->data[line->read + 1] == '('
234     #       skip-until-close-paren(line)
235     #       if (line->data[line->read] != ')'
236     #         abort
237     #       ++line->data[line->read] to skip ')'
238     #     out->end = &line->data[line->read]
239     #     return
240     #   if line->data[line->read] == ')'
241     #     ++line->read to skip ')'
242     #     # make sure there's nothing else of importance
243     #     if line->read >= line->write
244     #       out = {0, 0}
245     #       return
246     #     if line->data[line->read] != ' '
247     #       abort
248     #     skip-chars-matching-whitespace(line)
249     #     if line->read >= line->write
250     #       out = {0, 0}
251     #       return
252     #     if line->data[line->read] != '#'        # only thing permitted after ')' is a comment
253     #       abort
254     #     out.end = &line->data[line->write]      # skip to end of line
255     #     return
256     #   # default case: read a word -- but no metadata
257     #   while true
258     #     if line->read >= line->write
259     #       break
260     #     if line->data[line->read] == ' '
261     #       break
262     #     if line->data[line->read] == '/'
263     #       abort
264     #     ++line->read
265     #   out.end = &line->data[line->read]
266     #
267     # registers:
268     #   ecx: often line->read
269     #   eax: often line->data[line->read]
270     #
271     # . prolog
272     55/push-ebp
273     89/<- %ebp 4/r32/esp
274     # . save registers
275     50/push-eax
276     51/push-ecx
277     56/push-esi
278     57/push-edi
279     # skip-chars-matching(line, ' ')
280     # . . push args
281     68/push 0x20/imm32/space
282     ff 6/subop/push *(ebp+8)
283     # . . call
284     e8/call skip-chars-matching/disp32
285     # . . discard args
286     81 0/subop/add %esp 8/imm32
287 $next-word-string-or-expression-without-metadata:check0:
288     # if (line->read >= line->write) clear out and return
289     # . eax = line->read
290     8b/-> *(esi+4) 0/r32/eax
291     # . if (eax < line->write) goto next check
292     3b/compare *esi 0/r32/eax
293     7c/jump-if-lesser $next-word-string-or-expression-without-metadata:check-for-comment/disp8
294     # . return out = {0, 0}
295     c7 0/subop/copy *edi 0/imm32
296     c7 0/subop/copy *(edi+4) 0/imm32
297     e9/jump $next-word-string-or-expression-without-metadata:end/disp32
298 $next-word-string-or-expression-without-metadata:check-for-comment:
299     # out->start = &line->data[line->read]
300     8b/-> *(esi+4) 1/r32/ecx
301     8d/copy-address *(esi+ecx+0xc) 0/r32/eax
302     89/<- *edi 0/r32/eax
303     # if (line->data[line->read] != '#') goto next check
304     # . eax = line->data[line->read]
305     31/xor %eax 0/r32/eax
306     8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
307     # . if (eax != '#') goto next check
308     3d/compare-eax-and 0x23/imm32/pound
309     75/jump-if-not-equal $next-word-string-or-expression-without-metadata:check-for-string-literal/disp8
310 $next-word-string-or-expression-without-metadata:comment:
311     # out->end = &line->data[line->write]
312     8b/-> *esi 0/r32/eax
313     8d/copy-address *(esi+eax+0xc) 0/r32/eax
314     89/<- *(edi+4) 0/r32/eax
315     # line->read = line->write  # skip rest of line
316     8b/-> *esi 0/r32/eax
317     89/<- *(esi+4) 0/r32/eax
318     # return
319     eb/jump $next-word-string-or-expression-without-metadata:end/disp8
320 $next-word-string-or-expression-without-metadata:check-for-string-literal:
321     # if (line->data[line->read] != '"') goto next check
322     3d/compare-eax-and 0x22/imm32/dquote
323     75/jump-if-not-equal $next-word-string-or-expression-without-metadata:check-for-expression/disp8
324 $next-word-string-or-expression-without-metadata:string-literal:
325     # skip-string(line)
326     # . . push args
327     56/push-esi
328     # . . call
329     e8/call skip-string/disp32
330     # . . discard args
331     81 0/subop/add %esp 4/imm32
332     # out->end = &line->data[line->read]
333     8b/-> *(esi+4) 1/r32/ecx
334     8d/copy-address *(esi+ecx+0xc) 0/r32/eax
335     89/<- *(edi+4) 0/r32/eax
336     # return
337     eb/jump $next-word-string-or-expression-without-metadata:end/disp8
338 $next-word-string-or-expression-without-metadata:check-for-expression:
339     # if (line->data[line->read] != '*') goto next check
340     3d/compare-eax-and 0x2a/imm32/asterisk
341     75/jump-if-not-equal $next-word-string-or-expression-without-metadata:check-for-end-of-call/disp8
342     # if (line->data[line->read + 1] == ' ') goto error1
343     8a/copy-byte *(esi+ecx+0xd) 0/r32/AL
344     3d/compare-eax-and 0x20/imm32/space
345     74/jump-if-equal $next-word-string-or-expression-without-metadata:error1/disp8
346     # if (line->data[line->read] != '(') goto regular word
347     3d/compare-eax-and 0x28/imm32/open-paren
348     75/jump-if-not-equal $next-word-string-or-expression-without-metadata:regular-word/disp8
349 $next-word-string-or-expression-without-metadata:paren:
350     # skip-until-close-paren(line)
351     # . . push args
352     56/push-esi
353     # . . call
354     e8/call skip-until-close-paren/disp32
355     # . . discard args
356     81 0/subop/add %esp 4/imm32
357     # if (line->data[line->read] != ')') goto error2
358     # . eax = line->data[line->read]
359     8b/-> *(esi+4) 1/r32/ecx
360     8a/copy-byte *(esi+ecx+0xc) 0/r32/AL
361     # . if (eax != ')') goto error2
362     3d/compare-eax-and 0x29/imm32/close-paren
363     75/jump-if-not-equal $next-word-string-or-expression-without-metadata:error2/disp8
364     # skip ')'
365     ff 0/subop/increment *(esi+4)
366     # out->end = &line->data[line->read]
367     8b/-> *(esi+4) 1/r32/ecx
368     8d/copy-address *(esi+ecx+0xc) 0/r32/eax
369     89/<- *(edi+4) 0/r32/eax
370     # return
371     eb/jump $next-word-string-or-expression-without-metadata:end/disp8
372 $next-word-string-or-expression-without-metadata:end:
373     # . restore registers
374     5f/pop-to-edi
375     5e/pop-to-esi
376     59/pop-to-ecx
377     58/pop-to-eax
378     # . epilog
379     89/<- %esp 5/r32/ebp
380     5d/pop-to-ebp
381     c3/return
382 
383 # . . vim:nowrap:textwidth=0