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