Make S" work inside compiled words
[rrq/jonasforth.git] / main.asm
1 ;; vim: syntax=fasm
2
3 include "uefi.asm"
4
5 ;; "Syscalls" {{{
6
7 ;; [NOTE] Volatile registers Linux (syscalls) vs UEFI
8 ;;
9 ;;   Linux syscalls: RAX, RCX, R11
10 ;;   UEFI:           RAX, RCX, R11, RDX, R8, R9, R10
11
12 ;; We are in the process of replacing our dependency on Linux with a dependency
13 ;; on UEFI. The following macros attempt to isolate what would be syscalls in
14 ;; Linux; thus, we will be able to replace these with UEFI-based implementations,
15 ;; and in theory we should expect the program to work.
16
17 ;; Print a string of a given length.
18 ;;
19 ;; Input:
20 ;; - RCX = Pointer to buffer
21 ;; - RDX = Buffer length
22 ;;
23 ;; Clobbers: RAX, RCX, R11, RDI, RSI
24 macro sys_print_string {
25   push r8
26   push r9
27   push r10
28
29   call uefi_print_string
30
31   pop r10
32   pop r9
33   pop r8
34 }
35
36 ;; Read a character from the user into the given buffer.
37 ;;
38 ;; Input:
39 ;; - RSI = Character buffer
40 ;;
41 ;; Output:
42 ;; - BYTE [RSI] = Character
43 ;;
44 ;; Clobbers: RAX, RCX, R11, RDI, RSI, RDX
45 macro sys_read_char {
46   push rbx
47   push r8
48   push r9
49   push r10
50   push r15
51
52   mov rcx, rsi
53   call uefi_read_char
54
55   pop r15
56   pop r10
57   pop r9
58   pop r8
59   pop rbx
60 }
61
62 macro sys_terminate code {
63   mov rax, code
64   call uefi_terminate
65 }
66
67 ;; }}}
68
69 ;; The code in this macro is placed at the end of each Forth word. When we are
70 ;; executing a definition, this code is what causes execution to resume at the
71 ;; next word in that definition.
72 macro next {
73   ;; RSI points to the address of the definition of the next word to execute.
74   lodsq                   ; Load value at RSI into RAX and increment RSI
75   ;; Now RAX contains the location of the next word to execute. The first 8
76   ;; bytes of this word is the address of the codeword, which is what we want
77   ;; to execute.
78   jmp qword [rax]         ; Jump to the codeword of the current word
79 }
80
81 ;; pushr and popr work on the return stack, whose location is stored in the
82 ;; register RBP.
83 macro pushr x {
84   sub rbp, 8
85   mov qword [rbp], x
86 }
87 macro popr x {
88   mov x, [rbp]
89   add rbp, 8
90 }
91
92 ;; The following macro generates the dictionary header. It updates the
93 ;; initial_latest_entry variable, which is used as the initial value of the
94 ;; latest_entry variable that is made available at runtime.
95 ;;
96 ;; The header contains a link to the previous entry, the length of the name of
97 ;; the word and the word itself as a string literal.
98 ;;
99 ;; This macro also defines a label LABEL_entry.
100 initial_latest_entry = 0
101 macro header label, name, immediate {
102   local .string_end
103
104 label#_entry:
105   dq initial_latest_entry
106   if immediate eq
107     db 0
108   else
109     db 1
110   end if
111   db .string_end - ($ + 1)
112   db name
113   .string_end:
114 label:
115
116 initial_latest_entry = label#_entry
117 }
118
119 ;; Define a Forth word that is implemented in assembly. See 'header' for details.
120 macro forth_asm label, name, immediate {
121   header label, name, immediate
122   dq .start
123 .start:
124 }
125
126 section '.text' code readable executable
127
128 include "impl.asm"      ; Misc. subroutines
129 include "bootstrap.asm" ; Forth words encoded in Assembly
130
131 main:
132   cld                        ; Clear direction flag so LODSQ does the right thing.
133   mov rbp, return_stack_top  ; Initialize return stack
134
135   call uefi_initialize
136
137   mov rax, MAIN
138   jmp qword [rax]
139
140 program: dq MAIN
141
142 ;; The codeword is the code that will be executed at the beginning of a forth
143 ;; word. It needs to save the old RSI and update it to point to the next word to
144 ;; execute.
145 header DOCOL, 'DOCOL'
146   pushr rsi            ; Save old value of RSI on return stack; we will continue execution there after we are done executing this word
147   lea rsi, [rax + 8]   ; RAX currently points to the address of the codeword, so we want to continue at RAX+8
148   next                 ; Execute word pointed to by RSI
149
150 ;; This word is called at the end of a Forth definition. It just needs to
151 ;; restore the old value of RSI (saved by 'DOCOL') and resume execution.
152 forth_asm EXIT, 'EXIT'
153   popr rsi
154   next
155
156 ;; LIT is a special word that reads the next "word pointer" and causes it to be
157 ;; placed on the stack rather than executed.
158 forth_asm LIT, 'LIT'
159   lodsq
160   push rax
161   next
162
163 ;; When LITSTRING is encountered while executing a word, it instead reads a
164 ;; string from the definition of that word, and places that string on the stack
165 ;; as (buffer, length).
166 forth_asm LITSTRING, 'LITSTRING'
167   lodsb
168   push rsi ; Buffer
169   movzx rax, al
170   push rax ; Length
171   add rsi, rax ; Skip over string before resuming execution
172   next
173
174 ;; Given a string (a pointer following by a size), return the location of the
175 ;; dictionary entry for that word. If no such word exists, return 0.
176 forth_asm FIND, 'FIND'
177   mov [.rsi], rsi
178
179   pop [find.search_length]
180   pop [find.search_buffer]
181   mov rsi, [latest_entry]       ; Start with the last added word
182   call find
183   push rsi
184
185   mov rsi, [.rsi]
186   next
187   push rsi
188
189   mov rsi, [.rsi]
190   next
191
192 ;; Given an entry in the dictionary, return a pointer to the codeword of that
193 ;; entry.
194 forth_asm TCFA, '>CFA'
195   pop rax
196   add rax, 8 + 1                ; [rax] = length of name
197   movzx rbx, byte [rax]
198   inc rax
199   add rax, rbx                  ; [rax] = codeword
200   push rax
201   next
202
203 ;; BRANCH is the fundamental mechanism for branching. BRANCH reads the next word
204 ;; as a signed integer literal and jumps by that offset.
205 forth_asm BRANCH, 'BRANCH'
206   add rsi, [rsi] ; [RSI], which is the next word, contains the offset; we add this to the instruction pointer.
207   next           ; Then, we can just continue execution as normal
208
209 ;; 0BRANCH is like BRANCH, but it jumps only if the top of the stack is zero.
210 forth_asm ZBRANCH, '0BRANCH'
211   ;; Compare top of stack to see if we should branch
212   pop rax
213   cmp rax, 0
214   jnz .dont_branch
215 .do_branch:
216   jmp BRANCH.start
217 .dont_branch:
218   add rsi, 8     ; We need to skip over the next word, which contains the offset.
219   next
220
221 ;; Duplicate the top of the stack.
222 forth_asm DUP_, 'DUP'
223   push qword [rsp]
224   next
225
226 ;; Execute the codeword at the given address.
227 forth_asm EXEC, 'EXEC'
228   pop rax
229   jmp qword [rax]
230
231 ;; Expects a character on the stack and prints it to standard output.
232 forth_asm EMIT, 'EMIT'
233   pushr rsi
234   pushr rax
235
236   lea rcx, [rsp]
237   mov rdx, 1
238   sys_print_string
239
240   add rsp, 8
241   popr rax
242   popr rsi
243   next
244
245 ;; Read a single character from the current input stream. Usually, this will wait
246 ;; for the user to press a key, and then return the corresponding character. When
247 ;; reading from a special buffer, it will instead return the next characater from
248 ;; that buffer.
249 ;;
250 ;; The ASCII character code is placed on the stack.
251 forth_asm KEY, 'KEY'
252   call .impl
253   push rax
254   next
255
256 ;; Result in RAX
257 .impl:
258   ;; Are we reading from user input or from the input buffer?
259   cmp [input_buffer], 0
260   jne .from_buffer
261
262   ;; Reading user input
263   push rsi
264   mov rsi, .buffer
265   sys_read_char
266   pop rsi
267
268   movzx rax, byte [.buffer]
269   ret
270
271 .from_buffer:
272   ;; Reading from buffer
273   mov rax, [input_buffer]
274   movzx rax, byte [rax]
275
276   inc [input_buffer]
277   dec [input_buffer_length]
278   ret
279
280 ;; Read a word and push it onto the stack as a pointer and a size. The pointer
281 ;; is valid until the next call to READ_WORD.
282 forth_asm READ_WORD, 'READ-WORD'
283   push rsi
284 .skip_whitespace:
285   ;; Read characters until one of them is not whitespace.
286   call KEY.impl
287   ;; We consider newlines and spaces to be whitespace.
288   cmp al, ' '
289   je .skip_whitespace
290   cmp al, $A
291   je .skip_whitespace
292
293   ;; We got a character that wasn't whitespace. Now read the actual word.
294   mov [.length], 0
295
296 .read_alpha:
297   movzx rbx, [.length]
298   mov rsi, .buffer
299   add rsi, rbx
300   mov [rsi], al
301   inc [.length]
302
303   call KEY.impl
304
305   cmp al, ' '
306   je .end
307   cmp al, $A
308   jne .read_alpha
309
310 .end:
311   pop rsi
312   push .buffer
313   movzx rax, [.length]
314   push rax
315
316   next
317
318 ;; Takes a string on the stack and replaces it with the decimal number that the
319 ;; string represents.
320 forth_asm PARSE_NUMBER, 'PARSE-NUMBER'
321   pop rcx     ; Length
322   pop rdi     ; String pointer
323
324   push rsi
325   call parse_number
326   pop rsi
327
328   push rax                      ; Result
329   next
330
331 ;; Takes a string (in the form of a pointer and a length on the stack) and
332 ;; prints it to standard output.
333 forth_asm TELL, 'TELL'
334   pushr rax
335   pushr rsi
336
337   pop rdx ; Length
338   pop rcx ; Buffer
339   sys_print_string
340
341   popr rsi
342   popr rax
343   next
344
345 ;; Exit the program cleanly.
346 forth_asm TERMINATE, 'TERMINATE'
347   sys_terminate 0
348
349 ;; Duplicate a pair of elements.
350 forth_asm PAIRDUP, '2DUP'
351   pop rbx
352   pop rax
353   push rax
354   push rbx
355   push rax
356   push rbx
357   next
358
359 ;; Swap the top two elements on the stack.
360 forth_asm SWAP, 'SWAP'
361   pop rax
362   pop rbx
363   push rax
364   push rbx
365   next
366
367 ;; Remove the top element from the stack.
368 forth_asm DROP, 'DROP'
369   add rsp, 8
370   next
371
372 forth_asm NOT_, 'NOT'
373   pop rax
374   cmp rax, 0
375   jz .false
376 .true:
377   push 0
378   next
379 .false:
380   push 1
381   next
382
383 ;; .U prints the value on the stack as an unsigned integer in hexadecimal.
384 forth_asm DOTU, '.U'
385   mov [.length], 0
386   mov [.printed_length], 1
387   pop rax                       ; RAX = value to print
388   push rsi                      ; Save value of RSI
389
390   ;; We start by constructing the buffer to print in reverse
391
392 .loop:
393   mov rdx, 0
394   mov rbx, $10
395   div rbx                       ; Put remainer in RDX and quotient in RAX
396
397   ;; Place the appropriate character in the buffer
398   mov rsi, .chars
399   add rsi, rdx
400   mov bl, [rsi]
401   mov rdi, .rbuffer
402   add rdi, [.length]
403   mov [rdi], bl
404   inc [.length]
405
406   ;; .printed_length is the number of characters that we ulitmately want to
407   ;; print. If we have printed a non-zero character, then we should update
408   ;; .printed_length.
409   cmp bl, '0'
410   je .skip_updating_real_length
411   mov rbx, [.length]
412   mov [.printed_length], rbx
413 .skip_updating_real_length:
414
415   cmp [.length], 16
416   jle .loop
417
418   ;; Flip buffer around, since it is currently reversed
419   mov rcx, [.printed_length]
420 .flip:
421   mov rsi, .rbuffer
422   add rsi, rcx
423   dec rsi
424   mov al, [rsi]
425
426   mov rdi, .buffer
427   add rdi, [.printed_length]
428   sub rdi, rcx
429   mov [rdi], al
430
431   loop .flip
432
433   ;; Print the buffer
434   mov rcx, .buffer
435   mov rdx, [.printed_length]
436   sys_print_string
437
438   ;; Restore RSI and continue execution
439   pop rsi
440   next
441
442 ;; Takes a value and an address, and stores the value at the given address.
443 forth_asm PUT, '!'
444   pop rbx                       ; Address
445   pop rax                       ; Value
446   mov [rbx], rax
447   next
448
449 ;; Takes an address and returns the value at the given address.
450 forth_asm GET, '@'
451   pop rax
452   mov rax, [rax]
453   push rax
454   next
455
456 forth_asm PUT_BYTE, 'C!'
457   pop rbx
458   pop rax                       ; Value
459   mov [rbx], al
460   next
461
462 forth_asm GET_BYTE, 'C@'
463   pop rax
464   movzx rax, byte [rax]
465   push rax
466   next
467
468 ;; Add two integers on the stack.
469 forth_asm PLUS, '+'
470   pop rax
471   pop rbx
472   add rax, rbx
473   push rax
474   next
475
476 ;; Calculate difference between two integers on the stack. The second number is
477 ;; subtracted from the first.
478 forth_asm MINUS, '-'
479   pop rax
480   pop rbx
481   sub rbx, rax
482   push rbx
483   next
484
485 ;; Given two integers a and b on the stack, pushes the quotient and remainder of
486 ;; division of a by b.
487 forth_asm TIMESMOD, '/MOD'
488   pop rbx                       ; b
489   pop rax                       ; a
490   mov rdx, 0
491   div rbx
492   push rax                      ; a / b
493   push rdx                      ; a % b
494   next
495
496 ;; Read input until next " character is found. Push a string containing the
497 ;; input on the stack as (buffer length). Note that the buffer is only valid
498 ;; until the next call to S" and that no more than 255 characters can be read.
499 forth_asm READ_STRING, 'S"'
500   ;; If the input buffer is set, we should read from there instead.
501   cmp [input_buffer], 0
502   jne read_string_buffer
503
504   push rsi
505
506   mov [.length], 0
507
508 .read_char:
509   mov rsi, .char_buffer
510   sys_read_char
511
512   mov al, [.char_buffer]
513   cmp al, '"'
514   je .done
515
516   mov rdx, .buffer
517   add rdx, [.length]
518   mov [rdx], al
519   inc [.length]
520   jmp .read_char
521
522 .done:
523   pop rsi
524
525   push .buffer
526   push [.length]
527
528   next
529
530 read_string_buffer:
531   push rsi
532
533   ;; We borrow READ_STRING's buffer. They won't mind.
534   mov [READ_STRING.length], 0
535
536 .read_char:
537   mov rbx, [input_buffer]
538   mov al, [rbx]
539   cmp al, '"'
540   je .done
541
542   mov rdx, READ_STRING.buffer
543   add rdx, [READ_STRING.length]
544   mov [rdx], al
545   inc [READ_STRING.length]
546
547   inc [input_buffer]
548   dec [input_buffer_length]
549
550   jmp .read_char
551
552 .done:
553   pop rsi
554
555   ;; Skip closing "
556   inc [input_buffer]
557   dec [input_buffer_length]
558
559   push READ_STRING.buffer
560   push [READ_STRING.length]
561
562   next
563
564 ;; CREATE inserts a new header in the dictionary, and updates LATEST so that it
565 ;; points to the header. To compile a word, the user can then call ',' to
566 ;; continue to append data after the header.
567 ;;
568 ;; It takes the name of the word as a string (address length) on the stack.
569 forth_asm CREATE, 'CREATE'
570   pop rcx                       ; Word string length
571   pop rdx                       ; Word string pointer
572
573   mov rdi, [here]               ; rdi = Address at which to insert this entry
574   mov rax, [latest_entry]       ; rax = Address of the previous entry
575   mov [rdi], rax                ; Insert link to previous entry
576   mov [latest_entry], rdi       ; Update LATEST to point to this word
577
578   add rdi, 8
579   mov [rdi], byte 0             ; Insert immediate flag
580
581   add rdi, 1
582   mov [rdi], byte cl            ; Insert length
583
584   ;; Insert word string
585   add rdi, 1
586
587   push rsi
588   mov rsi, rdx                  ; rsi = Word string pointer
589   rep movsb
590   pop rsi
591
592   ;; Update HERE
593   mov [here], rdi
594
595   next
596
597 forth_asm TICK, "'"
598   lodsq
599   push rax
600   next
601
602 forth_asm ROT, 'ROT'
603   pop rax
604   pop rbx
605   pop rdx
606   push rax
607   push rdx
608   push rbx
609   next
610
611 forth_asm PICK, 'PICK'
612   pop rax
613   lea rax, [rsp + 8 * rax]
614   mov rax, [rax]
615   push rax
616   next
617
618 forth_asm EQL, '='
619   pop rax
620   pop rbx
621   cmp rax, rbx
622   je .eq
623 .noteq:
624   push 0
625   next
626 .eq:
627   push 1
628   next
629
630 forth MAIN, 'MAIN'
631   dq SYSCODE
632   dq INTERPRET_STRING
633   dq INTERPRET
634   dq BRANCH, -8 * 2
635   dq TERMINATE
636
637 ;; Built-in variables:
638
639 forth STATE, 'STATE'
640   dq LIT, var_STATE
641   dq EXIT
642
643 forth LATEST, 'LATEST'
644   dq LIT, latest_entry
645   dq EXIT
646
647 forth HERE, 'HERE'
648   dq LIT, here
649   dq EXIT
650
651 forth SYSCODE, 'SYSCODE'
652   dq LIT, sysf
653   dq LIT, sysf.len
654   dq EXIT
655
656 forth INPUT_BUFFER, 'INPUT-BUFFER'
657   dq LIT, input_buffer
658   dq EXIT
659
660 forth INPUT_LENGTH, 'INPUT-LENGTH'
661   dq LIT, input_buffer_length
662   dq EXIT
663
664 section '.data' readable writable
665
666 ;; The LATEST variable holds a pointer to the word that was last added to the
667 ;; dictionary. This pointer is updated as new words are added, and its value is
668 ;; used by FIND to look up words.
669 latest_entry dq initial_latest_entry
670
671 ;; The STATE variable is 0 when the interpreter is executing, and non-zero when
672 ;; it is compiling.
673 var_STATE dq 0
674
675 ;; The interpreter can read either from standard input or from a buffer. When
676 ;; input-buffer is set (non-null), words like READ-WORD and S" will use this
677 ;; buffer instead of reading user input.
678 input_buffer dq 0
679 input_buffer_length dq 0
680
681 FIND.rsi dq ?
682
683 READ_WORD.rsi dq ?
684 READ_WORD.rbp dq ?
685
686 READ_STRING.char_buffer db ?
687 READ_STRING.buffer rb $FF
688 READ_STRING.length dq ?
689
690 DOTU.chars db '0123456789ABCDEF'
691 DOTU.buffer rq 16               ; 64-bit number has no more than 16 digits in hex
692 DOTU.rbuffer rq 16
693 DOTU.length dq ?
694 DOTU.printed_length dq ?
695
696 KEY.buffer dq ?
697
698 READ_WORD.buffer rb $FF
699 READ_WORD.length db ?
700
701 ;; Reserve space for compiled words, accessed through HERE.
702 here dq here_top
703 here_top rq $4000
704
705 ;; Return stack
706 rq $2000
707 return_stack_top:
708
709 ;; We store some Forth code in sys.f that defined common words that the user
710 ;; would expect to have available at startup. To execute these words, we just
711 ;; include the file directly in the binary, and then interpret it at startup.
712 sysf file 'sys.f'
713 sysf.len = $ - sysf
714