This commit is contained in:
Scott Duensing 2026-05-29 16:20:43 -05:00
parent 9e53e5fd38
commit a2f484b4cb
4 changed files with 143 additions and 6 deletions

View file

@ -9,7 +9,7 @@
3444 /home/scott/claude/llvm816/runtime/libcGno.o 3444 /home/scott/claude/llvm816/runtime/libcGno.o
925 /home/scott/claude/llvm816/runtime/gnoKernel.o 925 /home/scott/claude/llvm816/runtime/gnoKernel.o
34 /home/scott/claude/llvm816/runtime/gnoGsos.o 34 /home/scott/claude/llvm816/runtime/gnoGsos.o
32173 /home/scott/claude/llvm816/runtime/libc.o 31400 /home/scott/claude/llvm816/runtime/libc.o
9075 /home/scott/claude/llvm816/runtime/snprintf.o 9075 /home/scott/claude/llvm816/runtime/snprintf.o
10814 /home/scott/claude/llvm816/runtime/extras.o 10814 /home/scott/claude/llvm816/runtime/extras.o
4364 /home/scott/claude/llvm816/runtime/softFloat.o 4364 /home/scott/claude/llvm816/runtime/softFloat.o

View file

@ -470,6 +470,37 @@ with the failing check's output.
--- ---
## GNO/ME shell-command support (optional)
`setup.sh` does not provision GNO/ME — it's an optional extra for running
compiled programs as native GNO shell commands under real GS/OS (see
[Running under GNO/ME](USAGE.md#running-under-gnome) in USAGE.md and
[`tools/gno/README.md`](../tools/gno/README.md)). A fresh checkout needs
these one-time steps, in order; **none are run by `setup.sh`**:
1. **`nulib2`** — extracts the GNO `.shk` archives; required by
`tools/gno/buildDisk.sh`. Not in the apt list above:
`sudo apt-get install nulib2`.
2. **cadius** — builds the GNO/GS-OS disk images; required by
`buildDisk.sh` and `scripts/runInGno.sh`. Run
`bash scripts/installCadius.sh` (clones and builds it to
`tools/cadius/cadius`).
3. **GNO/ME 2.0.6 `.shk` archives** in `tools/gno/dist/`
(`gno.01`..`gno.16.shk`, `gnoboot.shk`, `gnohfs.shk`). These are *not*
git-tracked and *not* downloaded by any installer; fetch the GNO Base
Distribution (15 Feb 1999) from gno.org and place them there — see
`tools/gno/dist/README` for the source.
4. **GS/OS 6.0.4 system disk** at `tools/gsos/6.0.4 - System.Disk.po`.
This is **6.0.4 specifically** (the GNO boot chain requires it), not the
6.0.2 disk the bare-metal `demos/launch.sh` path uses; no installer
fetches it. Source it from an Apple IIgs System 6.0.4 `.po` disk image.
The IIgs ROMs (installed by `setup.sh`) and the GNO runtime objects (built
by `bash runtime/build.sh`) are handled automatically. Then follow the
three-step flow in [USAGE.md](USAGE.md#running-under-gnome).
---
## Where to go next ## Where to go next
- **Compile your first program:** [USAGE.md](USAGE.md). - **Compile your first program:** [USAGE.md](USAGE.md).

View file

@ -507,6 +507,99 @@ addresses afterward. Otherwise, the direct form is simpler.
--- ---
## Running under GNO/ME
The MAME path above runs your program bare-metal. GNO/ME 2.0.6 is a
Unix-like multitasking environment that runs *on top of* real GS/OS, and
a llvm816-compiled C (or C++) program can run as a native GNO **shell
command** — with console stdio, `argv`, and `FILE*` file I/O — booted
through GS/OS 6.0.4 in MAME. This is a sibling to the MAME path: a
different way to run the same C, inside a real OS.
This is verified headless and end-to-end. Three steps take you from C
source to a running command.
### 1. Build the base GNO disk (once)
```bash
bash tools/gno/buildDisk.sh # -> tools/gno/gnobase.po
```
This assembles the GNO/ME userland into an 800 KB ProDOS volume. Re-run
it only when the GNO archive set changes.
**One-time prerequisites.** `buildDisk.sh` needs `nulib2` (a system
package: `sudo apt-get install nulib2`) and `tools/cadius/cadius` (run
`bash scripts/installCadius.sh` if it is missing), plus the GNO/ME 2.0.6
`.shk` archives under `tools/gno/dist/`. The runner in step 3 also needs
the GS/OS 6.0.4 system disk at `tools/gsos/6.0.4 - System.Disk.po` and
the same IIgs ROMs the MAME path uses. None of these are installed by
`setup.sh` today — see [INSTALL.md](INSTALL.md) for the full list. You
also need the GNO runtime objects, which `bash runtime/build.sh` builds
automatically.
### 2. Compile a C program into a GNO OMF
```bash
bash demos/buildGno.sh gnoHello # demos/gnoHello.c -> demos/gnoHello.omf
```
`buildGno.sh` takes a single basename (required); it reads
`demos/<name>.c` and emits `demos/<name>.omf` (plus `.o`/`.bin`/`.map`/
`.reloc` sidecars). Bundled examples: `gnoHello`, `gnoCat`, `gnoFile`,
`gnoFmt`, `gnoStdin`.
It links the GNO crt0 and runtime, then runs `omfEmit --expressload
--relocs ... --stack-size 0x4000`. Override the DP/Stack size with the
`GNO_STACK_SIZE` environment variable if needed (default `0x4000`).
### 3. Boot, log in, run, and check
```bash
bash scripts/runInGno.sh demos/gnoHello.omf --check 0x025000=C0DE
```
The runner boots GS/OS 6.0.4 + GNO in headless MAME, logs in as `root`,
runs your command, then probes memory. `gnoHello` writes `0xC0DE` to
`$02:5000` as a harness marker, so a successful run prints:
```
[llvm816] GNO check OK: 0x025000 = 0xc0de
```
`--check` takes `ADDR=VALUE` pairs (multiple allowed after one
`--check`). **The address uses `0x` form (`0x025000`); the expected
value is bare hex with no prefix (`C0DE`, not `0xC0DE`).** The runner
prints the matched value lowercased. Add `--snapshots` to capture a PNG
of each boot/login/run stage to `/tmp/gnosnaps`.
### Things you must know
- **The OMF command basename must be ProDOS-legal — no hyphen.** Name
it `testgno`, not `test-gno`, or the command never launches.
- **stdio needs `libcGno` linked.** `buildGno.sh` does this for C.
Without it the program runs but prints nothing (the console hooks fall
through to a dead sink).
- Console file descriptors follow GNO's convention: **stdin=1,
stdout=2, stderr=3** (a documented deviation from POSIX 0/1/2).
- Commands that do GS/OS file I/O need the `--stack-size` DP/Stack OMF
segment that `buildGno.sh` passes (`0x4000`); the 4 KB default crashes.
### C++ shell commands
For a C++ command use `clang++` and link `libcGno` *before* `libc` so
its strong console hooks win. See the `gno` target in
[`stuff/baztest/Makefile`](../stuff/baztest/Makefile) for a worked
recipe.
For the full picture — disk layout, the inline GS/OS QUIT convention,
the double-run/QUIT trap, `argv` handover, `FILE*` round-trips, and the
`runInGno.sh` environment hooks (`GNO_STDIN`, `GNO_ADDFILE`,
`GNO_RUNCMD`, `GNO_POLL_FRAMES`) — see
[`tools/gno/README.md`](../tools/gno/README.md).
---
## Worked examples ## Worked examples
### Recursion + printing ### Recursion + printing
@ -891,6 +984,9 @@ bash compare/regen.sh
[`docs/multiSegmentPlan.md`](multiSegmentPlan.md) and the [`docs/multiSegmentPlan.md`](multiSegmentPlan.md) and the
`demos/launch.sh` script for booting through real GS/OS 6.0.2 in `demos/launch.sh` script for booting through real GS/OS 6.0.2 in
MAME. The 9 demos under `demos/` are reasonable starting points. MAME. The 9 demos under `demos/` are reasonable starting points.
- **Running as a GNO/ME shell command:** see [Running under
GNO/ME](#running-under-gnome) above, [`tools/gno/README.md`](../tools/gno/README.md),
and the `demos/gno*.c` examples.
- **Backend internals (you're hacking on the compiler):** - **Backend internals (you're hacking on the compiler):**
[LLVM_65816_DESIGN.md](../LLVM_65816_DESIGN.md). [LLVM_65816_DESIGN.md](../LLVM_65816_DESIGN.md).
- **Smoke tests:** `scripts/smokeTest.sh` runs ~150 end-to-end checks. - **Smoke tests:** `scripts/smokeTest.sh` runs ~150 end-to-end checks.

View file

@ -227,10 +227,18 @@ int atoi(const char *s) {
// emulator harness can poll). Real targets (MAME with our IIgs // emulator harness can poll). Real targets (MAME with our IIgs
// glue, or a console emulator) override this with a strong // glue, or a console emulator) override this with a strong
// definition. Marked `weak` so users can replace it. // definition. Marked `weak` so users can replace it.
__attribute__((weak)) //
// Console byte sink. Default writes the IIgs MAME console hook at $E2. // NB: this is a weak DECLARATION (no definition here), NOT a weak
// A hosted environment (e.g. GNO/ME) provides a strong __putByte that // default definition. A weak default defined in this same TU would
// routes to its console (see runtime/src/libcGno.c). // lose to itself: the strong override in libcGno.o gets gc'd / out-
// resolved (verified — `putchar`'s call bound to the local weak $E2
// default and the GNO console went silent). Keeping it an undefined-
// weak ref forces link-time resolution to whatever strong def is
// linked (libcGno's console putByte under GNO, else nothing -> the
// $E2 fallback below). The `&__putByte` footgun this creates is
// handled in the backend: W65816AsmPrinter emits `lda #0` (not the PBR
// `lda $BE`) for an external-weak symbol's bank half, and link816 skips
// recording a cRELOC for a sub-text-base (weak-null) target.
extern void __putByte(char c) __attribute__((weak)); extern void __putByte(char c) __attribute__((weak));
int putchar(int c) { int putchar(int c) {
@ -260,7 +268,8 @@ int puts(const char *s) {
// should call ReadCh/GetNextEvent directly via iigs/toolbox.h. // should call ReadCh/GetNextEvent directly via iigs/toolbox.h.
// Console byte source. Default polls the IIgs keyboard at $C000. A // Console byte source. Default polls the IIgs keyboard at $C000. A
// hosted environment (GNO/ME) provides a strong __getByte that reads // hosted environment (GNO/ME) provides a strong __getByte that reads
// its console (returns -1 on EOF). // its console (returns -1 on EOF). Weak DECLARATION, not a default
// definition — see __putByte for why.
extern int __getByte(void) __attribute__((weak)); extern int __getByte(void) __attribute__((weak));
int getchar(void) { int getchar(void) {
@ -953,6 +962,7 @@ clock_t clock(void) {
// Console byte sink for stderr. A hosted environment (GNO/ME) provides // Console byte sink for stderr. A hosted environment (GNO/ME) provides
// a strong __putByteErr that targets the stderr stream (GNO fd 3); when // a strong __putByteErr that targets the stderr stream (GNO fd 3); when
// absent, stderr just shares stdout's sink (the historical behavior). // absent, stderr just shares stdout's sink (the historical behavior).
// Weak DECLARATION, not a default definition — see __putByte for why.
extern void __putByteErr(char c) __attribute__((weak)); extern void __putByteErr(char c) __attribute__((weak));
// Write one byte to the stdout (kind 1) or stderr (kind 2) console // Write one byte to the stdout (kind 1) or stderr (kind 2) console