snapshot before branching
[rrq/jonasforth.git] / README.md
index db57758760b723a5e8c1751ef4930f3408c207db..a54792500ef2033d4c963ad64bf7a57e9ba8eb42 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,33 +1,55 @@
 # Building and running
 
-Assemble and run the executable:
+You can run Jonasforth inside QEMU or on real hardware. If you want to run
+inside QEMU, you should have the following dependencies installed (assuming
+Arch Linux):
 
-    $ make main
-    $ ./main
+    $ pacman -S fasm qemu edk2-ovmf
 
-The `example.f` file contains an example that you can run with:
+Then, to run a UEFI shell inside QEMU, run:
 
-    $ cat sys.f example.f | ./main
+    $ make qemu
+
+JONASFORTH will be available as `main` on `FS0:`. Thus, to run it, you can run
+the following command inside the UEFI shell:
+
+    Shell> fs0:main
+    Ready.
+    : SAY-HELLO S" Hello, World!" TELL NEWLINE ;
+    SAY-HELLO
+    Hello World!
+
+(Try typing in the code in `lib/example.f` for something a little more
+interesting.)
 
-## Running with UEFI
+## Running on real hardware
 
-We are currently in the process of implementing support for running without
-Linux, by instead relying on UEFI. Eventually, this will be the only supported
-method of running the interpreter, but currently the UEFI-related code is
-isolated in the `uefi/` directory and does not yet contain an implementation of
-the main program.
+Making the program run on physical hardware is pretty easy. Just create a
+FAT32-formatted USB drive, and copy `out/main` to it. Then, you can execute the
+program in the same way that you did from inside QEMU, assuming your system
+comes with a UEFI shell built-in.
 
-You should have the following dependencies installed (assuming Arch Linux):
+If your system doesn't have a UEFI shell, then you can copy the executable to
+`\EFI\BOOT\BOOTx64.EFI` on the USB drive. Then, the system should be able to
+boot from the USB drive and directly into JONASFORTH. The way to do this is a
+little bit different depending on the exact firmware, but most firmwares will
+have some way to enter a boot menu where you can select the USB drive. You may
+need to disable Secure Boot first.
 
-    $ pacman -S qemu ovmf
+To format a USB drive as FAT32, you can run
 
-To run a UEFI shell inside qemu, cd to `uefi/` and run:
+    # mkfs.vfat -F32 /dev/sdx
 
-    $ make run
+with `/dev/sdx` replaced by the path of your USB drive. Then mount the drive,
+and copy `out/main` to `\EFI\BOOT\BOOTx64.EFI`:
 
-### Running on real hardware
+    $ mkdir mnt
+    # mount /dev/sdx mnt
+    $ mkdir -p mnt/EFI/BOOT
+    $ make out/main
+    $ cp out/main mnt/EFI/BOOT/BOOTx64.EFI
 
-* [ ] This is not supported yet
+Now you should be able to boot directly from the USB drive.
 
 # Notes on implementation
 
@@ -67,17 +89,18 @@ The Forth variable LATEST contains a pointer to the most recently defined word.
 
 ## Threaded code
 
-In a typical Forth interpreter, code is stored in a peculiar way. (This way of
-storing code is primarily motivated by space contraints on early systems.)
+In a typical Forth interpreter, code is stored in an unusual format that makes it
+easy to implement an interpreter, and which was also motivated by space
+constraints on early systems.
 
 The definition of a word is stored as a sequence of memory adresses of each of
 the words making up that definition. (At the end of a compiled definition, there
 is also some extra code that causes execution to continue in the correct way.)
 
-We use a register (ESI) to store a reference to the next index of the
+We use a register (RSI) to store a reference to the next index of the
 word (inside a definition) that we are executing. Then, in order to execute a
-word, we just jump to whatever address is pointed to by ESI. The code for
-updating ESI and continuing execution is stored at the end of each subroutine.
+word, we just jump to whatever address is pointed to by RSI. The code for
+updating RSI and continuing execution is stored at the end of each subroutine.
 
 Of course, this approach only works if each of the words that we are executing
 is defined in assembly, but we also want to be able to execute Forth words!
@@ -86,22 +109,22 @@ We get around this problem by adding a "codeword" to the beginning of any
 compiled subroutine. This codeword is a pointer to the intrepreter to run the
 given function. In order to run such functions, we actually need two jumps when
 executing: In order to execute a word, we jump to the address at the location
-pointed to by the address in ESI.
+pointed to by the address in RSI.
 
 ## Definitions
 
 What does the codeword of a Forth word contain? It needs to save the old value
-of ESI (so that we can resume execution of whatever outer definition we are
-executing at the time) and set the new version of ESI to point to the first word
+of RSI (so that we can resume execution of whatever outer definition we are
+executing at the time) and set the new version of RSI to point to the first word
 in the inner definition.
 
-The stack where the values of ESI are stored is called the "return stack". We
-will use EBP for the return stack.
+The stack where the values of RSI are stored is called the "return stack". We
+will use RBP for the return stack.
 
