lib4th (L4)
Der Forth-Kern enthält
einen ausführbaren Processor-Code compilierenden "subroutine threaded native code"
Compiler und Interpreter, wobei der Interpreter sich vom Compiler (im wesentlichen) nur
durch die sofortige Ausführung des gerade compilierten Codes unterscheidet. Alle
'syscalls' beliebiger Linux-Versionen sind mit Namen aufrufbar, sie werden
beim Erzeugen der L4 aus den jeweils vorhandenen Kernel-Quellen des Linux übernommen.
"lib4th"-Quellen und Hilfsprogramme sind ausführlich kommentiert, samt einer
'handgefertigten' (der "nasm"-Assembler leistet leider nichts dergleichen), Labels und
deren Aufrufstellen verzeichnenden Querverweis-Liste ("cross reference").
Die Worte des Forth liegen als Subroutinen vor, sodaß sie auch unmittelbar zur
vereinfachten Assemblerprogrammierung benutzt werden können.
Man mag sowas "High Level Assembler" nennen - und hat damit dem Kind einen (m.E. ziemlich
hohlen) Namen gegegben. Die Vielseitigkeit solchen Zugangs auf Assembler-Niveau ist damit
nicht annähernd beschrieben, diese werden wohl nur eigene Versuche veranschaulichen.
Die "lib4th" besteht aus nur positionsunabhängigem und wiedereintrittstauglichem Code
und enthält keinerlei Bezug auf fremde Datenstrukturen ("g.o.t", "p.l.t", o.dgl.),
d.h. solche, die nicht dem Forth selber zueigen sind. Sie ist von allen Linux/ELF-Spezifika
soweit unabhängig, daß sie sich nach Anpassung der 'syscalls'
leicht auch innerhalb anderer Systeme verwenden lassen sollte.
Grund für die Einrichtung als eigenständiger, insbes. auch von einer libc
unabhängiger "dll" ist zum einen der grandiose Mist, der in einigen
der "glibc"-Versionen produziert wurde und wohl auch weiterhin zu erwarten ist (2.2.5!),
sowie vor allem mein Wunsch, nicht mit jedem neuen Forth-Programm wieder und wieder immer
auch den ganzen Kern und irgendwelche zweifelhaften Hilfsprogramme in den Speicher holen
zu müssen. Was neben der Speicheranforderung auch die Startzeit der betr. Programme
(mit K6-2/350mcs um eine sagenhafte Millisekunde) verringert. Zugriff aus andersartigen
Programmen steht vorerst nicht im Vordergrund.
Der 'subroutine threaded native code' compilerende und interpretierende Aufbau ist vor
allem wegen seiner gegenüber den traditionellen Ansätzen einfacheren und z.B.
im Hinblick auf geeignete Register-Zuordnung weit besseren Darstellbarkeit entstanden,
und wegen der im Gegensatz zu den Ergebnissen gemeiner Programmierhilfen wie "C" u.dgl.
bis ins letzte Detail überschaubaren inneren Gestalt. Die im Forth relativ
seltene Möglichkeit, auch Schleifen und bedingte Konstrukte 'interaktiv', ohne
vorheriges Compilieren in ein "Wort" auszuführen, ergab sich auf diese Weise von
selbst und hat den Compiler/Interpreter eher noch weiter vereinfacht. Die recht hohe
Ausführungsgeschwindigkeit wurde als willkommenes Nebenprodukt zwar beachtet, aber
doch nur zweitrangig mitgenommen; sie hat erst nach Vorliegen einiger Tests durch ihre
(für mich) unvermutet guten Werte besonderen Stellenwert erlangt. Ganz drollig, am
Rande, daß die L4 in den c.l.f.-news bei gutem Willen
zwar bestenfalls als originelle private Einzellösung aufgenommen wurde, sich später
aber Meldungen über ähnliche Ansätze in bemerkenswerter Weise zu häufen
beginnen - natürlich (mit einer Ausnahme) ohne Bezug auf die "lib4th". Niedlich, das:)
Anwendungen
liegen neben dem - inzwischen hinreichend erprobten und bewährten - Blockfile-Editor
noch kaum vor. Das Startprogramm "f8ed" zum L4 Blockfile-Editor gibt u.a. ein Beispiel
für automatisches Compilieren eines Programmtextes und Starten der betr. Erweiterung durch
ein ausführbares Basis-Programm. Auch der Umgang mit CREATE .. DOES>, den besonderen
Kontrollworten für FORGET, der periodisch durch SIGALARM ausgelösten
(Teil-)Programm-Ausführung, Formatierung der Bildschirm-Ausgabe u.v.m. ist dort am
konkreten Beispiel nachvollziehbar.
Die bekannten ANS-4th Testprogramme wie (der nicht wirklich standardkonforme!) "coretest", "bench", das
obligatorische "tetris", auch ein Assembler und Disassembler (Quellen im Archiv), u.dgl. werden immerhin
erfolgreich verarbeitet. Da sämtliche "syscalls" des
jeweiligen Linux-Kerns leicht handhabbar zur Verfügung stehen, eignen sich die L4-Programme besonders
auch zu recht sicheren Experimenten mit diesen Systemaufrufen.
Im Übrigen sei auf die einfachen Programme im Quellenpaket verwiesen, "f8" mit etwas über
1K Bytes Programm-Größe zum Einsprung in das Forth-System, und "calc4th" als absolutes
Minimalprogram zum Einsprung in den lib4th-Kern mit gerade mal 628 Bytes, für die Nutzung des Forth-Systems
z.B. als einem interaktiven rsp. als Script ausführbaren Kommandozeilen-Rechner für Ganze oder
Reelle Zahlen zu beliebiger (sinnvoller) Basis 2..256 bei unbegrenztem(!) Wertebereich - aus welchen
Vorhaben nebenbei die gesamte "lib4th" sintemalen überhaupt erst entstanden ist...
"quotaq" entstand aus der Notwendigkeit, meinen (nicht
zuletzt deshalb aufgegebenen) Inet-Bunker bei "snafu" (r.i.p.)
zu kontrollieren, seit man dort besonders clever die Preise 'korrigiert' hat und stillschweigend
die zugesagten 10MB in 'Cluster'-Einheiten nichtgenanter Größe vergibt, und die Grenze
pedantisch rsp. eher 'vorausschauend' überwacht - aufgrund welcher seltsamen Interpretation
mein "quota" überschritten war. Das Script ermittelt den Platzbedarf einer Anzahl Dateien
aus einer nach Muster der Linux-Ausgabe von <ls -l ...> übergebenen Liste und erzeugt
abhängig von der Art des Aufrufs ggf. auch eine HTML-Datei mit einem entsprechenden, 'gelinkten'
Verzeichnis. Neben dem Nutzeffekt zeigt es insbes. den Umgang mit den verschiedenen Typen der
Ein-/Ausgabe-Umleitung in einem L4-Script.
"p3f" als Secundär-Filter zum Perl-Programm
"POP3Filter" veranschaulicht einige besondere Funktionen der Textbearbeitung und gibt Hinweise
zur Anwendung als Forth-Script mit Ein-/Ausgabe auch über 'pipes'. Er ist sehr simpel
aufgebaut, sein praktischer Nutzen hält sich (noch) in recht engen Grenzen.
"htmlxref.f8" dient der Erzeugung der Querverweis-Referenzen
zu den L4-Quellen. Das per {INCLUDE} o.ä. zu compilierende Programm liefert sein Ergebnis
bereits nach ca. 5m ("call-by-disp"-Modus), mit dem vorher verwandten "bash"-Script wurden
für dieselbe Aufgabe ca. 60m benötigt. Es arbeitet dazu für jedes der gut 2400
Forth-Worte 1MB Quelltexte durch und liefert den Ort der Definition mit Filenamen und Zeilennummer,
entsprechend alle Stellen, an denen das betr. Wort aufgerufen wird, und es legt einen Verweis
auf den zugehörigen Glossar-Eintrag an. Insbes. für den Zugriff auf mit {MMAP}
in den Speicher eingeordnete Dateien und für die Vielseitgkeit von {EVALUATE} bieten
sich darin einige Anwendungsbeispiele. Besonders hilfreich ist die Möglichkeit, mit EVALUATE
aus den von der Kommandozeile hineigereichten Filenamen die jeweils zugeordneten Datenposten in
Form von daraus benannten {CONSTANT}- und {VALUE}-Definitionen einzurichten und abzufragen,
etwa Bereichsgröße und Basisadresse des betr. Dateien-Bereichs. Die mmap-Variante
hat gegenüber dem unmittelbaren File-Zugriff die Ausführungszeit des Forth-Programms
insgesamt auf ca. 1/3 reduziert - beide im L4-Archiv.
-
Per v1.0.3 alle Wortsätze nach ANS/ISO vollständig, mit Ausname von
- CORE hat LEAVE in fig-4th-Version, ANS-Wort ggf. nachzuladen.
- EXCEPTION als (ungeprüftes) hi-level Beispiel aus DPANS94 in 4th/thrw.f8.
Weitere Worte u.a. in den Vocabularen
- ANS
- BIGNUM
- BLKFILE
- COMPILER
- EDITOR
- FIG
- FLOAT
- LINUX
- RATIONAL
- VT
- .. &c ..
-
Die nach ANS geforderte IEEE-745 Zahlendarstellung in FLOATING/-EXT ist nicht vorgesehen,
u.a. wegen mangelnder Genauigkeit, unklarer Beschreibung der Darstellungsweise und auch
zugunsten anderweitiger Nutzung der mmx-Register. Ersatzweise sind im FLOAT-Vocabular die
- m.E. der IEEE-Darstellung entscheidend überlegenen -
{RATIONAL}-Definitionen als Synonyme zusammengefaßt. Insbes.
bei mehrfach-genauen Arithmetik- und Stackoperationen ergibt sich dadurch für das
Gesamtsystem im allgemeinen erheblicher Laufzeitgewinn. Worte zur Umwandlung zwischen den
Darstellungsformen 'ranum' und IEEE-745 sind vorhanden.
Die lib4th kann auch ganz frei von mmx-Operationen erzeugt werden, sodaß der
cpu-spezifischen FLOATING-Ergänzung nichts im Wege steht, eigene Unternehmungen in
dieser Richtung wird es dagegen nicht geben.
DPANS94:
- "lib4th"
- ist nicht "ANS-Forth".
- kann zum Erzeugen von Programmen nach dem ANSI/ISO Forth-Standard benutzt werden.
- kann ANS-Forth-Programme ausführen, evtl. sind geringfügige Anpassung erforderlich (s.u).
-
isoliert nach ANS inkonsistent definierte Worte durch das Vocabular
{ans} vom Rest des Systems. Z.B.
- {2/} mit falschem Ergebnis bei einigen negativen Werten,
- "big-endian" Datenspeicherung bei doppeltgenauen Posten.
Compilieren von ANS-Forth-Programmen erfordert selten besondere Änderungen.
Aufrufe über { ' execute } sollten nur bei genauer Kenntnis
der Worteigenschaften (z.B. mit "help") benutzt werden, da aufgrund der Besonderheiten
des Compilers viele zustandsabhängige "immediate" Worte existieren, die
dann u.U. fehlerhaft ausgeführt werden.
Verknüpfung zwischen Zahlen- oder reinen Binärwerten und der inneren Zahlendarstellung
ist zu vermeiden (s.u.).
Im gesamten lib4th-System werden Datengruppen "little endian"
(höherwertige Teile zu höhergelegenen Adressen hin) abgelegt. Dadurch kann es zu
Konflikten kommen, da das ANS-Forth die für x86-Rechner absurde "big-endian" Anordnung
doppelt-genauer Gruppen von "CELLS" vorschreibt. Entsprechende Worte finden sich im {ans}-Vocabular,
das ggf. vorrangig in die Such-Ordnung aufzunehmen ist.
Der Forth-Standard nach "ANS" ist mir nicht ultimativer Maßstab, wohl aber hervorragende
Grundlage für austauschbare Programme und Ausgangspunkt zur Verständigung über
das, was - ohne seine Eigenschaft als Betriebssystem - ein "Forth" ausmacht. In diesem Sinne
mag man meine Bemühungen sehen, besagtem Standard Genüge zu tun.
Da die L4-Dokumentation in englischer Sprache abgefaßt ist, in loser
Folge ein paar ergänzende Angaben:
Dokumentation
liegt als "Glossary" in Klartext vor, außerdem in einer Gruppe html-Texte, in der die ca.
2400 Worte des Kerns nach verschiedenen Gesichtspunkten geordnet sind, "l4gls.html, "l4toc.html",
etc. "l4xrf.html" ist eine Querverweis-Liste der Aufrufe und Einsprungstellen in den
Assembler-Quellen. Wurde die Vorgabe nicht geändert, finden sich die Texte nach Installation
im Verzeichnis "/usr/share/doc/lib4th" und einige Beispiele in "/usr/local/lib/f8".
Zu allen Worten des Kerns kann jederzeit, auch während einer Wortdefinition, der zugehörige
Glossar-Text in's Bild geholt werden, { help forthwort } gibt den Text zum ersten in
der gesamten Vocabularliste gefundenen "forthwort" aus. Soll nur in der Reihenfolge des Vocabularstacks
gesucht werden, schreibt man { hv help forthwort }.
Ob ein Wort überhaupt vorhanden ist, zeigt { v forthwort } an, indem es entweder
die "lfa" als Referenz in den Wortheader und das Vocabular, in dem es definiert ist, ausgibt, oder
ein Fragezeichen, wenn es unbekannt ist.
Den Zustand des Vocabularstacks zeigt {order}, und {vocs} gibt aus, welche Vocabulare
vorhanden sind.
Localer Speicher, "Value"s
"Locals" werden in einem zum Zeitpunkt der Ausführung betr. Worte angelegten geschlossenen
Speicherbereich durchgehend adressierbar und ohne Einfluß auf irgendwelche Zugriffsformen
auf andere Daten (Stacks, Speicher) angeordnet. Sie sind durch nichts eingeschränkt, als ihre
auf das Wort bezogene Gültigkeit, ihre Namen sind gewöhnliche Forth-Namen aus bis zu 255
Schriftzeichen, ihre Anzahl ist einzig durch den gerade verfügbaren Speicher begrenzt. Es
können jederzeit auch in der Größe frei wählbare Bereiche eingerichtet werden.
Stets ist der geordnete Zugriff auf die jeweils übergeordneten "Locals" in der Kette aufrufender
Worte möglich.
Andererseits werden viele LOCALs dadurch entbehrlich, daß alle Varianten {PICK} und {ROLL} mit
negativem Argument die jeweilige Umkehr-Operation ausführen. Negatives {PICK} erlaubt damit auch
Rückspeichern an ausgewählte Stackpositionen ohne zeitraubende und u.U. auch verwirrende
Datenschaufelei. Sofern nicht größere geschlossene Speicherbereiche benötigt werden
(die "der Standard" ebensowenig vorsieht), zeigte sich diese Vorgehensweise der Verwendung von "LOCALs"
weit überlegen.
Byte Order
ist die Reihenfolge von Daten im Speicher, die, wenn nicht strikt auf Konsistenz geachtet wird,
oft (und leicht) zu Konflikten und Mißverständnissen führt.
- 'Little Endian' - die ihre Früstückseier am dünneren Ende aufschlagen,
das Wort ist "Gulliver's Reisen" entlehnt - bedeutet die Datenablage mit niederwertigen Teilen
byteweise an niedrigerer Adresse, hin zu höheren Adressen für die höherwertigen
Teile, durchgehend per Byte für jeden beliebigen Datenposten.
- 'Big Endian' steht für die andere Richtung, wo in visionärer Symbolik das
'Dicke Ende' zuerst kommt, die bereits durch den gewöhnlich 'aufwärts' gerichteten
Programmzähler eine nicht immer leicht zu fassende Inkonsistenz birgt.
Vieles, insbes. einfache Arithmetik an größeren Posten, läßt sich in der
little-endian-Manier leichter ausführen, als in 'big-endian'-Anordung. - In über
zehnjähriger Mühsal z.B. mit den MC680xx-Processoren ist mir kein Fall begegnet,
wo letztere irgendeinen nennenswerten Vorteil erwiesen hätte. Beschränkte Erfahrung,
sicherlich, und vielleicht rein persönlicher Eindruck, sei's drum, die L4 "wohnt" im 386er
Linux. Und dort liegen Bytes (nahezu) unvermeidlich nur 'little endian', wirre Spezifikationen
"des" Standards implizieren aber die 'big-endian'-Ablage von Zellen-Paaren, jedoch, man muß
wohl nicht jeden Unsinn mitmachen.
L4 ist ganz stur 'little endian' organisiert, selbst, wenn irgendwer das aus welch krausen
Gründen auch immer anders vorgesehen haben sollte. Nach ANS geforderte Mischformen wurden
in das gleichnamige Vocabular ausgelagert, stehen damit zwar zur Verfügung, belästigen
aber auch nicht weiter.
In Konsequenz sind einige Spielereien aus dem ANS-Forth mit L4 nur im {ans}-Vocabular möglich:
- { ... >R ... >R ... 2R> } wird offenbar gerne trick- und geistreich zu einem impliziten
SWAP mißbraucht.
- { ... 2! ... @ ... } dasselbe sonstwo im Speicher.
- { ... [ 1 2 2dup ] 2literal literal literal ... } liefert dagegen stets das erwartete
Ergebnis.
Abschließend und nur für besonders Hartnäckige: Die Inkonsistenz liegt darin, daß
in der Speicheradressierung fortschreitende Adressen vorzeichenlos zu höheren Zahlenwerten hin
gezählt werden und nicht, wie in grauer Frühzeit gelegentlich gängige Praxis, zu niedrigeren
Werten hin. In letzterem Falle wäre die 'big-endian' Anordnung durchaus vernünftig; wo's sowas
aber noch geben mag, entzieht sich meiner Kenntnis, und so ist mir das Raisonieren über eine 'bessere'
Datenanordnung nur noch nostalgisch nutzlose Zeitvergeudung.
Zahlen
Bei Ein- und Ausgabe aller Zahlenarten einschl. "Fließkomma" wird die in der
User-Variablen {BASE} gerade eingestellte Umrechnungsbasis zugrundegelegt, lediglich
innerhalb von Texten zur Ausgabe mit {TYPE} &c nach dem Muster "\nnn" gilt stets
die oktale Basis als Vorgabe.
Zusätzlich läßt sich die Basis einer einzelnen Zahl mit einem Praefix festlegen:
- %(zahl) binaer
- !(zahl) quaternaer(?, Basis 4)
- &(zahl) oktal
- @(zahl) oktal
- #(zahl) dezimal
- §(zahl) duodecimal
- $(zahl) sedezimal
- 0x(zahl) sedezimal
- -(praefix)(zahl) das Vorzeichen "-" kann vor oder hinter einem
einzelnen Praefix stehen, bei "0x" nur davor.
- \(praefix)(zahl) Fließkomma-Format für die folgende Zahl, mit
oder ohne Praefix, wird durch "\" eingeleitet,
im dauerhaften {FLT}-Modus, wo das Fließkomma-Format vorgegeben ist,
holt man damit eine vierfach-genaue (128 bit) Ganzzahl.
- ^(char) ein ascii Control-Code als (Code des Schriftzeichens - 64)
- "(char) bis zu vier Schriftzeichen - führendes "-" negiert auch hier
Zweiercomplement
In L4 gilt strikt die Anwendbarkeit der Negation auf jede beliebig Zahl. Die (einer nicht ganz unbekannten
Quelle entnommene) Sequenz zur Ermittlung der kleinsten darstellbaren, negativen Zahl
{ 0 INVERT 1 RSHIFT INVERT CONSTANT MIN-INT }
sieht zwar ungemein "Standard"-mäßig aus, ist aber doch nur Unsinn (s. u.a. DPPANS94 3.1.3.2),
oder bestenfalls als 'Überlauf in das Vorzeichen-Bit' interpretierbar. Etwa die Charakterisierung
der Null nach dem Axiom der Addition durch { 0+x =/= 0-x } für alle x=/=0 führt
mit nach obigem Modell besetztem x zum Widerspruch, (in 'c.l.f.' gehabte) Streitereien um deren Gültigkeit
sind müßig. Des einfachen Begriffes halber und wegen { 0-x = 0+x } sowie
{x < 0 } heißt solch ein Bitmuster im Zusammenhang mit L4 auch 'negative Null'.
Im übrigen belegt auch die Charakterisierung gültiger Zahlen im DPANS94-Dokument, derzufolge etwa
die Sequenz { DUP NEGATE OVER OVER < ROT ROT > = } für
keinen von Null verschiedenen Wert das Ergebnis "{true}" haben darf, die Wertlosigkeit der o.g. Herleitung.
BIGNUM
steht kurz für eine Art Ganzzahlen, deren Repräsentation aus einem Zähler als erster "Zelle"
und der entsprechenden Anzahl 32bit-Posten der Zahl besteht. Notation negativer Werte im Zweiercomplement.
Deren Größe ist nur durch den gerade verfügbaren Speicherplatz begrenzt. Es sind sämtliche
arithmetischen Operationen einschließlich Quadratwurzel und Stackmanipulationen vorhanden, die erforderlich
sind, um diese Zahlenart leicht und vollwertig einsetzbar zu machen. Alle betr. Operationen finden im Datenstack
ohne zusätzlichen Speicherplatz statt, sie sind uneingeschränkt Recursions-tauglich.
Rationale Zahlen
genauer, die "Reellen Zahlen", bestehen aus Paaren von Ganzzahlen für Zähler
und Nenner eines (ggf. unechten) Bruches. D.h. Operationen für "Fließkommazahlen"
sind auch in der lib4th enthalten, allerdings nicht in der vergleichsweise schlecht zu
verarbeitenden Form nach IEEE-Norm. Die Darstellung als Bruch zweier Zahlen erlaubt sehr
einfache und schnelle arithmetische Operationen sowie die völlig problemfreie
Verknüpfung mit den "normalen" im Forth gewöhnlich bevorzugten Ganzzahlen beliebiger
Postengröße. Insbes. die Scalierungsoperatoren wie { */ } sind dazu
praedestiniert. Weiterrechnen nach Aufteilung in Ganzzahl und reinen Bruch ermöglicht
unbegrenzte Genauigkeit und eindeutige Rundungsergebnisse - wo die Zahlenrepräsentation
nach IEEE-Norm völlig versagt. Nebenbei kann damit wie bei Ganzzahlen mit gleichermaßen
geringem Aufwand in jeder auch auf jene anwendbaren Zahlenbasis gerechnet werden.
Ein- oder Ausgabe in Form einer "Fließkomma"-Zahl oder einzeln als Zähler und Nenner,
Ausgabe zusätzlich auch als gemischt gebrochene Zahl, beides zu der in {BASE} gegebenen
Zahlenbasis, Eingabe auch mit Praefix (s.o.).
Die RATIONAL rsp. FLOATING/-EXT Wortsätze sind vollständig nach ANS
und bezüglich aller Erfordernisse, d.h. sie enthalten bei einstellbarer Genauigkeit
die Grundrechenarten samt Quadratwurzel und transzendenten Funktionen (e- und Winkel-Funktionen)
sowie viele unterstützende Worte, so z.B. eine FOR/STEP/NEXT-Variante mit variierbarem
Laufindex rsp Increment. Ausführungszeit der leeren Schleife mit gebrochenem Increment
ca 30-fach gegenüber DO/LOOP mit einfachen Ganzzahlen, andere Worte ab etwa dem Vierfachen
doppeltgenauer (64-Bit) Ganzzahl-Operationen, einige brauchen weit weniger, etwa das Reziproke
oder die Negation.
Das {FLOAT}-Vocabular enthält die Worte des ANS-Forth nebst Umformung zwischen der vom
Standard geforderten Datenrepräsentation nach IEEE-Norm und dem eigenen RATIONAL-Format.
Ein/Ausgabe-Umleitung
und die recht effiziente Zwischenspeicherung bewerkstelligt der Linux-Kern bereits, die L4 fügt
lediglich eine Indirektion hinzu, um zum einen der Vorgabe des ANS-Forth Genüge zu tun, die
eine einzelne Zelle als eindeutige Kennung einer offenen Datei vorschreibt, welche aber für
den geordneten Zugriff nicht immer ausreicht, und zum andern die Vereinheitlichung des Zugriffs
sowie Bereitstellung besonders häufig benötigter Parameter (File-Pointer, -Größe,
Zustandsflags) zu ermöglichen. Dies sind die als Indices in eine Tabelle von entspr.
Datenblöcken dargestellten "Kanäle", die den File-Operationen anstelle der wirklichen
"File descriptoren" des Linux übergeben werden.
Besondere Kanäle sind "stdin", "stdout", "stderr" entsprechend den Linux-Konventionen,
sowie "work", der einheitlich bestimmten Operationen dient und u.a. durch {open-file}
besetzt wird; "kbd" weist stets auf die Tastatur, "dr0" und "dr1" sind vorrangig den "Blockfiles"
zugeordnet. {new-chan} gibt den ersten freien Platz der Kanaltabelle zurück, und
{is-chan} besetzt jenen Kanal mit den betr. Daten aus "work", die damit zugleich auch vor
dem Überschreiben geschützt werden. Um auch das Vertauschen solcher Knäle zu
ermöglichen, wird dabei ein unter einem solchen Index bereits offener Kanal nicht automatisch
geschlossen. Das ggf. zuvor aufzurufende {close-file} macht zunächst die betr. Kanaltabelle
ungütig und schließt den wirklichen Systemkanal nur, wenn derselbe 'file descriptor'
nicht auch noch unter anderen Kanalnummern (außer "work") eingetragen ist.
{in-chan}, {out-chan}, {err-chan} sind Kernel-VALUEs, die beim Systemstart
automatisch mit den Standardkanälen des Linux besetzt werden und diese z.B. zur Verwendung
in Scripts vor dem Überschreiben durch die Initialisierung des ausführenden Forth-Jobs
schützen. Beim (interaktiven) Aufruf ohne Ein- oder Ausgabe-Umleitung sind sie mit {stdin},
{stdout}, {stderr} identisch.
Die Anforderungen sind nicht ganz leicht zu bewätigen:
Ein- und Ausgabe existieren in den Konstellationen
pipe: eingabe | f8 | ausgabe
file: f8 <eingabe ausgabe>
cmd: f8 forthworte -- inaktive-daten
script: -in allen Varianten-
Die Arbeit mit beliebigem Kombinationen davon ist nicht immer ohne weiteres möglich, da eine
ggf. benutzte Script-Datei, "stdin" und die Kommandozeile um den Interpreter konkurrieren, der den
"Eingabestrom" stets aus "stdin" empfängt. Auf relativ einfache Weise unterstützt das Wort
{x-i/o} den jeweils benötigten Zugriff, das im {root}-Vocabular definiert und damit stets
verfügbar ist. Es vertauscht die Standardkanäle mit den beim Start übergebenen:
Ein Script liegt dem ausführenden Forth zunächst selbst als Text in "stdin" vor, sodaß,
weil der Interpreter stets aus diesem Kanal liest, er erst nach dessen Abarbeitung rsp. an sonstwie
geeigneter Stelle durch den ggf. umgeleiteten Text ersetzt werden kann. Da die Kanäle nur
vertauscht werden und offen bleiben, kann von der jeweils freigegebenen Seite aus beliebig zwischen
ihnen umgeschaltet werden. Werden nur Daten in ein Script gereicht, ist diese Umschaltung unnötig,
sie erlaubt aber die Abarbeitung von Programmteilen sowohl z.B. über Pipe-Eingabe als aus der
Kommandozeile desselben Aufrufs, auch nach dort eingetragenem "include" anderer Programmquellen.
BLOCK
Forth-"screenfiles" oder "blockfiles" bestehen aus Klartext ohne irgendwelche Steuerzeichen,
der üblicherweise so angeordnet wird, daß er in Zeilen zu 64 Zeichen lesbar ist.
16 solcher Zeilen heißen eine "screen", gedacht als Mindestmaß selbst auf
bescheidenen Sichtgeräten (z.B. von Meßgeräten, Maschinen) zumeist noch
darstellbaren Bildschirminhalts, mit 1K Bytes zugleich vereinbarte Blockgröße
und ganzzahliges Maß für die Länge der betr. Dateien.
Vorteil dieser Ablageform ist ihre größtmögliche Einfachheit und damit
weitgehende Unabhängigkeit von der jeweiligen System-Umgebung, die leichte Wiedergabe
auf Druckern oder Sichtgeräten, auch effiziente Comprimierbarkeit, oder z.B. der sehr
kompakte Aufbau zugehöriger Editoren - der Zeileneditor im L4-Kern belegt gerade mal 2K
Speicher. Verlust von Texten etwa durch Speicherüberlauf während des Editierens ist
unmöglich, da stets nur in bereits zugewiesene Bereiche geschrieben wird. Selbstätige
Pufferung und Übergabe des Pufferinhalts an die betr. Datei machen die "Block-Puffer"
ebensogut ganz allgemein zur Datengewinnung und -Verarbeitung tauglich.
L4 richtet Pufferspeicher für "blockfiles" bedarfsweise automatisch durch die betr.
Worte ein, mit {FORGET} eines älteren Wortes (sie selbst haben keinen Header)
kann ihr Speicher freigegeben werden. Auch dies geschieht automatisch und bedarf außer
evtl. vorherigen Sicherns veränderter Daten keiner besonderen Vorkehrungen, Einrichtung
eines "Forget-Handlers" kann ggf. auch diese Aufgabe noch automatisieren. Zusätzliche
Puffer können durch Aufruf in nur durch den verfügbaren Speicher begrenzter Zahl
zugewiesen werden - was im allgemeinen nicht erforderlich sein wird.
Die Blockpuffer haben "PAGE_SIZE"-Größe (vier unmittelbar einander folgende
"screens" zu je 1K) und liegen an entsprechend justierter Adresse, wodurch sie sich auch
zur Einordnung beliebiger Files in den Speicher etwa mittels Syscall "mmap" eignen.
Verschiedenen Files werden jeweils unabhängige Pufferblöcke zugeordnet. Alle
Blockfile-Operationen finden nur an den Puffern statt, die zu dem gerade bearbeiteten Kanal
gehören.
FORGET
geht völlig problemlos durch alle Vocabulare der aktuellen Such-Ordnung und erfaßt auch
Vocabulare rsp. "wordlist"s selbst. "current" und "context" werden ggf. möglichst auf den vorherigen
Zustand gesetzt, notfalls geht "forth" nach "current", im Vocabularstack wird anstelle gelöschter
Vocabulare "local" eingetragen, das dort von neutraler Wirkung ist, da es ohnehin stets zuerst durchsucht
wird.
DEFERed Worte stellen sich beim Aufruf automatisch auf die z.B. mit {default-is}
festgelegte Vorgabe zurück, wenn das eigentliche Ziel nicht im benutzten Codebereich liegt, oder
werden inaktiv, falls auch jene entfernt wurde. Sie sollten darum nach FORGET vor der Definition anderer
Worte etwa mit { 0 is name } deaktiviert werden, da sie andernfalls das nächste
mit dem "uot-Index" der ersten Festlegung (s. Glossar, "execution token") bezeichnete Wort ausführen
würden. Diese Deaktivierung kann auch ein zusätzlicher "forget-handler" erledigen:
Durch { FORGET: forthwort ..auszuführende Wortfolge.. ; } lassen sich
ohne Ausnahme allen Worten beliebig viele, ggf. der Reihe nach auszuführende "forget-handler" in
Form gewöhnlicher Wortdefinitionen zuordnen, die unmittelbar vor Entfernen des betr. Wortes automatisch
aufgerufen werden.
Aus CREATE und DOES> gebildete Definitionsworte können in der Form
{.. CREATE .. FORGET> handlerwort DOES> ..} erweitert werden,
sie weisen dann den daraus entstehenden Worten ohne weitere Vorkehrungen einheitlich diesen Handler zu.
Textverarbeitung
wird insbes. unterstützt durch:
- Ermitteln einer Zeichenposition durch {skip}, {scan}, {rskip}, {rscan}.
{scan} durchsucht einen Text bis zum ersten Zeichen, das gleich einem angegebenen ist,
{skip} sucht das erste Zeichen ungleich dem angegebenen. {rskip} und {rscan}
beginnen die entsprechende Suche am Text-Ende. Alle geben die bis zum Textende
verbleibende Länge und die gefundene Position zurück.
Ein Sondermodus (das doppelte Zeichen, codiert mit 8224) erlaubt gleichrangige Behandlung des
Leerzeichens ('blank') und der Steuerzeichen (code 0..31), in L4 beispielsweise eingesetzt um
bei {PARSE} u.dgl. auch das Zeilenende als Wortbegrenzung zu erfassen.
- Vereinfachtes Isolieren von Teiltexten ("string-slicing") ermöglichen
{bslice} mit Begrenzung per Zeichenposition,
{cslice} Schriftzeichen, oder {sslice} durch umgebende Textstücke.
- Austausch von Textstücken oder von in Marken eingeschlossenen Partien mit {replace}
und {substitute}.
- String-Verkettung am Anfang oder am Ende mit {+place}.
- {count} und die String-Worte arbeiten gleichermaßen mit "counted strings" bis zu
einer Länge von 255 Bytes und mit in <nul>-Bytes eingeschlossenen Texten beliebiger
Länge. Strings kürzer als 256 Bytes können gezielt in asciz-Form abgelegt werden,
ab dieser Länge erfolgen asciz-Ablage und ggf. -Anpassung automatisch.
- {type} und verwandte Worte werten die wichtigsten "escape"-Zeichen der Linux 'bash'
aus ("\n" &c), beliebige Zeichen können mit ihrem Code (binaer, octal, dezimal, sedezimal)
eingefügt werden. Die Umwandlung geschieht mittels {e\stg} in {[etype]},
welches neben {e\stg} selbst das einzige L4-Wort ist, das diese Umwandlung enthält.
Insbes. werden Strings stets so aufgenommen rsp gespeichert, wie hingeschrieben.
Zusätzlich ist "\-" als Platzhalter vorhanden, etwa zur Abgrenzung numerischer Angaben der
Art "\(zahl)" von unmittelbar folgenden, unverändert auszugebenden Ziffern.
{type} ist auf {[type]} vectorisiert, das als 'deferred' Wort die Variante {[etype]}
ausführt, diese Vorgabe läßt sich etwa mit { ' [ctype] is [type] }
leicht auf das "normale" TYPE ändern.
Im übrigen, da jedes Wort der L4 letztlich über eine zentrale Tabelle ('uot') vectorisiert
ist, kann auch {e\stg} durch eine andere Definition ersetzt werden, wonach ggf. die Umwandlung durch
{type} und die Worte, die sich dessen bedienen, ebenfalls den neuen Kriterien folgt. Da aber solche
Neudefinitionen ein Risiko insbes. gegenüber fremden Einflüssen darstellen können,
lassen sich diese durch irreversiblen Schreibschutz der 'uot' unterbinden, etwa mit {mprotect}.
Genaueres dazu im Text 'a-forth-design'.
- String-"constante", änderbar nach Art der VALUEs,
definiert {STG}, {TO-STG} besetzt sie neu, {+TO-STG} fügt ein Textstück,
{C+STG} ein einzelnes Zeichen am Ende oder am Anfang hinzu. Die Länge solcher Strings ist
auf 4K begrenzt - allein zur Anwendungs-Sicherheit, durch Neucomplieren der L4 auf andere Werte einstellbar.
Sicher nicht sonderlich sinnvoll, aber immerhin, wegen der besonderen Textbehandlung können
auch Forth-Namen mit Zeichen, die nicht durch die Tastatur erreichbar sind, problemlos hingeschrieben
werden, etwa
: DIESER\#160COMPUTER [ latest ] literal id. ." ist meiner.\n" ;
DIESER\#160COMPUTER
(s. "bench.f8").
Als Zeichensatz gilt, was für die Linux-Konsole (oder ein X-Terminal) gerade eingestellt
ist. Alle Schriftzeichen (derzeit code 0...256, Unicode -vorsichtig- in Vorbereitung) sind erreichbar,
Einschränkung auf die im ANSI-Standard beschriebenen, aber - auch wenn Gegenteiliges gerne zitiert
wird - keineswegs darauf begrenzten us-ascii-Zeichen ist nicht vorgesehen. Wäre m.E. gar zu absurd,
ausgerechnet durch solcherlei unnötige Beschränktheit den Nutzen eines Forth-Systems auf einen
minderen Interessentenkreis zu begrenzen. Auch Zusammensetzen von Schriftzeichen durch Betätigung
mehrerer Tasten ist möglich, sofern die zugrundeliegende Linux-Konsole(n-Emulation) dies vorsieht
rsp. einzustellen erlaubt.
Umschalten zwischen den Zeichensätzen sehr einfach z.B. mittels VT-Steuersequenzen, Änderungen
auch des Schriftbildes sind mit dem 'ioctl'-Systemaufruf bei Angabe neuer Zeichensätze möglich.
{EMIT} sendet alle Bytes einer 32-Bit-"Zelle", beginnend mit der niederwertigeren Position und
solange, bis nur noch Nullen übrigbleiben. Dies ist recht bequem bei der Ausgabe kleiner, bekannter
Zeichenfolgen, ist aber in erster Linie als Voraussetzung für die - noch fehlende - Ausgabe von
Unicode-Zeichen vorgesehen.
Terminal/Console
sind mit den Definitionen ihrer Steueraktionen in eingermaßen verrottetem Zustand.
Linux-Distributionen und selbst einzelne Programme kommen zudem noch oft mit eigenen
Definitionen, die aus welch rätselhaftem Grunde auch immer deren Autoren der Vorgabe
für überlegen zu halten scheinen, die aber kaum mehr bewirken, als die Verwirrung
zu steigern. Seitens der "lib4th" werden darum weder 'termcap'- noch 'terminfo'-Daten in
irgendeiner Weise interpretiert oder überhaupt wahrgenommen, die betr. Worte senden lediglich
ihre Steuercodes an das jeweilige Terminal und es bleibt dem Anwender überlassen, die in den
betr. VT-Beschreibungen dargestellten Aktionen ggf. entsprechend zu konfigurieren. Solche Konfiguration
ist im allgemeinen nicht schwer, nur es ist aufgrund des herrschenden Durcheinanders (mir) gar zu
umständlich, ein von 'außen' hinzukommendes Programm zuverlässig darauf einzustellen.
Auch soll die "lib4th" diesem Wirrwarr nicht noch eine weitere Konfigurationsvariante hinzufügen.
Für die - eher ferne - Zukunft ist auch Auswertung der 'terminfo' vorgesehen. Bis dahin
müssen die fest codierten Aktionen genügen, sofern sich nicht jemand anderes aufrafft...
Die betr. L4-Worte sind soweit irgend möglich so gestaltet, daß die bereits zwischen
der Linux Console und einem der ihr am nächsten stehenden Terminal-Emulatoren für
X-Windows, "rxvt", bestehenden Unterschiede in den VT-Steuersequenzen nicht in
Erscheinung treten. D.h, "Linux Console" und "rxvt" sind mit den Worten der L4
annähernd gleichartig kontrollierbar. Ein Beispiel hierzu gibt der Screenfile-Editor,
"f8ed". Andere Terminal-Emulationen sind z.T. derart eingenwillig definiert, weisen selbst
versionsbedingt noch erhebliche, schlecht bis garnicht dokumentierte Unterschiede auf (z.B.
"xterm"), daß sie einfach nur unberücksichtigt bleiben konnten.
>BODY
ergibt unterschiedliche, vom jeweils gerade gegebenen Wort abhängige Adressen, erzeugt
konstante Referenz einzig in Bezug auf den gerade compilierten Code, steht auch bei gleicher
Grunddefinition nicht immer im selben Abstand zu irgendeinem anderen Speicherort! Dasselbe
trifft auf {>DATA} zu, mit dem man die {>body} entsprechende Referenz auf bereits im
Kern definierte umd beim Systemstart in den schreibbaren Speicher übertragene Datenposten
erhält.
Hier offenbart sich ein (früher) Entwurfsfehler; generelle Referenz über den Abstand
zur Basisadresse des Datenbereichs wäre zwar ein wenig langsamer, in vielerlei Hinsicht aber
weitaus günstiger, entspr. Änderung ist - vorerst mal in eher ferner Zukunft - vorgesehen.
save-system kommt nicht vor,
denn das Compilieren von Forth-Textquellen geht außerordentlich schnell, braucht
kaum mehr Zeit, als etwa `Linken' einer zusätzlichen Library, und ist, da der Inhalt
damit völlig offengelegt ist, die einzige für jeden Anwender sicher durchschaubare
Form eines Programms. Fest daran binden lassen sich solche Quellen durch entsprechende
Eintragung in den Programm-Rumpf, der Screenfile-Editor "f8ed" gibt ein Beispiel
dafür. Nebenbei macht dieses Verfahren den Copright-Kram einfacher und erspart mir
den zickigen Aufbau (a la GNU &c) zur Lizensierung von Folgeprogrammen, da jene und die
lib4th so stets klar unterscheidbar bleiben. Auch wird Schwachsinn der Art 'behead' oder
sonstwie mutwilliges Unleserlichmachen von Forth-Namen durch das Compilieren von Klartextquellen
sicher unterbunden, ein weiterer Grund, weshalb auch für spätere L4-Versionen
vorcompilierbare Programme oder Module definitiv nicht vorgesehen sind.
Code-Optimierung
Abgesehen davon, daß es nicht #die# Optimierung gibt und ich mich auch nicht mit hohlem Gequake
über Super-optimierende Super-Compiler - die "man" hat, aber über die offenbar niemand
verfügt - abgebe, die folgende Notiz; mehr dazu im Text "a-forth-design" des Quellenarchivs:
-
Als 'Optimierung' mag man gelten, daß die "lib4th" als Assemblerprogramm verfaßt
ist, daß sie ohne Umweg direkt ausführbaren Code erzeugt, und daß beim
Compilieren von Forth-Worten der jeweils letzte Subroutinenaufruf statt mit abschließendem
<ret> möglichst als direkter Sprung codiert wird, auch im Falle der Recursion. - Wurde sie
nicht explizit unterbunden (Variable "cmc" < 0), ermöglicht diese Eigenschaft nebenbei
auch Weiterreichen von Daten im Return-Stack an das jeweils letzte Wort einer Definition.
-
Compilieren der Forth-Worte kann in Form Speicher-indirekter oder PC-relativer Subroutinen-Aufrufe
erfolgen, letztere Variante wird je nach Programmstruktur bis ca. 30% schneller ausgeführt
(allgemein ca 10%).
-
Trennung der Speicherbereiche für Wort-Header und ausführbaren Code und die
Tatache, daß der Einsprung in ein Wort keine besonderen Aktionen (lo-level Header wie "nest"
o.dgl) erfordert, kompensieren insbes. bei vielen kurzen Definitionen den durch die Subroutinenaufrufe
etwas größeren Speicherbedarf restlos und erlauben mit {;CODE} die direkte Fortführung
eines Wortes im unmittelbar danach definierten.
-
Localer Speicher basierend auf einer allgemeinen, in der Größe nicht beschränkten
Zuweisung macht diese Einrichtung überhaupt erst zu mehr als einer Schreibhilfe für jene
'Programmierer', die allzuleicht die Übersicht über ihre Stackposten verlieren. Durch enge
Nachbarschaft der Bereiche für Datenstack und localen Speicher sowie denkbar einfache Datenübergabe
hält sich der Zeitverlust, der überall entsteht, wo nur wenige Processorregister verfügbar
sind, auch für die "localen Variablen" nach ANS-4th in erträglichen Grenzen - PICK und ROLL der
L4 mit negativem Argument zur Rückspeicherung sind gegenüber "LOCALS| .. |" allerdings
in jedem Falle schneller!
-
Geeignete Zuordnung der Processorregister hat offensichtlich wesentlichen Anteil, insbes.
Verwendung unterschiedlicher Register für Forth-Datenstack und Returnstack der cpu.
Einheit der Pointer behindert Subroutinen im Kern empfindlich und scheint auch hinsichtlich
der Vorgänge in der cpu ausgesprochen ungeschickt gewählt. Das allgemein als langsam
beschriebene <stosd> dient als 'push' in den Datenstack, 'pop' existiert als <mov../lea..>,
welche Paarung sich im Kontext der L4 gegenüber <push/pop> der cpu nicht unterlegen zeigte.
-
Der jeweils jüngste Posten des Datenstacks steht im Register EAX; die Operationen mit
EAX werden im Processor allgemein besonders unterstützt, sodaß sich auch hier
die Abweichung von der 'Tradition' (EBX) vorteilhaft erwies. - Alles bezogen auf den
K6/2-Processor, der (bis dato) als einziger Typ mir zur Verfügung steht, und nur damit
hinreichend überprüft.
-
Übernahme von Code aus dem Kern anstelle der betr. Subroutinen und rückwärtsgerichtetes
Zusammenfassen geeigneter Worte als (minimale) Formen wirklicher Optimierung sind vorbereitet, in der
Ausführung aber vorläufig zurückgestellt.
Als erster Ansatz liegt die Zusammenfassung für den Fall { 0= IF .. } vor, was
hingeschrieben in der Form { IF ELSE .. } vom {ELSE}-Compiler im nicht-interpretierenden
Modus zu {0if}, compiliert als {?branch}, zusammengefaßt wird. Von dieser Art gibt es noch ein paar
weitere Kleinigkeiten, wie etwa {ENDIF;}, die alle zusammen ein wenig zu kürzerem Code und geringerer
Ausführungszeit beitragen.
Auch ohne die hier beschriebenen Mätzchen sind L4-Programme recht schnell, im allgemeinen schneller
als gleichartige "C"-Proggramme (s. Beispiel am Ende der Seite). Weiteres zur "Optimierung" dürfte
sich damit vorerst erübrigen...
Was neben allen anderen Details (hoffentlich...) hinreichend in der
beigefügten Dokumentation beschrieben ist.
Kommentare & Anregungen, egal welcher Art, interessieren mich sehr;
ggf. bitte e-mail.