Forum: Projekte & Code Shi - embeddable Forth Engine


von Vincent H. (vinci)


Lesenswert?

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:
1
#include "shi.hpp"
2
3
using namespace shi::literals;
4
5
// Erzeuge Definition "mean"
6
shi::evaluate(": mean + 2 / ;");
7
shi::Word mean{"mean"};
8
int32_t mittelwert = mean(100, 200);
9
10
// Oder via literals
11
": mean + 2 / ;"
12
mittelwert = "mean"_w(100, 50);
13
14
// Mehrere returns
15
": range 0 do i loop ;"_s;
16
std::tuple<int32_t, int32_t, int32_t, int32_t> t{"range"_w(4)};


Weiters ist es möglich Variablen zwischen Shi und C zu "sharen".
1
// Charles H. Moores Geburtstag
2
int32_t moore_birthday{1938};
3
shi::variable(&moore_birthday, "moore_bday");
4
"2019 moore_bday @ -"_s;
5
6
// 2019 - 1938 = 81
7
int32_t age = shi::top(); 
8
9
// Oder ebenfalls via literals
10
uint8_t moore_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

von Olaf (Gast)


Lesenswert?

> 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

von Vincent H. (vinci)


Lesenswert?

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

von Bernd K. (prof7bit)


Lesenswert?

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.

: Bearbeitet durch User
von Uhu U. (uhu)


Lesenswert?

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…

von Yalu X. (yalu) (Moderator)


Lesenswert?

Uhu U. schrieb:
> Witzig finde ich jedenfalls, dass Forth die "Sprache der vierten
> Generation" werden sollte.

Nein, da hast du etwas missverstanden.

von Uhu U. (uhu)


Lesenswert?

Yalu X. schrieb:
> Nein, da hast du etwas missverstanden.

Was?

von Vincent H. (vinci)


Lesenswert?

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. ;)

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

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.

von Vincent H. (vinci)


Lesenswert?

Ein bisschen was über Shi's interne Struktur und erweiterte Doku:
http://www.higaski.at/projects/Shi/doc/html/index.html


Ins Flash compilieren geht nun via >text ... >data block:
1
">text"_s;
2
": six 6 ;"_s;
3
": seven six 1+ ;"_s;
4
">data"_s;

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.

von Fitzebutze (Gast)


Lesenswert?

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..

von Vincent H. (vinci)


Lesenswert?

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
ldr r, =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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.