Improve build instructions in README
[rrq/jonasforth.git] / README.md
1 # Building and running
2
3 You can run JONASFORTH inside QEMU or on real hardware. If you want to run
4 inside QEMU, you should have the following dependencies installed (assuming
5 Arch Linux):
6
7     $ pacman -S qemu ovmf
8
9 Then, to run a UEFI shell inside QEMU, run:
10
11     $ make qemu
12
13 JONASFORTH will be available as `main` on `FS0:`. Thus, to run it, you can run
14 the following command inside the UEFI shell:
15
16     Shell> fs0:main
17     Ready.
18     S" Hello, World!" TELL
19     Hello World!
20
21 (Try typing in the code in `example.f` for something a little more
22 interesting.)
23
24 ## Running on real hardware
25
26 * [ ] This is not supported yet
27
28 # Notes on implementation
29
30 The implementation is based on
31 [JONESFORTH](https://raw.githubusercontent.com/nornagon/jonesforth/master/jonesforth.S).
32 This is my summary of the most important parts.
33
34 ## Dictionary
35
36 In Forth, words are stored in a dictionary. The dictionary is a linked list
37 whose entries look like this:
38
39     +------------------------+--------+---------- - - - - +----------- - - - -
40     | LINK POINTER           | LENGTH/| NAME              | DEFINITION
41     |                        | FLAGS  |                   |
42     +--- (4 bytes) ----------+- byte -+- n bytes  - - - - +----------- - - - -
43
44 For example, DOUBLE and QUADRUPLE may be stored like this:
45
46       pointer to previous word
47        ^
48        |
49     +--|------+---+---+---+---+---+---+---+---+------------- - - - -
50     | LINK    | 6 | D | O | U | B | L | E | 0 | (definition ...)
51     +---------+---+---+---+---+---+---+---+---+------------- - - - -
52        ^       len                         padding
53        |
54     +--|------+---+---+---+---+---+---+---+---+---+---+---+---+------------- - - - -
55     | LINK    | 9 | Q | U | A | D | R | U | P | L | E | 0 | 0 | (definition ...)
56     +---------+---+---+---+---+---+---+---+---+---+---+---+---+------------- - - - -
57        ^       len                                     padding
58        |
59        |
60     LATEST
61
62 The Forth variable LATEST contains a pointer to the most recently defined word.
63
64 ## Threaded code
65
66 In a typical Forth interpreter, code is stored in a peculiar way. (This way of
67 storing code is primarily motivated by space contraints on early systems.)
68
69 The definition of a word is stored as a sequence of memory adresses of each of
70 the words making up that definition. (At the end of a compiled definition, there
71 is also some extra code that causes execution to continue in the correct way.)
72
73 We use a register (ESI) to store a reference to the next index of the
74 word (inside a definition) that we are executing. Then, in order to execute a
75 word, we just jump to whatever address is pointed to by ESI. The code for
76 updating ESI and continuing execution is stored at the end of each subroutine.
77
78 Of course, this approach only works if each of the words that we are executing
79 is defined in assembly, but we also want to be able to execute Forth words!
80
81 We get around this problem by adding a "codeword" to the beginning of any
82 compiled subroutine. This codeword is a pointer to the intrepreter to run the
83 given function. In order to run such functions, we actually need two jumps when
84 executing: In order to execute a word, we jump to the address at the location
85 pointed to by the address in ESI.
86
87 ## Definitions
88
89 What does the codeword of a Forth word contain? It needs to save the old value
90 of ESI (so that we can resume execution of whatever outer definition we are
91 executing at the time) and set the new version of ESI to point to the first word
92 in the inner definition.
93
94 The stack where the values of ESI are stored is called the "return stack". We
95 will use EBP for the return stack.
96
97 As mentioned, whenever we finish executing a Forth word, we will need to
98 continue execution in the manner described in the previous section. When the
99 word being executed is itself written in Forth, we need to pop the old value of
100 ESI that we saved at the beginning of the definition before doing this.
101
102 Thus, the actual data for a word in a dictionary will look something like this:
103
104       pointer to previous word
105        ^
106        |
107     +--|------+---+---+---+---+---+---+---+---+------------+------------+------------+------------+
108     | LINK    | 6 | D | O | U | B | L | E | 0 | DOCOL      | DUP        | +          | EXIT       |
109     +---------+---+---+---+---+---+---+---+---+------------+--|---------+------------+------------+
110        ^       len                         pad  codeword      |
111        |                                                      V
112       LINK in next word                            points to codeword of DUP
113
114 Here, DOCOL (the codeword) is address of the simple interpreter described above,
115 while EXIT a word (implemented in assembly) that takes care of popping ESI and
116 continuing execution. Note that DOCOL, DUP, + and EXIT are all stored as
117 addresses which point to codewords.
118
119 ## Literals
120
121 Literals are handled in a special way. There is a word in Forth, called LIT,
122 implemented in assembly. When executed, this word looks at the next Forth
123 instruction (i.e. the value of ESI), and places that on the stack as a literal,
124 and then manipulates ESI to skip over the literal value.
125
126 ## Built-in variables
127
128 * **STATE** -- Is the interpreter executing code (0) or compiling a word (non-zero)?
129 * **LATEST** -- Points to the latest (most recently defined) word in the dictionary.
130 * **HERE** -- Points to the next free byte of memory.  When compiling, compiled words go here.
131 * **S0** -- Stores the address of the top of the parameter stack.
132 * **BASE** -- The current base for printing and reading numbers.
133
134 ## Input and lookup
135
136 `WORD` reads a word from standard input and pushes a string (in the form of an
137 address followed by the length of the string) to the stack. (It uses an internal
138 buffer that is overwritten each time it is called.)
139
140 `FIND` takes a word as parsed by `WORD` and looks it up in the dictionary. It
141 returns the address of the dictionary header of that word if it is found.
142 Otherwise, it returns 0.
143
144 `>CFA` turns a dictionary pointer into a codeword pointer. This is used when
145 compiling.
146
147 ## Compilation
148
149 The Forth word INTERPRET runs in a loop, reading in words (with WORD), looking
150 them up (with FIND), turning them into codeword pointers (with >CFA) and then
151 deciding what to do with them.
152
153 In immediate mode (when STATE is zero), the word is simply executed immediately.
154
155 In compilation mode, INTERPRET appends the codeword pointer to user memory
156 (which is at HERE). However, if a word has the immediate flag set, then it is
157 run immediately, even in compile mode.
158
159 ### Definition of `:` and `;`
160
161 The word `:` starts by reading in the new word. Then it creates a new entry for
162 that word in the dictoinary, updating the contents of `LATEST`, to which it
163 appends the word `DOCOL`. Then, it switches to compile mode.
164
165 The word `;` simply appends `EXIT` to the currently compiling definition and
166 then switches back to immediate mode.
167
168 These words rely on `,` to append words to the currently compiling definition.
169 This word simply appends some literal value to `HERE` and moves the `HERE`
170 pointer forward.
171
172 # Notes on UEFI
173
174 `JONASFORTH` is runs without an operating system, instead using the facilities
175 provided by UEFI by running as a UEFI application. (Or rather, in the future it
176 hopefully will. Right now, it uses Linux.) This section contains some notes
177 about how this functionality is implemented.
178
179 ## Packaging and testing the image
180
181 UEFI expects a UEFI application to be stored in a FAT32 file system on a
182 GPT-partitioned disk.
183
184 Luckily, QEMU has a convenient way of making a subdirectory availabe as a
185 FAT-formatted disk (see [the relevant section in the QEMU User
186 Documentation](https://qemu.weilnetz.de/doc/qemu-doc.html#disk_005fimages_005ffat_005fimages)
187 for more information):
188
189     $ qemu-sytem-x86_64 ... -hda fat:/some/directory
190
191 We use this to easily test the image in QEMU; see the Makefile for more
192 information, or just run the `qemu` target to run the program inside of QEMU
193 (of course, you must have QEMU installed for this to work):
194
195     $ make qemu
196
197 * [ ] How to build the image for real hardware (what should the image look like,
198   which programs, commands, etc.)
199
200 ## Interfacing with UEFI
201
202 From [OSDev Wiki](https://wiki.osdev.org/UEFI#How_to_use_UEFI):
203
204 >Traditional operating systems like Windows and Linux have an existing software
205 >architecture and a large code base to perform system configuration and device
206 >discovery. With their sophisticated layers of abstraction they don't directly
207 >benefit from UEFI. As a result, their UEFI bootloaders do little but prepare
208 >the environment for them to run.
209 >
210 >An independent developer may find more value in using UEFI to write
211 >feature-full UEFI applications, rather than viewing UEFI as a temporary
212 >start-up environment to be jettisoned during the boot process. Unlike legacy
213 >bootloaders, which typically interact with BIOS only enough to bring up the OS,
214 >a UEFI application can implement sophisticated behavior with the help of UEFI.
215 >In other words, an independent developer shouldn't be in a rush to leave
216 >"UEFI-land".
217
218 For `JONASFORTH`, I have decided to run as a UEFI application, taking advantage
219 of UEFI's features, including its text I/O features and general graphical device
220 drivers. Eventually, we would like to add some basic graphical drawing
221 capabilities to `JONASFORTH`, and it's my impression that this would be possible
222 using what is provided to us by UEFI.
223
224 A UEFI images is basically a windows EXE without symbol tables. There are three
225 types of UEFI images; we use the EFI application, which has subsystem `10`. It
226 is an x68-64 image, which has value `0x8664`.
227
228 UEFI applications use [Microsoft's 64-bit calling
229 convention](https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention)
230 for x68-64 functions. See the linked article for a full description. Here is
231 the short version:
232
233 * Integer or pointer arguments are given in RCX, RDX, R8 and R9.
234 * Additional arguments are pushed onto the stack from right to left.
235 * Integer or pointer values are returned in RAX.
236 * An integer-sized struct is passed directly; non-integer-sized structs are passed as pointers.
237 * The caller must allocate 32 bytes of "shadow space" on the stack immediately
238   before calling the function, regardless of the number of parameters used, and
239   the caller is responsible for popping the stack afterwards.
240 * The following registers are volatile (caller-saved): RAX, RCX, RDX, R8, R9, R10, R11
241 * The following registers are nonvolatile (callee-saved): RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15
242
243 When the application is loaded, RCX contains a firmware allocated `EFI_HANDLE`
244 for the UEFI image, RDX contains a `EFI_SYSTEM_TABLE*` pointer to the EFI system
245 table and RSP contains the return address. For more infromation about how a UEFI
246 application is entered, see "4 - EFI System Table" in [the latest UEFI
247 specification as of March 2020 (PDF)](https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_A_Feb14.pdf).
248
249 **Sources:**
250
251 * [UEFI applications in detail - OSDev Wiki](https://wiki.osdev.org/UEFI#UEFI_applications_in_detail)
252 * [Microsoft x64 calling convention](https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention)
253 * [UEFI Specifications](https://uefi.org/specifications)
254
255 ### UEFI with FASM
256
257 We might want to consider using something like this: https://wiki.osdev.org/Uefi.inc)
258
259 FASM can generate UEFI application binaries by default. Use the following
260 template to output a 64-bit UEFI application:
261
262     format pe64 dll efi
263     entry main
264
265     section '.text' code executable readable
266
267     main:
268        ;; ...
269        ret
270
271     section '.data' data readable writable
272
273     ;; ...
274
275 Use `objdump -x` to inspect the assembled application binary.
276
277 ### UEFI documentation
278
279 * [Latest specification as of March 2020 (PDF)](https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_A_Feb14.pdf)
280
281 Notable sections:
282
283 * 2\. Overview (14)
284 * 4\. EFI System Table (89)
285 * 7\. Services - Boot Services (140)
286 * 8\. Services - Runtime Services (228)
287 * 12\. Protocols - Console Support (429)
288 * 13\. Protocols - Media Access (493)
289 * Appendix B - Console (2201)
290 * Appendix D - Status Codes (2211)
291
292 ## Resources
293
294 * [UEFI - OSDev Wiki](https://wiki.osdev.org/UEFI)
295 * [Unified Extensible Firmware Interface (Wikipedia)](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface)
296 * [UEFI Specifications](https://uefi.org/specifications)