Grüß euch
Ich wollt mal mein letztes Projekt vorstellen, "Shi".
Shi ist eine winzige flotte Forth Engine für das Thumb2 Instruction Set
(sprich ARMv7-M und drüber). Der gesamte Code passt in weniger als 7kB
Flash und ~320B Ram.
Was Shi von anderen Forths abhebt ist, dass es in erster Linie als
embedded Skriptsprache gedacht ist und nicht als eigenständiges System.
Aus diesem Grund gibts es einen recht umfangreichen Header der diverse
Komfortfunktionen zur Verfügung stellt.
So ist es etwa möglich Forth Code zu compilieren und die erzeugten
Definitionen direkt aufzurufen:
Weiters ist es möglich Variablen zwischen Shi und C zu "sharen".
1
// Charles H. Moores Geburtstag
2
int32_tmoore_birthday{1938};
3
shi::variable(&moore_birthday,"moore_bday");
4
"2019 moore_bday @ -"_s;
5
6
// 2019 - 1938 = 81
7
int32_tage=shi::top();
8
9
// Oder ebenfalls via literals
10
uint8_tmoore_age;
11
"moore_age"_v(&moore_age);
12
"moore_age c!"_s;
13
// moore_age == 81
Eine C-API gibts auch, die ist jedoch naturgemäß weniger elegant. ;)
Trotzdem werden auch dort sämtliche Funktionen (abseits der multiple
returns und type deduction) unterstützt.
Falls das Ram mal ausgeht unterstützt Shi auch noch das Compilieren ins
Flash. Das werd ich in kürze in der Doku (oder hier bei Interesse)
hinzufügen.
Mehr Beispiele und den Source gibts hier:
https://gitlab.com/higaski/Shi
> Was Shi von anderen Forths abhebt ist, dass es in erster Linie als> embedded Skriptsprache gedacht ist und nicht als eigenständiges System.
Man koennte den Eindruck haben das du auf einen der vorderen Plaetze
beim obfuscated C contest scharf bist, aber vielleicht taeusche ich mich
ja und es hat einen sehr sinnvollen praktischen Nutzen. Kannst du mir
den mal erklaeren?
Olaf
Das könnte daran liegen dass dieses "obfuscated C" in Wahrheit C++ ist?
Der praktische Nutzen ist jener jeder anderen eingebetteten
Skriptsprache auch. Es kann unabhängig von der nativen Firmware Code von
einer externen Quelle (ext. Flash, USB, UART, etc.) geladen,
interpretiert, compiliert(!) und ausgeführt werden.
Der Unterschied zu den bekannten und gängigen Vertretern wie MicroPython
und Lua liegt in der Größe und Geschwindigkeit. Während MicroPython und
Lua mindestens 40-100kB Flash beanspruchen kommt Shi mit 7kB aus.
Während MicroPython und Lua je nach Anwendung etwa ~30-200x langsamer
sind als optimierter C Code ist Shi nur ~4-5x langsamer.
Das alles erkauft man sich halt durch
-) 0% Portierbarkeit da Shi abgesehn vom Header 100% Assembler ist
-) altmodische und ungewöhnliche Forth-Syntax
Wäre was Lisp- oder Scheme-artiges nicht anwenderfreundlicher und auch
zeitgemäßer gewesen als von allen denkbaren Sprachen ausgerechnet Forth?
Aber Du wirst sicher Deine Gründe haben, wenngleich es mir schwer fällt
mir auszumalen wie die aussehen könnten sofern Du es nicht einfach nur
aus Spaß an der Freude gemacht hast um nicht einzurosten oder um lange
kalte Winterabende unbeschadet zu überstehen.
Vincent H. schrieb:> -) altmodische und ungewöhnliche Forth-Syntax
Forth-Syntax? Hat Forth eine Syntax?
Aber Spaß beiseite, als eingebettete Skriptsprache hat es den Vorteil,
dass nicht jeder Depp daran rumbasteln kann, ohne gleich alles platt zu
machen.
Witzig finde ich jedenfalls, dass Forth die "Sprache der vierten
Generation" werden sollte. Wir können froh sein, dass dieser Kelch an
uns vorüber ging…
Bernd K. schrieb:> Wäre was Lisp- oder Scheme-artiges nicht anwenderfreundlicher und auch> zeitgemäßer gewesen als von allen denkbaren Sprachen ausgerechnet Forth?>> Aber Du wirst sicher Deine Gründe haben, wenngleich es mir schwer fällt> mir auszumalen wie die aussehen könnten sofern Du es nicht einfach nur> aus Spaß an der Freude gemacht hast um nicht einzurosten oder um lange> kalte Winterabende unbeschadet zu überstehen.
Ich hab nie ernsthaft Lisp Code oder ähnliches geschrieben, bezweifel
aber dass ein "embeddable" Lisp in weniger als 7kB Flash auch nur
annäherend an der Performance und Code-Density von Forth kratzt.
Weiters lässt sich Forth durch seinen Stack-basierten Ansatz sehr gut in
C/C++ integrieren. Wie wichtig dass ist sieht man etwa bei Lua, dass bei
meinem Lerp-Benchmark (siehe repo) MicroPython um den Faktor 6.8
abhängt!
Zwischen Lua und meiner Forth Implementierung liegt dann übrigens noch
einmal der Faktor 6.4...
Hätte ich die 40kB für Lua und wäre es mir schnell genug gewesen, dann
wär ich auch dabei gelieben. Trotz aller Eleganz find ich die Forth
Syntax nämlich auch recht grausig. ;)
Ist der nächst größere ARM SoC zu teuer so baut man sich sein eigenen
Forth Interpreter?
Sehr schön ;)
Leider bekomm ich bei Forth zu viel Hirnknoten, also bleib ich bei Lua.
Da es sich bei Shi um eine Library handelt muss die eigentlich Routine
die ins Flash schreibt vom User selbst implementiert werden. Wie diese
auszusehn hat steht in der Doku.
Wechselt man den "data-space" zu text, so sollte man die klassischen
Beschränkungen beachten die Flash nunmal so mitbringt.
-) Variablen werden unfreiwillig Konstanten ;)
-) Klassische create does> Konstrukte machen recht wenig Sinn wenn
hinter create irgendwelche veränderbaren Daten stehn sollen
Außerdem gilt zu beachten dass ein >text ... >data block bis zum Schluss
temporär in dem bei der Initialisierung zugewiesenen Ram liegt. Wer also
zig-kB ins Flash compilieren will der muss dies eventuell in mehreren
kleineren Blöcken tun.
Daumen hoch, Forth scheint nicht kleinzukriegen zu sein.
Was natürlich noch eine Steigerung davon wäre: Das Ding auf eine noch
kompaktere Architektur zu portieren. Da machen einige Leute (in den
Augen mancher) wüste, aber hocheffiziente Sachen mit Forth auf
FPGA-Soft-Cores wie der J1 oder der ZPU-Architektur. Wobei bei ersterer
die Option C-Compiler wegfiele, dafür ist sie so kompakt, um sie einfach
als Co-Prozessor für einen weiteren Prozess a la Parallax zu
instanzieren.
Für einige Safety-relevante Maschinen ist das auch für die Industrie
nicht uninteressant..
Nächstes Update.
postpone war noch fehlerhaft. Ich wusste nicht dass postpone compile und
[compile] ablöst und abhängig davon ist ob die zu "postponende"
Definition immediate ist oder nicht.
Folgender Teil des Test-Suits schlug deshalb fehl:
1
": GT1 123 ;"_s;
2
": GT4 postpone GT1 ; immediate"_s;
3
": GT5 GT4 ;"_s;
Anstatt dass die Stacktiefe gleich blieb pushte GT5 bereits während dem
compilieren 123 auf den Stack.
Der Fix war zwar flott eingebaut, funktionierte aber wegen einer
gemeinen Eigenheit des Thumb2 Assemblers nicht sofort. Und zwar benutze
ich zum Compilieren von Funktionsaufrufen das Wort "compile,".
compile, löst lange Funktionsaufrufe mit der Instruktion blx auf. In der
blx Instruktion ist das unterste Bit für den Wechsel zw. Thumb und ARM
Instruction Set reserviert. Ein gesetztes Bit steht hierbei für Thumb,
ein gelöschtes für ARM. Weil der ARMv7-M ja kein ARM kann hab ich
innerhalb von compile, stets #1 zur Adresse dazuaddiert.
Das geht aber nur so lange gut, solange Forth intern selbst nur gerade
Adressen an compile, übergibt. Innerhalb der Definition von postpone
lade ich nun aber das Symbol einer Funktion direkt via Pseudoload
1
ldrr,=symbol
Der Assembler ist so smart und setzt dabei automatisch das untereste
Bit. Zählt man jetzt in compile, noch #1 dazu, dann explodiert der
Funktionsaufruf natürlich... :D