ctai - compile time assembly interpreter

ctai - compile time assembly interpreter
stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:607
9

Cześć,

Ostatnio na L4 trochę się nudziłem i napisałem wspomniany w temacie projekt + artykuł o nim. Nie chcę tutaj się rozpisywać, jeżeli ktoś chce, to zapraszam do artykułu. Jest to mój pierwszy dłuższy art, więc wszelka konstruktywna krytyka oczywiście mile widziana.

Dla tych, którzy nie zajrzą do artykułu, o co chodzi chyba najlepiej wyjaśni przykład:

Kopiuj
#include "string.hpp"
#include "execute.hpp"

using code = decltype(
    "mov ebp , esp "
    "push eax " // declare four variables
    "push eax "
    "push eax "
    "push eax "
    "mov DWORD PTR [ ebp + 8 ] , 0 "
    "mov DWORD PTR [ ebp + 12 ] , 1 "
    "mov DWORD PTR [ ebp + 16 ] , 1 "
    "mov DWORD PTR [ ebp + 4 ] , 1 "
":loop_label "
    "mov eax , DWORD PTR [ ebp + 4 ] "
    "mov ebx , 15 " //we want to get 15th fibonacci element
    "cmp eax , ebx "
    "jge .end_label "
    "mov edx , DWORD PTR [ ebp + 8 ] "
    "mov eax , DWORD PTR [ ebp + 12 ] "
    "add eax , edx "
    "mov DWORD PTR [ ebp + 16 ] , eax "
    "mov eax , DWORD PTR [ ebp + 12 ] "
    "mov DWORD PTR [ ebp + 8 ] , eax "
    "mov eax , DWORD PTR [ ebp + 16 ] "
    "mov DWORD PTR [ ebp + 12 ] , eax "
    "mov eax , DWORD PTR [ ebp + 4 ] "
    "mov ebx , 1 "
    "add eax , ebx "
    "mov DWORD PTR [ ebp + 4 ] , eax "
    "jmp .loop_label "
":end_label "
    "mov eax , DWORD PTR [ ebp + 16 ] "
    "exit"_s);

int main()
{
    return cai::execute_code<code>;
}

cai::execute_code, w czasie kompilacji, sparsuje kod asm, zamieni na opcody, wykona i zwróci to co było na końcu w eax, czyli piętnasty element ciągu Fibonacciego - 610.

Kod programu, który wygeneruje kompilator: (kompilowane clangiem 4.0)

Kopiuj
main:                                   
        mov     eax, 610
        ret

(Fun fact: Jak zapomnimy o jednej spacji po "mov ebp , esp " kompilator rzuci nam w twarz 70KB templejtowych błędów ^^)

All in one plik: https://github.com/stryku/ctai/blob/master/all_in_one_fibonacci.cpp
cały ctai v0.1 + powyższy przykład. Można się bawić na godbolcie.

Jeżeli ktoś, mimo wszystko, będzie chciał zaglądnąć do repo, to wspomnę tylko, że z perspektywy czasu widzę, że niektóre rzeczy można było inaczej/lepiej napisać. Wersja 2.0 będzie je uwzględniała. Na razie chciałem pokazać światu 1.0 (:

Repo: https://github.com/stryku/ctai
Art: http://stryku.pl/poetry/ctai.php

W artykule jest napisane co ctai może, a czego nie. Sam artykuł ma na celu wyjaśnienie jak ctai działa i co się dzieje pod spodem. Jeżeli znajdzie się ktoś kto arta przeczyta i dalej nie będzie wiedział WTF, to znak, że art jest do d**y. W takim wypadku byłbym wdzięczny o info co jest nie tak, co źle wyjaśniłem itp.

kq
Moderator C/C++
  • Rejestracja:prawie 12 lat
  • Ostatnio:3 dni
  • Lokalizacja:Szczecin
0

Pobieżnie przejrzałem. Całkiem fajne, chociaż dużo kodu byś uniknął gdybyś użył Boost.MPL albo (lepiej) Boost.Hana, albo jakiegoś innego Metala. To co mnie najbardziej interesowało - _s, niestety jest niestandardowym rozszerzeniem. Warto by o tym wspomnieć, jako o obecnym ograniczeniu języka. (był proposal do C++17 aby wprowadzić templated user defined literala ze stringa, ale komisja uznała, że lepiej będzie poczekać kilka lat dodać bardziej ogólne definiowanie tablic jako parametrów szablonów)


edytowany 2x, ostatnio: kq
stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:607
0

Dzięki za feedback, w najbliższym czasie wrzucę info odnośnie _s. Głównym celem było napisanie czegoś większego samemu, a dopiero potem, jak będę miał podstawy, zacząć używać Hany i tym podobnych. Coś jak z pisanie listy dwukierunkowej od zera, żeby ogarnąć jak to działa.

Azarien
  • Rejestracja:ponad 21 lat
  • Ostatnio:29 minut
0

Dobra, może działa i coś robi, tylko że...
ten kod jest zupełnie nieczytelny.
Do jakiejkolwiek „produkcji” się absolutnie nie nadaje.

WTF na 3000 linii.

w czasie kompilacji, sparsuje kod asm, zamieni na opcody, wykona i zwróci to co było na końcu

Tylko po co, skoro w czasie kompilacji to można napisać normalnie w C++, a bardziej przydatne byłoby to samo w run-time ;-)

