====== Rohspeicher: alloc, peek & poke ====== Die gesamte Lyx-Standardbibliothek arbeitet auf einer einzigen Speicherabstraktion: ''alloc(n)'' liefert einen rohen Byte-Puffer als ''int64''-Adresse, ''peek8''/''poke8'' lesen und schreiben einzelne Bytes, ''peek64''/''poke64'' lesen und schreiben 8-Byte-Wörter. Kein GC, keine versteckten Allokationen, kein Overhead. Diese Seite erklärt das Muster und zeigt, wie es in der Praxis eingesetzt wird. Sie ergänzt die allgemeine Seite [[lyx_-_programmiersprache:sprache:memory-management|Memory Management]] um die Low-Level-Schicht, auf der die Standardbibliothek aufbaut. → [[lyx_-_programmiersprache:units:alloc|std.alloc]] · [[lyx_-_programmiersprache:sprache:handles-und-fd|Handle-Konzept]] · [[lyx_-_programmiersprache:sprache:std-fehlerkonventionen|Fehler-Konventionen]] ---- ===== alloc und free ===== ''std.alloc'' stellt zwei Grundoperationen bereit: ^ Funktion ^ Signatur ^ Beschreibung ^ | ''alloc'' | ''alloc(n: int64): int64'' | Allokiert ''n'' Bytes. Gibt die Startadresse zurück, 0 bei OOM. Inhalt undefiniert (nicht nullisiert). | | ''free'' | ''free(ptr: int64, n: int64): void'' | Gibt ''n'' Bytes ab ''ptr'' frei. **Beide Parameter** sind Pflicht — Lyx hat keine versteckte Größeninformation. | import std.alloc; fn CreateBuffer(size: int64): int64 { var buf: int64 := alloc(size); if (buf == 0) { return 0; } // OOM return buf; } fn DestroyBuffer(buf: int64, size: int64): void { free(buf, size); } > Die Größe bei ''free'' muss exakt der bei ''alloc'' übergebenen Größe entsprechen. Das ist kein Versehen — es ermöglicht dem Allocator, ohne Metadaten-Overhead zu arbeiten. ---- ===== peek und poke — Roher Speicherzugriff ===== Lyx liest und schreibt Speicher byte-genau über vier Primitive: ^ Funktion ^ Liest/Schreibt ^ Beschreibung ^ | ''peek8(addr)'' | 1 Byte | Liest uint8 an Adresse ''addr'' | | ''poke8(addr, v)'' | 1 Byte | Schreibt uint8 ''v'' an Adresse ''addr'' | | ''peek64(addr)'' | 8 Bytes | Liest int64 little-endian an Adresse ''addr'' | | ''poke64(addr, v)'' | 8 Bytes | Schreibt int64 ''v'' little-endian an Adresse ''addr'' | Diese Primitiven sind **in der Sprache eingebaut** — kein Import nötig. Sie sind das Fundament für alle Struct-ähnlichen Datenstrukturen in der Standardbibliothek. ---- ===== Structs als Byte-Offset-Konstanten ===== Da Lyx keine generischen Structs mit dynamischer Größe im ABI kennt, werden interne Datenstrukturen als **flache Byte-Puffer** mit benannten Offset-Konstanten implementiert. Das Muster durchzieht die gesamte Standardbibliothek: // Konzeptueller Aufbau einer internen Datenstruktur (Beispiel) // // Offset Größe Feld // 0 8 fd (Dateideskriptor) // 8 8 buf (Zeiger auf Lesepuffer) // 16 8 bufLen (belegte Bytes im Puffer) // 24 8 flags // // Gesamtgröße: 32 Bytes pub con MY_STRUCT_SIZE: int64 := 32; pub con MY_STRUCT_FD: int64 := 0; pub con MY_STRUCT_BUF: int64 := 8; pub con MY_STRUCT_BUFLEN: int64 := 16; pub con MY_STRUCT_FLAGS: int64 := 24; fn CreateMyStruct(fd: int64): int64 { var s: int64 := alloc(MY_STRUCT_SIZE); if (s == 0) { return 0; } poke64(s + MY_STRUCT_FD, fd); poke64(s + MY_STRUCT_BUF, 0); poke64(s + MY_STRUCT_BUFLEN, 0); poke64(s + MY_STRUCT_FLAGS, 0); return s; } fn GetFd(s: int64): int64 { return peek64(s + MY_STRUCT_FD); } fn SetBufLen(s: int64, len: int64): void { poke64(s + MY_STRUCT_BUFLEN, len); } fn DestroyMyStruct(s: int64): void { var buf: int64 := peek64(s + MY_STRUCT_BUF); if (buf != 0) { free(buf, 4096); } // innere Ressourcen zuerst free(s, MY_STRUCT_SIZE); } Dasselbe Muster findet sich in ''std.db.sqlite'' (''SQLiteDB'', 24 Bytes), ''std.db.postgres'' (''PGConn'', 120 Bytes), allen PDF-Untermodulen und allen Netzwerk-Units. ---- ===== Byte-Arrays befüllen und lesen ===== import std.alloc; fn ZeroFill(ptr: int64, n: int64): void { var i: int64 := 0; while (i < n) { poke8(ptr + i, 0); i := i + 1; } } fn CopyBytes(dst: int64, src: int64, n: int64): void { var i: int64 := 0; while (i < n) { poke8(dst + i, peek8(src + i)); i := i + 1; } } fn ReadInt32BE(ptr: int64, off: int64): int64 { // Big-Endian uint32 lesen (Netzwerk-Byteorder) return (peek8(ptr + off) << 24) | (peek8(ptr + off + 1) << 16) | (peek8(ptr + off + 2) << 8) | peek8(ptr + off + 3); } fn WriteInt32BE(ptr: int64, off: int64, v: int64): void { poke8(ptr + off, (v >> 24) & 0xFF); poke8(ptr + off + 1, (v >> 16) & 0xFF); poke8(ptr + off + 2, (v >> 8) & 0xFF); poke8(ptr + off + 3, v & 0xFF); } ---- ===== Dynamisch wachsende Puffer ===== Das Standard-Muster für wachsende Puffer in der Bibliothek — analoges Verhalten zu einem StringBuilder oder einem dynamischen Array: import std.alloc; // Typische Puffer-Verwaltung wie in std.pdf.builder, std.db.postgres, … fn GrowBuf(oldBuf: int64, oldCap: int64, needed: int64): int64 { var newCap: int64 := oldCap * 2; while (newCap < needed) { newCap := newCap * 2; } var newBuf: int64 := alloc(newCap); if (newBuf == 0) { return 0; } // Inhalt kopieren var i: int64 := 0; while (i < oldCap) { poke8(newBuf + i, peek8(oldBuf + i)); i := i + 1; } free(oldBuf, oldCap); return newBuf; } ---- ===== Freigabe-Reihenfolge ===== Bei verschachtelten Strukturen (Struct enthält Zeiger auf weiteren Heap-Speicher) gilt immer: **von innen nach außen freigeben**. // Falsch — Memory Leak: fn LeakyFree(conn: int64): void { free(conn, CONN_SIZE); // conn wird freigegeben, aber // conn.buf wurde nicht freigegeben → Leak } // Richtig: fn CorrectFree(conn: int64): void { var buf: int64 := peek64(conn + CONN_BUF); if (buf != 0) { free(buf, peek64(conn + CONN_BUFCAP)); // erst innere Ressource } free(conn, CONN_SIZE); // dann äußere Struktur } ---- ===== Warum dieser Ansatz? ===== Begründung für die gewählte Implementierungsstrategie: * **Deterministische Performance** — kein GC, kein Stop-the-world, keine versteckten Allokationen * **Null Overhead** — ein ''alloc'' ist ein Syscall (mmap) oder ein einfacher Pointer-Bump; ''peek''/''poke'' sind direkte Load/Store-Instruktionen * **Portierbarkeit** — das Muster funktioniert identisch auf x86-64, ARM64 und Android (Bionic) * **Nachvollziehbarkeit** — jeder Speicherzugriff ist im Quellcode sichtbar; kein Magic Proxy, kein Reflection ---- ===== Zusammenfassung ===== ^ Aufgabe ^ Mittel ^ | Puffer anlegen | ''alloc(n)'' → ''int64''-Adresse | | Puffer freigeben | ''free(ptr, n)'' — Größe muss stimmen | | Einzelbyte lesen/schreiben | ''peek8(addr)'' / ''poke8(addr, v)'' | | 8-Byte-Wort lesen/schreiben | ''peek64(addr)'' / ''poke64(addr, v)'' | | Strukturfeld lesen | ''peek64(base + OFFSET)'' | | Strukturfeld schreiben | ''poke64(base + OFFSET, v)'' | | Mehrere Ressourcen freigeben | Innen zuerst, außen zuletzt | → [[lyx_-_programmiersprache:sprache:handles-und-fd|Handle-Konzept — warum alles ein int64 ist]]\\ → [[lyx_-_programmiersprache:units:alloc|std.alloc — Funktionsreferenz]]\\ → [[lyx_-_programmiersprache:sprache:memory-management|Memory Management — Stack, Heap, Safety]] Letzte Aktualisierung: 2026-06-05