diff --git a/demos/gnoFmt.map b/demos/gnoFmt.map index 63ff30e..682b8ad 100644 --- a/demos/gnoFmt.map +++ b/demos/gnoFmt.map @@ -9,7 +9,7 @@ 3444 /home/scott/claude/llvm816/runtime/libcGno.o 925 /home/scott/claude/llvm816/runtime/gnoKernel.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 10814 /home/scott/claude/llvm816/runtime/extras.o 4364 /home/scott/claude/llvm816/runtime/softFloat.o diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 3c0fdc8..eb205a7 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -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 - **Compile your first program:** [USAGE.md](USAGE.md). diff --git a/docs/USAGE.md b/docs/USAGE.md index 38a4c74..c740ed4 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -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/.c` and emits `demos/.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 ### Recursion + printing @@ -891,6 +984,9 @@ bash compare/regen.sh [`docs/multiSegmentPlan.md`](multiSegmentPlan.md) and the `demos/launch.sh` script for booting through real GS/OS 6.0.2 in 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):** [LLVM_65816_DESIGN.md](../LLVM_65816_DESIGN.md). - **Smoke tests:** `scripts/smokeTest.sh` runs ~150 end-to-end checks. diff --git a/runtime/src/libc.c b/runtime/src/libc.c index cc3ebdf..ca457de 100644 --- a/runtime/src/libc.c +++ b/runtime/src/libc.c @@ -227,10 +227,18 @@ int atoi(const char *s) { // emulator harness can poll). Real targets (MAME with our IIgs // glue, or a console emulator) override this with a strong // definition. Marked `weak` so users can replace it. -__attribute__((weak)) -// Console byte sink. Default writes the IIgs MAME console hook at $E2. -// A hosted environment (e.g. GNO/ME) provides a strong __putByte that -// routes to its console (see runtime/src/libcGno.c). +// +// NB: this is a weak DECLARATION (no definition here), NOT a weak +// default definition. A weak default defined in this same TU would +// 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)); int putchar(int c) { @@ -260,7 +268,8 @@ int puts(const char *s) { // should call ReadCh/GetNextEvent directly via iigs/toolbox.h. // Console byte source. Default polls the IIgs keyboard at $C000. A // 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)); int getchar(void) { @@ -953,6 +962,7 @@ clock_t clock(void) { // Console byte sink for stderr. A hosted environment (GNO/ME) provides // a strong __putByteErr that targets the stderr stream (GNO fd 3); when // 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)); // Write one byte to the stdout (kind 1) or stderr (kind 2) console