-As mentioned, whenever we finish executing a Forth word, we will need to
-continue execution in the manner described in the previous section. When the
-word being executed is itself written in Forth, we need to pop the old value of
-ESI that we saved at the beginning of the definition before doing this.
+Whenever we finish executing a Forth word, we will need to continue execution
+somewhere else. When the word being executed is itself written in Forth, we need
+to pop the old value of RSI that we saved at the beginning of the definition
+before doing this.
 
 Thus, the actual data for a word in a dictionary will look something like this:
 
@@ -116,7 +139,7 @@ Thus, the actual data for a word in a dictionary will look something like this:
       LINK in next word                            points to codeword of DUP
 
 Here, DOCOL (the codeword) is address of the simple interpreter described above,
-while EXIT a word (implemented in assembly) that takes care of popping ESI and
+while EXIT a word (implemented in assembly) that takes care of popping RSI and
 continuing execution. Note that DOCOL, DUP, + and EXIT are all stored as
 addresses which point to codewords.
 
@@ -124,24 +147,27 @@ addresses which point to codewords.
 
 Literals are handled in a special way. There is a word in Forth, called LIT,
 implemented in assembly. When executed, this word looks at the next Forth
-instruction (i.e. the value of ESI), and places that on the stack as a literal,
-and then manipulates ESI to skip over the literal value.
+instruction (i.e. the value of RSI), and places that on the stack as a literal,
+and then manipulates RSI to skip over the literal value.
+
+When compiling a word, we need to handle the input specially such that literals
+are compiled as `LIT x`.
 
 ## Built-in variables
 
-* **STATE** -- Is the interpreter executing code (0) or compiling a word (non-zero)?
-* **LATEST** -- Points to the latest (most recently defined) word in the dictionary.
-* **HERE** -- Points to the next free byte of memory.  When compiling, compiled words go here.
-* **S0** -- Stores the address of the top of the parameter stack.
-* **BASE** -- The current base for printing and reading numbers.
+- **STATE** -- Is the interpreter executing code (0) or compiling a word (non-zero)?
+- **LATEST** -- Points to the latest (most recently defined) word in the dictionary.
+- **HERE** -- Points to the next free byte of memory. When compiling, compiled words go here.
+- **S0** -- Stores the address of the top of the parameter stack.
+- **BASE** -- The current base (radix) for printing and reading numbers.
 
 ## Input and lookup
 
 `WORD` reads a word from standard input and pushes a string (in the form of an
-address followed by the length of the string) to the stack. (It uses an internal
-buffer that is overwritten each time it is called.)
+address followed by the length of the string) to the stack. The buffer is only
+valid until the next call to `WORD`.
 
-`FIND` takes a word as parsed by `WORD` and looks it up in the dictionary. It
+`FIND` takes a word (as parsed by `WORD`) and looks it up in the dictionary. It
 returns the address of the dictionary header of that word if it is found.
 Otherwise, it returns 0.
 
@@ -150,15 +176,16 @@ compiling.
 
 ## Compilation
 
-The Forth word INTERPRET runs in a loop, reading in words (with WORD), looking
-them up (with FIND), turning them into codeword pointers (with >CFA) and then
-deciding what to do with them.
+The Forth word `INTERPRET` runs in a loop, reading in words (with `WORD`),
+looking them up (with `FIND`), turning them into codeword pointers (with `>CFA`)
+and then deciding what to do with them.
 
-In immediate mode (when STATE is zero), the word is simply executed immediately.
+In immediate mode (when `STATE` is zero), the word is simply executed
+immediately.
 
-In compilation mode, INTERPRET appends the codeword pointer to user memory
-(which is at HERE). However, if a word has the immediate flag set, then it is
-run immediately, even in compile mode.
+In compilation mode, `INTERPRET` appends the codeword pointer to user memory
+(which is at HERE). However, if a word has the immediate flag set, then it is run
+immediately, even in compile mode.
 
 ### Definition of `:` and `;`
 
@@ -175,10 +202,14 @@ pointer forward.
 
 # Notes on UEFI
 
