Skip to content

Commit

Permalink
Don't use the uefix chainloader in the default build configuration.
Browse files Browse the repository at this point in the history
Miniforth is now small enough to fit a partition table itself.
  • Loading branch information
meithecatte committed Apr 13, 2023
1 parent a9bcbc8 commit caecfc9
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 38 deletions.
87 changes: 50 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand All @@ -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 <kbd>Alt</kbd> + <kbd>2</kbd>, <kbd>q</kbd>, <kbd>Enter</kbd>.

## 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.
Expand All @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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!
Expand All @@ -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/
Expand Down
6 changes: 5 additions & 1 deletion scripts/mkdisk.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit caecfc9

Please sign in to comment.