több ötlet

This commit is contained in:
Richard Thier 2024-09-07 21:51:09 +02:00
parent f56a322328
commit 81890e9aa3

187
BASED.md
View File

@ -66,6 +66,27 @@ Lesz továbbá tagged union is - lásd lejjebb - és interfészek (ami tagged un
Szerintem itt a 'T' helyett talán érdemes csak egy interfész-nevet megengedni???
# Placement new
Igazából szükség van valamire, amivel `emplace_back` jellegű dolgokat írhatunk le, tehát a konstruktor kódját egy adott
másik memória címen futtatjuk, ahol elég hely van az adatnak. A placement new C++ alakja nekem nem túl szimpi:
// placement new in buf1-2
int *pInt = new (buf1) int(3);
DataSource *pds = new (buf2) DataSource("localhost:1212");
A C++20-as megoldás valamivel jobb, de az sem teljesen kényelmes szerintem...
https://en.cppreference.com/w/cpp/memory/construct_at
Főleg, hogy a C++ esetén ugye ahhoz, hogy adott memóriacímen lévő dolgot destruáljak, külön kell mókolni kézi
destruktor hívással. De annál a const adattagok értékét a fordító optimalizálásból trükkösen értheti, majd
persze emiatt ilyen hack-elések jelentek meg, mint az std::launder ami ezt megakadályozza...
Szóval erre szerintem jobb syntax kell - esetleg úgy is dönthetünk, hogy egyáltalán ne legyen ilyen (optimizer megoldja?)
Kérdés: Ha csinálok ilyet, akkor kell-e "placement delete" is?
# Rekord VS handler: copy és move konstruktorok / assignment
Mindenképp jobbnak látom a C++ féle move szemantikát a rust-hoz képest: tehát ha én mondom meg mi történjen!
@ -275,16 +296,124 @@ Ez szerintem low-level helyekre jó lehet... Ha jól definiálom mi történik,
## Jobb pointerek
Valahogy jó lenne elérni, hogy a default a "restrict" kulcsszóhoz legyen közelebb. Szerintem tömbre a default a restrict, de
a pointerre ki kell írni?
Valahogy jó lenne elérni, hogy a default a "restrict" kulcsszóhoz legyen közelebb.
Egy lehetőség, hogy tömbre (tehát ptr+méret-re)a default a restrict, de pointerre ki kell írni.
* Talán a tömbök külön típussá emelése ebben már eleve segít - igen... legyenek továbbá mérettel is ellátva stb. lásd ott
* Mivel az ownerek kezelnek csak ptr-eket, ők adhatnk ki referenciákat és array-eket, de azok ugye nem alias-olnak be könnyen
* A restrict kulcsszó támogatása a minimum - de talán kéne gondolkozni mit lehetne még kihozni, hogy jobb legyen!
* Ugye mivel van külön "tömb" típusunk, ezért arra is lehetővé kéne tenni a restrict-et pl. paramétereknél, különben bakis lesz.
Egy (talán elvetendő) ötlet: A pointer típusa lehessen csoportosítható:
handle A {
int n;
};
A@aliasinggroup1 variable = { 1 };
A@aligrp2 array[5] = { 1, 2, 3, 4, 5 };
inline int magic(A *ptr1, A *ptr2) {
ptr2->n = 42;
return ptr1->n; // return 1 when inlined
}
magic(&variable, &array[3]); // see that it cannot alias
Esetleg szükséges legyen ilyenkor a használati helyen is kiírni a típust?
inline int magic(A@aliasinggroup1 *ptr1, A@aligrp2 *ptr2);
Az a bajom ezzel... hogy "gyalázatosan ocsmány" sajnos ez mind...
Alternatíva:
* Tömbökről feltételezzük, hogy sosem alias-olnak be (hacsak nem "unrestrict" kulcsszavas).
* Referenciák sem alias-olódnak
Alternatíva (all-in):
* Mindent restrict-nek veszünk by default és az unrestrict-et be kell írni...
* Kicsit unsafe-nek hangzik és sok benne a meglepetés, de a perf jó.
Lásd FORTRAN: https://flang.llvm.org/docs/Aliasing.html
Alternatíva (typedef-szerű):
restrictptr PooledPtr int*;
restrictptr RawPtr int*;
inline int magic(PooledPtr ptr1, RawPtr ptr2) {
*ptr2 = 42;
return *ptr1; // Should optimize as: return 2
}
int i1 = 1;
int i2 = 2;
magic(&i1, &i2); // can optimize
Jelenleg ez az alternatíva tetszik a legjobban + persze a restrict kulcsszó támogatása emellett még pluszban!
+ Talán lehetne restrictptr PooledArr int[]; jellegű típus definíció is (tömbök alias kerülésére)
Szemantika:
* Ha csak különböző ilyeneket látok egy paraméterlistán, akkor restrict-et kódgenerálunk mindre.
* Ha csak különböző ilyeneket ÉS más típusú dolgokat... akkor is...
* Egy ugyan ilyen típusú és egy másik ugyan ilyen típusú restricptr között viszont kell aliasing!
* Egy (fenti példával élve) PooledPtr-es int* és egy sima int* között viszont nem kell!
* Van továbbra is restrict kulcsszó (C-s szemantikával)
Ezekkel a szabályokkal egész jó dolgot csináltunk szerintem, mert ha valaki kiad egy pointert, akkor kiadhat
hozzá ilyen plusz infókat, ami jelzi a fordítónak, hogy mik alias-olhatnak és mik nem. Egy jó példa, ha egy
játék mondjuk ilyen memory arénákban tárol dolgokat és az "engine" tudhatja, hogy melyik arénából adjuk ki
éppen a mutatót. Gyakorlatilag ez majdnem olyan, mintha valamit restrictptr-el deklarálunk, akkor annak
az összes előfordulásánál beíródik a generált pointer elé a "restrict" - ez csak annyiban téves, hogy
ha több ilyen is van egy függvényben / scope-ban azonos típusnéven, akkor azok egymással nem restrict-elnek!
Ez utóbbit első körben implementálhatjuk úgy, hogy ilyenkor lekerül a restrict, de úgy is, hogy kódgenerálással
megoldjuk csak a kettő közötti restrict-álást és fenn hagyjuk a kulcsszót. Ez egy kicsit bonyolítja a dolgokat...
Az ilyen pointer cast-olható az alaptípusára - de ezzel elveszítjük a speed boost lehetőségét olyankor!
Megj.: Szerintem ezzel generálható C-kód -fno-strict-aliasing mellett is akár, ha típus alapút nem akarok!
## Jobb referenciák
* A referenciának lehetne "értéket adni" - nem mint C++ban...
* A referencia "lényegében egy pointer, de nincs rajta aritmetika"...
* Kérdőjel hozzáadásával jelölhető, hogy null lehet-e (default: nem).
## Jobb tömbök
Néhány dologra van szerintem szükségünk:
* (Pointer + méret / range) jellegű tömb történet, ahol a méret / range lekérhető a típusból ADA-szerűen (.len) és az elemtípus is!
* Range subtype (ada szerűen) NEM kell külön a típusrendszerben, mert hajlok arra, hogy 0..méret lehessen csak (praktikus: dyn)
* Dinamikus esetben, hívhasd az ilyen függvény paramétert sort(array(ptr, 42)) történettel (ahol ptr egy pointer, 42 méret).
A fordító két függvényt is generál ilyen függvényekből. A típusrendszer szerint azt a függvényt hívjuk meg, amelyik gyorsabb!
Igazából ha fordítási időben ismert méretű tömbbel hívjuk meg, akkor a "sima" függvény hívódik, kivéve, ha ez valami linkelt lib,
mert abban az esetben természetesen a "valódi" függvény fog hívódni - ezt C-re fordításnál még nem látom át hogyan kéne, de majd
meg kellene oldani...
A lényeg, hogy a "tömb" típus az pointer + hossz minden esetben, de ahol tudja, a fordító kioptimalizálja a történetet!
Ha valami mást akar az ember, például performancia okokból, akkor simán adjon át egy pointert (és mondjuk null termináns).
Ha az "array" második paramétere nem fordítási idejű konstans szám, akkor ott is a dinamikus függvényt hívjuk majd meg.
A range, mint altípus megadás lehetővé tenné, hogy dolgok "osztozzanak" a méreten - pl. ECS egy játékmotorban, stb.
De mivel nem tól-ig, vagyis ADA-szerű range-jeink vannak, simán csak méret, ezért ha ilyet akarsz konstans számként tárolod.
Emellett természetesen a standard library-ban kell "vector" típus és az tudjon array-t is adni neked.
Szerintem az így kapott kód "már kellően biztonságos" általában. A vektorra debug build esetén legyen range check kötelezően!
A tömböknél a restrict lehet ESETLEG a default - de ennek fényében kellene unrestrict kulcsszó rájuk!
Lásd még:
https://youtu.be/MUISz2qA640?t=1980
ui.: Ha nem lesz operátor overload, akkor szerintem legyen több dimenziós tömb (nem a tömbök tömbje, hanem: matrix[15, 10, 42] = 5;
## Típusos indexek
Egy pointer a modern gépen 64 bit, sokszor tömörebben tudsz tárolni "referencia-szerűséget" ha indexet tárolsz rá!
@ -301,6 +430,18 @@ Viszont! A pointerek - mint itt is látszik - típusosak, de az indexek NEM! Vis
A syntax-on lehetne még variálni...
De az a helyzet, hogy erre már van megoldás jelenleg is (sőt C-ben is, struct-al):
record ChildIndex {
uint32_t index;
};
esetleg még jobb, ha "like-olást" is használunk közben:
record child_index {
like uint32_t index;
};
### NonOwningPointer - smartpointer
Ezt szerintem érdemes C++ template-el előre megcsinálni! A lényeg az, hogy kreálni lehet a dolgot - tehát van egy factory-szerű,
@ -352,37 +493,6 @@ invalidálódik, de nem is egyesével, hanem blokkosával láncolt lista!
Ezt esetleg nem is nyelvi elemként tennénk be, hanem library-ként a BASED-be is, az talán jobb?
## Jobb tömbök
Néhány dologra van szerintem szükségünk:
* (Pointer + méret / range) jellegű tömb történet, ahol a méret / range lekérhető a típusból ADA-szerűen (.len) és az elemtípus is!
* Range subtype (ada szerűen) NEM kell külön a típusrendszerben, mert hajlok arra, hogy 0..méret lehessen csak (praktikus: dyn)
* Dinamikus esetben, hívhasd az ilyen függvény paramétert sort(array(ptr, 42)) történettel (ahol ptr egy pointer, 42 méret).
A fordító két függvényt is generál ilyen függvényekből. A típusrendszer szerint azt a függvényt hívjuk meg, amelyik gyorsabb!
Igazából ha fordítási időben ismert méretű tömbbel hívjuk meg, akkor a "sima" függvény hívódik, kivéve, ha ez valami linkelt lib,
mert abban az esetben természetesen a "valódi" függvény fog hívódni - ezt C-re fordításnál még nem látom át hogyan kéne, de majd
meg kellene oldani...
A lényeg, hogy a "tömb" típus az pointer + hossz minden esetben, de ahol tudja, a fordító kioptimalizálja a történetet!
Ha valami mást akar az ember, például performancia okokból, akkor simán adjon át egy pointert (és mondjuk null termináns).
Ha az "array" második paramétere nem fordítási idejű konstans szám, akkor ott is a dinamikus függvényt hívjuk majd meg.
A range, mint altípus megadás lehetővé tenné, hogy dolgok "osztozzanak" a méreten - pl. ECS egy játékmotorban, stb.
De mivel nem tól-ig, vagyis ADA-szerű range-jeink vannak, simán csak méret, ezért ha ilyet akarsz konstans számként tárolod.
Emellett természetesen a standard library-ban kell "vector" típus és az tudjon array-t is adni neked.
Szerintem az így kapott kód "már kellően biztonságos" általában. A vektorra debug build esetén legyen range check kötelezően!
Lásd még:
https://youtu.be/MUISz2qA640?t=1980
ui.: Ha nem lesz operátor overload, akkor szerintem legyen több dimenziós tömb (nem a tömbök tömbje, hanem: matrix[15, 10, 42] = 5;
## Holy-C féle kiegészítések a switch statement-hez
Erről még nem vagyok meggyőzve, de néha hasznosnak tűnnek:
@ -1260,7 +1370,7 @@ Természetesen itt kellhet típust adni:
'some text word' 21 #inserts (word(W) float(F)) { .... }
Az inserts továbbá rendelkezhez output átirányítós paraméterrel:
Az inserts továbbá rendelkezhet output átirányítós paraméterrel:
#inserts [0] { ... }
@ -1703,12 +1813,17 @@ Szerintem ezeket inline-olható, function pointerré kéne "elkódolnom", akár
* Engine kódja
* Callstack - előre adott méret (pl. parancssori paraméter, vagy embeddednél beállított)
* Insert-stack - az input "nyelének" kiegészítésére egy char elemekből álló stack
* Adatstack, a műveleteivel - előre adott méret (pl. parancssori paraméter, vagy embeddednél beállított)
* Szimbólumtáblára MAP adatstruktúra (pl. kismap - név alapú lookupra + változó tároláshoz is [key alapján tudható melyik!])
* Complexword-ök "vektora" (mindig indexelés alapú! Nem pointerezős!
* Session storage - egy charakter vektorhoz hasonló, de csak állandóan növő storage + session reset rá.
* IO accessor (fájlműveletek, pipe-ok stb.)
Megj.:
* A különdféle "stack"-ek igazából használhatják ugyan azt az API-t.
* A session storage viszont nem lehet stack, sőt "vektor" sem, mert nem pointer invalidálódhat ki! Pl. egy realloc-os tömb se jó!
^^Ezzel a memória kialakítással a memory layout:
| enginecode | callstack | datastack | symbolmap -> ... <-malloc_store|insert_stack-> ... <- session_storage | src |
@ -1716,7 +1831,7 @@ Szerintem ezeket inline-olható, function pointerré kéne "elkódolnom", akár
Igazából embedded környezetben, ilyen kismap-szerű (vagy még egyszerűbb) szimbólumtáblával tehát a legtöbb dolog
vagy konstans, vagy csak két ponton van balról-jobbra és jobbról-balra folyton akár végtelenig (memhatár) növő rész!
A `malloc_store` egy embedded megoldásban "mozoghat", tehát mivel csak indexálással érjük el és NEM pointeresen, ezért
A `malloc_store` egy embedded megoldásban "mozoghat", tehát mivel csak indexálással érjük el és NEM pointeresen, ezért
ott memcpy-vel mozgatható, ha kezd elfogyni vagy a szimbólumtábla, vagy a session storage.
A jobbra-balra mozgó indexes elérésű malloc-ozgatón túl még egy ilyen memóriánk van, tehát egy cövek két oldalát is használva!