diff --git a/BASED.md b/BASED.md index c91d871..237b716 100644 --- a/BASED.md +++ b/BASED.md @@ -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!