Otóż parę tygodni temu wprowadziłem do mojego języka wskaźniki na funkcje (póki co jedynie na GitHubie, ale najnowsza wersja kompilatora już blisko :>).
Działa to na takiej zasadzie:
@("stdlib\\stdio.ss")
use std; // `std` -> standardowa przestrzeń nazw funkcji będących w "standardzie" języka. `use` działa jak C++owe `using namespace`.
function<void> zrob_cos()
{
println("Hello World!");
}
function<int> main()
{
var<function<void>()> func = zrob_cos; // sama nazwa funkcji oznacza jej adres - podobnie jak w Object Pascalu; w C++ ten kod byłby czymś w rodzaju `func = &zrob_cos;` - tak dla uzupełnienia
func(); // wywołanie funkcji, na którą wskazuje wskaźnik
return 0;
}
W bajtkodzie wygląda to tak:
call(:__function_self_main__int_)
stop()
__function_self_zrob_cos__void_:
add(stp,1) // tak naprawdę zbędne, ale jest to dodawane przez kompilator "tak-w-razie-gdyby". Chodzi o to, że podczas wywoływania opcodu `call()`, na stos wrzucony zostaje instruction-pointer. Wcześniej był osobno callstack oraz osobno stos programu, lecz teraz jest to połączone, stąd też to `add` w tym miejscu, oraz `sub` na końcu. Może potem pomyślę jakby się tego pozbyć or something :P
__function_self_zrob_cos__void__body:
push("Hello World!")
call(:__function_std_println)
sub(stp,1) // konwencja wywoływania na zasadzie `cdecl` - funkcja, która wywołuje jakąś inną, czyści po niej stos. `stp` to pozycja stosu.
__function_self_zrob_cos__void__end:
sub(stp,1)
ret()
__function_self_main__int_:
add(stp,1)
push(er3) // pierwsza zmienna wczytana jest do tego rejestru, więc musimy go wrzucić na stos
__function_self_main__int__body:
mov(er3,@__function_self_zrob_cos__void_) // zaraz wytłumaczę o co z tym chodzi :P
acall(@__function_self_zrob_cos__void_)
{ krótkie wytłumaczenie:
Ten kod na początku wyglądał tak:
mov(er3, @__function...)
mov(er1, er3)
acall(er1)
Lecz wyniku propagacji kopiowania, zostało to skrócone do:
mov(er3, @__function...)
acall(@__function...)
Optymalizator nie może usunąć tego pierwszego przypisania (do rejestru `er3`), ponieważ jest to przypisanie do rejestru trzymającego zmienną (nawet mimo tego, że nie wpłynęłoby to w żaden sposób na kod :P).
Potem dodam propagację kopiowania w optymalizatorze wyrażeń (póki co posiada ją jedynie optymalizator bajtkodu), więc to już zostanie bezproblemowo rozwinięte do samego `acall`.
---------------------------
Also: istnieją 3 wywołania typu `call`:
call(adres) - skacze pod `instruction_pointer+adres` (tj.po prostu `adres` opcodów dalej; jak zwykłe `call` na x86).
acall(adres) - skacze bezpośrednio pod `adres`; pod x86 bodajże to jest `call 0x8:adres` czy coś w tym rodzaju :P
icall(string) - wywołanie komendy maszyny wirtualnej, np.`output.print`
}
mov(ei1,0) // to jest to nasze `return 0;`
jmp(:__function_self_main__int__end)
__function_self_main__int__end:
pop(er3) // przywracamy wartość starego `er3`
sub(stp,1)
ret()
// tutaj jest dodatkowo bajtkod tego `stdlib\stdio.ss`, ale jest on nieistotny
Odnośnie tego @nazwa labela
:
Otóż istnieją dwa sposoby pobrania adresu labela, najczęstszy z nich to poprzez dwukropek:
:nazwa_labela
- pobiera on adres labela relatywny względem aktualnego opcodu. Używany głównie (właściwie, to 'wyłącznie') podczas wywoływania call
.
Oraz @nazwa_labela
- pobiera on absolutny adres labela (tj.względem początku pliku). Używany np.właśnie we wskaźnikach na funkcje, jak w (bajt)kodzie powyżej.
Dodatkowo istotny jest fakt, że możliwe jest kompilowanie kodu do odpowiedników bibliotek (te biblioteki są potem linkowane statycznie wraz z aplikacją; tak na przykład zbudowane jest includowanie bibliotek standardowych - są one kompilowane wyłącznie raz i dostarczane są wraz z kompilatorem jedynie te skompilowane wersje + ich nagłówki).
I tutaj rodzi się problem:
Jeżeli na przykład napiszemy taką bibliotekę:
function<void> asdf()
{
}
public function<void> func() // tylko funkcje oznaczone jako `public` są eksportowane i widoczne poza biblioteką. "masowo" można skorzystać z makra `@visibility("public") / "private"` - tak na marginesie. Kiedyś to wszystko klarownie udokumentuję ;P
{
var<function<void>()> f = asdf;
f();
}
... wywołując f();
wywołamy acall
na pozycję 0
(bo funkcja asdf()
znajduje się na pozycji 0
w skompilowanym pliku). Niby ok, prawda? Jednak jeżeli zlinkujemy tę bibliotekę z programem i wywołamy funkcję func()
, także otrzymamy skok na pozycję 0
w pliku, co już nie będzie tym, co chcemy osiągnąć (bo opcode na pozycji 0
w zlinkowanym pliku nie odpowiada opcodowi 0
w bibliotece) :P
And that's the whole problem...
Nie mam pomysłów, jakby to poprawić. Jakby potrzebne były jakieś informacje, to oczywiście podam (ciężko tak wszystko w jednym poście od razu podać).