-`JONASFORTH` is runs without an operating system, instead using the facilities
-provided by UEFI by running as a UEFI application. (Or rather, in the future it
-hopefully will. Right now, it uses Linux.) This section contains some notes
-about how this functionality is implemented.
+Jonasforth runs without an underlying operating system, instead using the
+facilities provided by UEFI by running as a UEFI application. This section
+contains some notes about how this functionality is implemented.
+
+I also wrote an entire tutorial that descirbes how to write and compile a
+"Hello, World!" UEFI application, including how to run it on real hardware,
+which you can find here: [Getting started with bare-metal
+assembly](https://johv.dk/blog/bare-metal-assembly-tutorial.html).
 
 ## Packaging and testing the image
 
@@ -198,26 +229,23 @@ information, or just run the `qemu` target to run the program inside of QEMU
 
     $ make qemu
 
-* [ ] How to build the image for real hardware (what should the image look like,
-  which programs, commands, etc.)
-
 ## Interfacing with UEFI
 
 From [OSDev Wiki](https://wiki.osdev.org/UEFI#How_to_use_UEFI):
 
->Traditional operating systems like Windows and Linux have an existing software
->architecture and a large code base to perform system configuration and device
->discovery. With their sophisticated layers of abstraction they don't directly
->benefit from UEFI. As a result, their UEFI bootloaders do little but prepare
->the environment for them to run.
+> Traditional operating systems like Windows and Linux have an existing software
+> architecture and a large code base to perform system configuration and device
+> discovery. With their sophisticated layers of abstraction they don't directly
+> benefit from UEFI. As a result, their UEFI bootloaders do little but prepare
+> the environment for them to run.
 >
->An independent developer may find more value in using UEFI to write
->feature-full UEFI applications, rather than viewing UEFI as a temporary
->start-up environment to be jettisoned during the boot process. Unlike legacy
->bootloaders, which typically interact with BIOS only enough to bring up the OS,
->a UEFI application can implement sophisticated behavior with the help of UEFI.
->In other words, an independent developer shouldn't be in a rush to leave
->"UEFI-land".
+> An independent developer may find more value in using UEFI to write
+> feature-full UEFI applications, rather than viewing UEFI as a temporary
+> start-up environment to be jettisoned during the boot process. Unlike legacy
+> bootloaders, which typically interact with BIOS only enough to bring up the OS,
+> a UEFI application can implement sophisticated behavior with the help of UEFI.
+> In other words, an independent developer shouldn't be in a rush to leave
+> "UEFI-land".
 
 For `JONASFORTH`, I have decided to run as a UEFI application, taking advantage
 of UEFI's features, including its text I/O features and general graphical device
@@ -234,15 +262,15 @@ convention](https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_
 for x68-64 functions. See the linked article for a full description. Here is
 the short version:
 
-* Integer or pointer arguments are given in RCX, RDX, R8 and R9.
-* Additional arguments are pushed onto the stack from right to left.
-* Integer or pointer values are returned in RAX.
-* An integer-sized struct is passed directly; non-integer-sized structs are passed as pointers.
-* The caller must allocate 32 bytes of "shadow space" on the stack immediately
+- Integer or pointer arguments are given in RCX, RDX, R8 and R9.
+- Additional arguments are pushed onto the stack from right to left.
+- Integer or pointer values are returned in RAX.
+- An integer-sized struct is passed directly; non-integer-sized structs are passed as pointers.
+- The caller must allocate 32 bytes of "shadow space" on the stack immediately
   before calling the function, regardless of the number of parameters used, and
   the caller is responsible for popping the stack afterwards.
-* The following registers are volatile (caller-saved): RAX, RCX, RDX, R8, R9, R10, R11
-* The following registers are nonvolatile (callee-saved): RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15
+- The following registers are volatile (caller-saved): RAX, RCX, RDX, R8, R9, R10, R11
+- The following registers are nonvolatile (callee-saved): RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15
 
 When the application is loaded, RCX contains a firmware allocated `EFI_HANDLE`
 for the UEFI image, RDX contains a `EFI_SYSTEM_TABLE*` pointer to the EFI system
@@ -252,9 +280,9 @@ specification as of March 2020 (PDF)](https://uefi.org/sites/default/files/resou
 
 **Sources:**
 
-* [UEFI applications in detail - OSDev Wiki](https://wiki.osdev.org/UEFI#UEFI_applications_in_detail)
-* [Microsoft x64 calling convention](https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention)
-* [UEFI Specifications](https://uefi.org/specifications)
+- [UEFI applications in detail - OSDev Wiki](https://wiki.osdev.org/UEFI#UEFI_applications_in_detail)
+- [Microsoft x64 calling convention](https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention)
+- [UEFI Specifications](https://uefi.org/specifications)
 
 ### UEFI with FASM
 
@@ -280,21 +308,21 @@ Use `objdump -x` to inspect the assembled application binary.
 
 ### UEFI documentation
 
-* [Latest specification as of March 2020 (PDF)](https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_A_Feb14.pdf)
+- [Latest specification as of March 2020 (PDF)](https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_A_Feb14.pdf)
 
 Notable sections:
 
-* 2\. Overview (14)
-* 4\. EFI System Table (89)
-* 7\. Services - Boot Services (140)
-* 8\. Services - Runtime Services (228)
-* 12\. Protocols - Console Support (429)
-* 13\. Protocols - Media Access (493)
-* Appendix B - Console (2201)
-* Appendix D - Status Codes (2211)
+- 2\. Overview (14)
+- 4\. EFI System Table (89)
+- 7\. Services - Boot Services (140)
+- 8\. Services - Runtime Services (228)
+- 12\. Protocols - Console Support (429)
+- 13\. Protocols - Media Access (493)
+- Appendix B - Console (2201)
+- Appendix D - Status Codes (2211)
 
 ## Resources
 
-* [UEFI - OSDev Wiki](https://wiki.osdev.org/UEFI)
-* [Unified Extensible Firmware Interface (Wikipedia)](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface)
-* [UEFI Specifications](https://uefi.org/specifications)
+- [UEFI - OSDev Wiki](https://wiki.osdev.org/UEFI)
+- [Unified Extensible Firmware Interface (Wikipedia)](https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface)
+- [UEFI Specifications](https://uefi.org/specifications)