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