Nie zrozum mnie źle: przyznaję że kod jest być może i „genialny” i można się nim pochwalić jako ciekawostką. Zastosowania mu brak ;)

edytowany 1x, ostatnio: Azarien
stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:607
2

To że ctai będzie tylko ciekawostką wiedziałem już przed rozpoczęciem. Jest on raczej oderwaniem od codziennego kodzenia w robocie.
Wiem też, że można było to napisać z pomocą constexpr funkcji itp, ale chciałem sobie utrudnić
Poza tym, napisanie interpretera, który działa w runtime w ogóle nie daje frajdy :)

vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

Ogólnie zadanie ambitne acz pozbawione dla mnie sensu.
Może gdyby to był in-memory assembler dla run-time to mógłbym znaleźć zastosowanie, ale compile-time?
Załóżmy że gdzieś po drodze w moim ASM mam błąd - jak go znajdę?

A interpreter w czasie run-time jak najbardziej może być ambitny a jednocześnie można sobie stopniować ilość pracy.
Przykład: implementacja LISP-a jako REPL.

stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:607
0

Tak jak pisałem wyżej, wiem, że używanie ctai w kodzie produkcyjnym (w sumie w żadnym) nie ma sensu. Ostatnio zaciekawiłem się pisaniem rzeczy, które wykonują się w czasie kompilacji. Do tego jest stereotyp, że szablony są trudne, więc chciałem zobaczyć czy dam radę takie coś napisać.

Jeżeli doszukiwać się w tym sensu to byłoby nim:
-lepsze ogarnianie szablonów
-satysfakcja (:

stryku
  • Rejestracja:ponad 11 lat
  • Ostatnio:prawie 2 lata
  • Postów:607
0

ctai v2.0 :)
Arta póki co nie ma, ale pewnie się pojawi bo trochę zabawy było.
W sumie wszystko co jest niżej można znaleźć na githubie.

Ważniejsze zmiany:

Mniej ważne:

  • dzielenie kodu na funkcje
  • więcej instrukcji asm
  • jeden blok pamięci dla wszystkich. Taki RAM.

Czego nie ma, a można pomyśleć, że jest

  • zamknięcie wątku rodzica, nie zamyka jego dzieci
  • ochrony pamięci

Przykład
(Kod dałem na koniec, żeby nie rozwalić posta)
Przykład trywialny, ale nic większego nie dałem rady skompilować - za mało RAMu mam.
W inpucie czekają dwie liczby - elementy do obliczenia.

Master
-tworzy dwa wątki - slave'y
-joinuje slave'y
-wychodzi z wątku

Slave
-lockuje mutexa do wczytania inputu,
-wczytuje liczbę,
-unlockuje mutexa inputu,
-oblicza fibonacciego,
-lockuje mutexa outputu,
-wypisuje rezultat,
-unlockuje output,
-wychodzi z wątku.

Wynik tego wszystkiego, czyli to co wypisze skompilowany już program:
sync output

Jak usuniemy całą synchronizację, output będzie wyglądał tak:
output

Oczywiście cały input i output bazuje na ascii, przy wczytywaniu liczby wczytujemy kody ascii cyfr, a potem zamieniamy je wszystkie w liczbę. Analogicznie przy outpucie.

Liczby:

  • Maszynę mam taką:
    Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz
    16GB RAMu
  • Kompilacja przykładu bez synchronizacji:
    czas: real 1m37.620s
    RAM: ~15,5GB
  • Kompilacja przykładu z synchronizacją:
    czas: real 2m14.533s
    RAM: ~18GB

Wszystkie przykłady do tej wersji możecie znaleźć tu: https://github.com/stryku/ctai/tree/master/examples/v2.0

Kod przykładu z synchronizacją:

Kopiuj
#include "ctai/execute/execute_code.hpp"
#include "ctai/declare_code.hpp"
#include "ctai/runtime/io.hpp"
#include "kernel/thread.hpp"
#include "kernel/io.hpp"
#include "kernel/utils.hpp"
#include "kernel/memory.hpp"
#include "kernel/mutex.hpp"

#include <iostream>

//eax - element to calculate
using fibonacci = decltype(
":fibonacci "
        "push esi "
        "push ecx "
        "push edx "

        "mov ecx , eax "
        "cmp eax , 0 "
        "je .fib_ret_0 "

        "mov edx , 0 "
        "mov esi , 1 "

    ":fib_loop "
        "mov eax , esi "
        "add edx , eax "
        "mov esi , edx "
        "mov edx , eax "
        "dec ecx "
        "cmp ecx , 0 "
        "jne .fib_loop "
        "jmp .fib_end "

    ":fib_ret_0 "
        "mov eax , 0 "

    ":fib_end "
        "pop edx "
        "pop ecx "
        "pop esi "
        "ret "_s
);

