Tail-call optimalisatie in C is relatief recent

Een aanroepende functie kon een declaratie zien als int f();, terwijl de feitelijke aanroep $n > 0$ argumenten kon hebben en de feitelijke functie $m \le n$ parameters. Dit zou niet altijd werken als de aangeroepen functie de argumenten zelf zou verwijderen. Daarom moest de aanroepende functie de argumenten verwijderen tussen de aanroep en de daaropvolgende return, waardoor de aanroep geen tail-call werd.

De ontwikkeling van compilers

Toen ik in 1994 keek naar de toenmalige C-compilers, voerden deze geen tail-call optimalisatie uit voor het type gebruik dat in het artikel wordt beschreven. In 2001 implementeerde Mark Probst tail-call optimalisatie in GCC met een aparte aanroepconventie. Hij somt de beperkingen van de toen bestaande optimalisatie in GCC op in sectie 6.4, waaronder: "Het kan geen indirecte aanroepen afhandelen" (wat gebruikt zou worden bij tail-calls voor interpreter dispatch).

Ik heb het onderwerp daarna niet meer actief gevolgd; de goto van GCC was voldoende. Ik had weinig reden om aan te nemen dat er iets was veranderd wat betreft de ondersteuning van GCC voor tail-calls, hoewel een release-note melding maakte van sibcalls*.

Recente bevindingen en implementaties

Vorige jaar las ik het paper "Copy-and-Patch Compilation" van Xu en Kjolstad, waarin zij gebruikmaken van tail-call optimalisatie. Naar aanleiding hiervan heb ik getest of GCC en Clang tail-call optimalisatie kunnen uitvoeren voor de specifieke types die in het artikel worden genoemd. Dat blijkt te werken.

Xu en Kjolstad rapporteren dat zij 100.000 codefragmenten gebruiken, terwijl wij ons in Gforth beperken tot minder dan 2.000 (voor VM-instructies, variaties van stack-caching, statische superinstructies, etc.). De mogelijkheid om 100.000 fragmenten te gebruiken, zou ons in staat stellen technieken toe te passen die te veel verschillende codefragmenten vereisen om bruikbaar te zijn in een systeem gebaseerd op goto *.

We hebben dit nog niet in Gforth geïmplementeerd, dus gefeliciteerd aan de Python-gemeenschap dat zij hier als eerste zijn.

Praktische toepassing in een Forth-interpreter

Recentelijk is er een (toy) interpreter geïmplementeerd voor een variant van Forth die tail-calls gebruikt voor het dispatching van instructies. Het belangrijkste voordeel hiervan was dat alle ingebouwde functies zich konden gedragen als instructies zelf, in plaats van een aparte "roep een ingebouwde functie aan"-instructie te hebben. Ook is er ondersteuning voor enkele super-instructies, wat aanzienlijk hielp omdat deze niet veel verschillen van andere instructies.

De prestaties zijn redelijk, maar de optimizer vereist nog wat werk om het gewenste niveau te bereiken. Het uiteindelijke doel is om dit op een kleine computer te draaien, de interpreter via een browser toegankelijk te maken en mensen code te laten schrijven waarmee een LED-matrix kan worden aangestuurd (vergelijkbaar met projecten zoals Noisebridge, maar op kleinere schaal).

De code hiervoor is beschikbaar via GitHub (lpereira/lwan) en betreft een variant van de Forth Haiku-taal. Deze taal stelt gebruikers in staat om kunst te maken met kleine stukjes Forth-code, vergelijkbaar met hoe ShaderToy werkt voor GLSL.