From caecfc97288175e692b3a8d7b73a72d58548a1aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maja=20K=C4=85dzio=C5=82ka?= Date: Thu, 13 Apr 2023 19:41:10 +0200 Subject: [PATCH] Don't use the uefix chainloader in the default build configuration. Miniforth is now small enough to fit a partition table itself. --- README.md | 87 +++++++++++++++++++++++++++-------------------- scripts/mkdisk.py | 6 +++- 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 238c601..cce6eed 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,13 @@ You can either build a disk image yourself (see below), or download one from When Miniforth boots, no prompt will be shown on the screen. However, if what you're typing is being shown on the screen, it is working. You can: - - do some arithmetic: `1 2 + u.` + - do some arithmetic: + ``` + 7 5 - u. + : negate 0 swap - ; + : + negate - ; + 69 42 + u. + ``` - load additional functionality from disk: `1 load` (see [*Onwards from miniforth*](#onwards-from-miniforth) below). @@ -55,34 +61,6 @@ You can run the resulting disk image in QEMU with `./run.sh`, or pass `./run.sh if you do not want to include the code from `*.fth` in your disk. QEMU will run in curses mode, exit with Alt + 2, q, Enter. -## UEFI sucks, or `uefix.bin` - -Running Miniforth on real hardware is certainly possible. In fact, I hardly use -emulation for it these days. Anything not unreasonably old should work. However, -most implementations of UEFI have a bug/misfeature where they try to parse the -partition table of an MBR before booting from it. As Miniforth does not include -a partition table in the bootsector (as there is simply no space for it), this -prevents booting on most UEFI computers. - -To remedy this, this repository includes a small chainloader as a workaround. -So, instead of this disk layout, which works on computers old enough: - -``` -LBA 0 - boot.bin -LBA 1 - unused -LBA 2-3 - Forth block 1 -... ... -``` - -...the disk image generated by `build.sh` looks as follows: - -``` -LBA 0 - uefix.bin -LBA 1 - boot.bin -LBA 2-3 - Forth block 1 -... ... -``` - ## Blocks `load ( blk -- )` loads a 1K block of FORTH source code from disk and executes it. @@ -106,13 +84,46 @@ the stack on boot: and `1` means interpreting. - `#DISK` is not a variable, but the saved disk number of the boot media +## `-DAUTOLOAD` + +For some usecases, it might be desirable for the bootsector to load the first +block of Forth code automatically. You can achieve this by building `boot.bin` +with `-DAUTOLOAD`. Unfortunately, this requires 7 more bytes of code, making +miniforth go over the threshold of 446 bytes, which makes it no longer possible +to put an MBR partition table in the boot sector. + +The partition table is required for: + - the filesystem code in `blocks/filesystem.fth` + - booting in the BIOS compatibility mode of most UEFI implementations, due to + a common bug/misfeature. + +To work around this, `scripts/mkdisk.py` will use the small chainloader in +`uefix.s` when it detects that miniforth is larger than 446 bytes. +Instead of the default disk layout, which looks like this: + +``` +LBA 0 - boot.bin +LBA 1 - unused +LBA 2-3 - Forth block 1 +... ... +``` + +...the disk image will looks as follows: + +``` +LBA 0 - uefix.bin +LBA 1 - boot.bin +LBA 2-3 - Forth block 1 +... ... +``` + ## Onwards from miniforth The main goal of the project is bootstrapping a full system on top of Miniforth as a seed. Thus the repository also contains various Forth code that may run on top of Miniforth and extend its capabilities. - - In `bootstrap.fth` (`1 load`): + - In `blocks/bootstrap.fth` (`1 load`): - A simple assembler is implemented, and then used to implement additional primitives, which wouldn't fit in Miniforth itself. This includes control flow words like `IF`/`THEN` and `BEGIN`/`UNTIL`, as well as calls to the BIOS @@ -127,12 +138,12 @@ top of Miniforth and extend its capabilities. - A separate, more featureful outer interpreter overrides the one built into Miniforth, to correct the ugly backspace behavior and handle things such as uncaught exceptions and vocabularies. - - A way of searching for occurences of a particular string in the code stored - in the blocks is provided: - - `10 20 grep create` searches blocks `$10` through `$20` inclusive for - occurences of `create` - - If your search term includes spaces, use `grep"` — the syntax is similar - to `s"` string literals: `10 20 grep" : >number"` + - In `blocks/grep.fth` (`2f load`), a way of searching for occurences of a + particular string in the code stored in the blocks is provided: + - `10 20 grep create` searches blocks `$10` through `$20` inclusive for + occurences of `create` + - If your search term includes spaces, use `grep"` — the syntax is similar + to `s"` string literals: `10 20 grep" : >number"` - In `editor.fth` (`30 load`), a vi-like block editor is implemented. It can be started with e.g. `10 edit` to edit block 10. - Non-standard keybindings: @@ -182,7 +193,7 @@ Git or GitHub's web interface. This disparity is handled by two Python scripts: ## Free bytes At this moment, not counting the `55 AA` signature at the end, **445** bytes are used, -leaving 65 bytes for any potential improvements. +leaving 1 byte for any potential improvements. Byte saving leaderboard: - Ilya Kurdyukov saved 24 bytes. Thanks! @@ -197,6 +208,8 @@ If a feature is strongly desirable, potential tradeoffs include: - ?? bytes: Instead of storing the names of the primitives, let the user pick their own names on boot. This would take very little new code — the decompressor would simply have to borrow some code from `:`. However, reboots would become somewhat bothersome. + - ?? bytes: Instead of providing `;` in the kernel, give a dictionary entry to `EXIT` and + terminate definitions with `\` |` until `immediate` and `;` can be defined. [FORTH]: https://en.wikipedia.org/wiki/Forth_(programming_language) [blog]: https://compilercrim.es/bootstrap/ diff --git a/scripts/mkdisk.py b/scripts/mkdisk.py index 93434d3..6e06c95 100644 --- a/scripts/mkdisk.py +++ b/scripts/mkdisk.py @@ -60,7 +60,11 @@ def blocks_at(begin, fname): boot = f.read() blocks = {} - blocks[0] = uefix + boot + if boot[0x1be:0x1fe] == bytes(64): + blocks[0] = boot + bytes(512) + else: + print('Using the uefix chainloader to avoid overwriting code with the partition table') + blocks[0] = uefix + boot for bnum, fname in FILE_MAP: blocks_at(bnum, fname)