From 9c729edac1772aef31826f82b3a68e4e4f88733b Mon Sep 17 00:00:00 2001 From: Nexoniarz Date: Sun, 25 Oct 2026 18:31:40 +0100 Subject: [PATCH] Cheaper cursor, a fortify panic that names its function; Finch's gap The cursor now costs nothing for a byte inside an escape sequence and one cell fewer for a plain glyph: the grid is the truth, so the old cursor cell is repainted after the byte, and not at all when a glyph has just been drawn over it. __fortify_panic says "r8169: rtl_open.cold+0x..." through ldlsd's module list, so a photograph of the screen is enough, and exits instead of spinning on a core and holding /dev/netdev. docs/plan/finch.md: only integrating Finch is later; finishing it is not. The self-hosting gap measured against Finch's own bootstrap test. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_015f73FesGZGYPkmDvUcj6QX --- cmd/ldlsd/main.c | 35 ++++++++++++++++++++++++++++++++- docs/plan/finch.md | 49 ++++++++++++++++++++++++++++++++++------------ kernel/dev/vt.c | 15 +++++++++++++- ldls/kpi/kpi.h | 3 +++ ldls/kpi/string.c | 14 ++++++++++++- 5 files changed, 101 insertions(+), 15 deletions(-) diff --git a/cmd/ldlsd/main.c b/cmd/ldlsd/main.c index 78cacd5..195a83d 100644 --- a/cmd/ldlsd/main.c +++ b/cmd/ldlsd/main.c @@ -326,6 +326,33 @@ find_symbol(struct ko *k, const char *want) return 0; } +// "r8169: rtl_open.cold+0x4f" for an address in a loaded module, so that a +// photograph of the screen is enough to find the line. Modules are named by +// the file they were loaded from, with the directory and ".ko" dropped. +static const char *g_modname[MAXMOD]; + +static void +where(unsigned long addr) +{ + for (int m = 0; m < g_nmods; m++) { + struct ko *k = g_mods[m]; + for (int i = 0; i < k->nsym; i++) { + const struct ko_sym *s = &k->sym[i]; + if (s->st_shndx == KO_SHN_UNDEF || s->st_shndx >= (unsigned short)k->nsh || + !k->sec[s->st_shndx].alloc || s->st_size == 0 || (s->st_info & 0xf) != 2) + continue; + unsigned long long a = k->sec[s->st_shndx].addr + s->st_value; + if (addr >= a && addr < a + s->st_size) { + puts_(g_modname[m]); puts_(": "); + puts_(k->str + s->st_name); puts_("+"); + puthex(addr - a); + return; + } + } + } + puthex(addr); +} + // --- reading and writing a struct at an offset BTF gave us -------------------- static unsigned int @@ -1009,8 +1036,14 @@ load_module(const char *path) puts_("\n"); entry = find_symbol(k, "init_module"); - if (g_nmods < MAXMOD) + if (g_nmods < MAXMOD) { + const char *b = path, *q; + for (q = path; *q; q++) + if (*q == '/') b = q + 1; + g_modname[g_nmods] = b; g_mods[g_nmods++] = k; + kpi_where = where; + } if (!entry) { puts_(" no init_module -- nothing to call\n"); return; } puts_("\n calling init_module\n\n"); diff --git a/docs/plan/finch.md b/docs/plan/finch.md index 9ab77f7..d05caf1 100644 --- a/docs/plan/finch.md +++ b/docs/plan/finch.md @@ -1,8 +1,12 @@ # Finch, for SchOS's own programs -**Later. Not being worked on** — it is behind [1.0.md](1.0.md)'s freeze like -everything else here that is not named there. This records the decision so it -is not rediscovered. +**Two things, and only one of them is later.** + +- *Bringing Finch into SchOS* (below) is **later** — behind [1.0.md](1.0.md)'s + freeze like everything else here that is not named there. +- *Finishing Finch itself*, and making it fully self-hosting, is work on the + Finch repository and does not touch this tree. It can proceed now; see + [the gap](#the-self-hosting-gap). Finch is the author's own language: (GitHub mirror: `Nexoniarz/Finch`). The plan is to write SchOS's own @@ -31,7 +35,7 @@ Measured from the repository, not from its README alone: - **Tooling:** a language server (`finch lsp`), VS Code and Kate support, 74 tests. - **Self-hosting:** `boot/` is a Finch compiler written in Finch (about 3,100 - lines, against about 5,000 for the C++ one in `src/`) that builds itself to a + lines, against about 5,900 for the C++ one in `src/`, language server included) that builds itself to a byte-identical result — `tests/boot.sh`. ## What SchOS would need to do @@ -53,16 +57,37 @@ Measured from the repository, not from its README alone: the headers in `include/uapi/`, so they are not hand-written and cannot drift from the kernel. -## What "finishing Finch" means is not settled +## The self-hosting gap -The repository has no list of what is unfinished. Before work starts, the -author names it. Candidates seen in the tree: `note.txt` and `notatka.txt` are -scratch files; `OpenGL.fn` was deleted in the last commit; the `boot/` compiler -is shorter than the C++ one, so some language features may exist only in the -latter. **The first task is to diff the two feature sets**, not to guess. +"Rewrite it in Finch itself" is mostly done: `boot/` compiles itself. What it +does **not** yet do is stated by its own test, `tests/boot.sh`, which skips +every test that uses a feature `boot/` lacks. Counting mentions in the two +sources confirms it (`boot/` vs `src/`): + +| Feature | `boot/` | `src/` | Needed for | +|---|---|---|---| +| maps | 0 | 185 | `codegen_map.cpp` (242 lines) to port | +| errors as values (`or`, `try`) | few | hundreds | most real programs | +| methods on user types | 17 | 87 | partial | +| `defer` | 2 | 31 | partial | +| sized numbers (`u8`..`u64`, `i8`..`i32`, `f32`, `f64`) | none | throughout | kernel-facing code | +| enums | 0 | 14 | | +| C import (`import "x.h"`, `link`) | none | `cimport.cpp`, 373 lines on libclang | **the way into every C library, SchOS's included** | +| target and ABI layer (AArch64, macOS, Windows) | 0 | `target.cpp`, `abi.cpp` | non-Linux-x86 targets | +| language server | 0 | `lsp.cpp`, 580 lines | editors | + +So "finishing" has a concrete order: **errors as values and maps first** (they +are what the rest uses), then sized numbers and `defer`, then C import — which +needs libclang bindings, so it needs C import's own simpler form first — and +the target layer last. `tests/boot.sh` is the measure: the list of skipped +tests shrinks to nothing. + +The scratch files `note.txt` and `notatka.txt` in the repository are not +Finch's and can go. ## Rules -- Nothing in the kernel is rewritten in Finch. It is for applications and tools. +- Inference, not an instruction: the kernel stays in C. Finch is for + applications and tools; the user said "our apps or stuff like that". - No new capability starts until [the register of unfinished work](1.0.md#the-freeze-and-the-register-of-unfinished-work) - is empty; this document is a plan, not an exception to that. + is empty; the SchOS half of this document is a plan, not an exception to that. diff --git a/kernel/dev/vt.c b/kernel/dev/vt.c index 65131fd..e892624 100644 --- a/kernel/dev/vt.c +++ b/kernel/dev/vt.c @@ -1241,7 +1241,20 @@ vt_cycle(int dir) void vt_feed(struct vt *v, int c) { - cursor_erase(v); + // The grid is the truth, so the old cursor cell can be repainted from it + // *after* the byte is consumed -- which lets two cases cost nothing: + // - a byte inside an escape sequence changes nothing on screen; + // - a plain glyph is drawn over the cell the cursor sat on, which repaints + // it anyway (unless a wrap is pending, when it lands on the next line). + int mid = v->esc_state != VE_NORMAL; + int over = !mid && !v->wrap_pending && c >= 0x20 && c < 0x7f && + v->u8_need == 0; vt_feed_1(v, c); + if (mid && v->esc_state != VE_NORMAL) + return; + if (over) + v->curs_drawn = 0; + else + cursor_erase(v); cursor_draw(v); } diff --git a/ldls/kpi/kpi.h b/ldls/kpi/kpi.h index 076a858..3871173 100644 --- a/ldls/kpi/kpi.h +++ b/ldls/kpi/kpi.h @@ -152,6 +152,9 @@ unsigned long kpi_jiffies_now(void); // Output. The KPI is inside a process with no libc, and every implementation // wants to be able to say what it did. void kpi_log(const char *s); +// Says "module+0xoffset" for an address in a loaded module; set by the host. +extern void (*kpi_where)(unsigned long addr); + void kpi_lognum(long long v); void kpi_loghex(unsigned long long v, int digits); diff --git a/ldls/kpi/string.c b/ldls/kpi/string.c index 973f7ad..5e1c8ff 100644 --- a/ldls/kpi/string.c +++ b/ldls/kpi/string.c @@ -14,6 +14,7 @@ // have been easier to find. #include "core/types.h" +#include "uapi/user.h" #include "kpi.h" void * @@ -135,6 +136,8 @@ kpi_memcpy_toio(void *dst, const void *s, unsigned long n) // __fortify_panic is what a driver's bounds-checked string helpers call when // they detect an overflow. Reaching it means the driver found a bug in itself, // and the honest thing is to say so rather than return. +void (*kpi_where)(unsigned long addr); + void kpi_fortify_panic(unsigned int reason, unsigned long avail, unsigned long size) { @@ -147,8 +150,17 @@ kpi_fortify_panic(unsigned int reason, unsigned long avail, unsigned long size) kpi_log(" reason "); kpi_lognum(reason); kpi_log(", available "); kpi_lognum((long long)avail); kpi_log(", wanted "); kpi_lognum((long long)size); - kpi_log(", from 0x"); kpi_loghex((unsigned long)__builtin_return_address(0), 16); + kpi_log(", from "); + if (kpi_where) + kpi_where((unsigned long)__builtin_return_address(0)); + else { + kpi_log("0x"); kpi_loghex((unsigned long)__builtin_return_address(0), 16); + } kpi_log("\n"); + // Not spin: a driver that has found a bug in itself holds /dev/netdev and + // one core at 100% until reboot. The job is KeepAlive = no, so leaving is + // free, and the kernel takes the interface back. + exit_group(1); for (;;) ; }