//eax - fibonacci element
//ecx - result
using write_result = decltype(
":write_result "
        "push ebx "
        "push edi "
        "push ebp "

        "sub esp , 30 "//esp - buffer ptr
        "mov edi , eax "//edi - fibonacci element
        "mov ebp , esp "//ebp - buffer ptr

        //we will write something like this:
        //fib(15) = 610
        //and the new line

        //this is ugly but ctai doesn't support static data definitions yet

        //write fib(
        "mov BYTE PTR [ ebp ] , 'f' "
        "mov BYTE PTR [ ebp + 1 ] , 'i' "
        "mov BYTE PTR [ ebp + 2 ] , 'b' "
        "mov BYTE PTR [ ebp + 3 ] , '(' "
        "mov BYTE PTR [ ebp + 4 ] , 0 "
        "mov eax , ebp "
        "call .write_string "


        //write element
        //convert to string
        "mov ebx , ebp "//ebx - buffer ptr
        "mov eax , edi "//eax - fibonacci element
        "call .uitoa "

        //write to stdout
        "mov eax , ebp "//eax - buffer ptr
        "call .write_string "


        //write ) =
        "mov BYTE PTR [ eax ] , ')' "
        "mov BYTE PTR [ eax + 1 ] , ' ' "
        "mov BYTE PTR [ eax + 2 ] , '=' "
        "mov BYTE PTR [ eax + 3 ] , ' ' "
        "mov BYTE PTR [ eax + 3 ] , ' ' "
        "mov BYTE PTR [ eax + 4 ] , 0 "
        "mov eax , ebp "
        "call .write_string "


        //write result
        //convert to string
        "mov ebx , ebp "//ebx - buffer ptr
        "mov eax , ecx "//eax - fibonacci result
        "call .uitoa "

        //write result to stdout
        "mov eax , ebp "//eax - buffer ptr
        "call .write_string "

        //write new line
        "mov al , 10 "
        "call .sys_write "

        "add esp , 30 "

        "pop ebp "
        "pop edi "
        "pop ebx "
        "ret "_s
);

using slave_code = decltype(
":slave_code "
        "mov esi , BYTE PTR [ esp ] "//esi - ptr to input sync mutex
        "mov edi , BYTE PTR [ esp + 1 ] "//edi - ptr to output sync mutex
        "mov eax , esi " //eax - ptr to input mutex

        //lock input
        "call .lock_mutex "

        "call .read_uint " //eax - element to calculate
        "mov edx , eax "//edx - fib element to calculate

        //unlock input
        "mov eax , esi "//eax - ptr to input mutex
        "call .unlock_mutex "

        //calculate fibonacci element
        "mov eax , edx "//eax - fibonacci element to calculate
        "call .fibonacci "//eax - calculated fibonacci element
        "mov ecx , eax " //edx - calculated fibonacci element

        //lock output
        "mov eax , edi "//eax - ptr to output sync mutex
        "call .lock_mutex "

        "mov eax , edx "//eax - fibonacci element to calculate
        "call .write_result "

        //unlock output
        "mov eax , edi "//eax - ptr to output sync mutex
        "call .unlock_mutex "

        "call .sys_exit_thread "_s
);

using main_code = decltype(
":main "
        "sub esp , 2 " //two mutexes to sync input and output in slaves threads

        //clear mutexes
        "mov BYTE PTR [ esp ] , 0 "
        "mov BYTE PTR [ esp + 1 ] , 0 "

        //prepare arguments to create slaves threads
        "mov ebx , .slave_code "//slave thread entry point
        "mov ecx , 50 " //slave thread priority
        "mov edx , esp " //pointer to args - pointers to mutexes

        //create two slaves
        "call .sys_create_thread " //eax - slave 1 id
        "mov edi , eax "//edi slave 1 id

        "call .sys_create_thread " //eax - slave 2 id

        //join the slaves
        "call .join_thread "//join thread 2

        "mov eax , edi "
        "call .join_thread "//join thread 1

        "call .sys_exit_thread "_s
);

using code = ctai::declare_code<ctai::include::thread,
                                ctai::include::io,
                                ctai::include::utils,
                                ctai::include::mutex,
                                fibonacci,
                                slave_code,
                                write_result,
                                main_code>;


//program input - two fibonacci elements to calculate
using input_t = decltype(
"15 10 "_s
);

using execution_result = ctai::execute2::execute_code<code, input_t>;

int main()
{
    constexpr auto output = ctai::runtime::io::make_runtime_output<execution_result::output>();

    std::cout << "Executed instructions: " << execution_result::ret_val << "\n";

    std::cout << "execution output: \n";

    for(auto c : output)
        std::cout<<c;

    return 0;
}
edytowany 1x, ostatnio: stryku

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.