Teorema stergerii cu rest

Posted in Codare cu premeditare, Slagare internationale on April 13th, 2011 by Mihnea

Am stabilit deja ca lista inlantuita e un subiect delicat. Este timpul sa aratam ca nu doar Ovidiu “MVP” Cucu are probleme cu dinsa, ci si aia carora el le pupa bombeurile au mici dificultati in intelegerea acestei structuri de date fenomenal de complicate. Sa urmarim pentru inceput un scurt material motivational in care se incearca stergerea unei configuratii dintr-un proiect in Visual Studio 2008 (nu stiu de ce se misca in reluare, muie youtube sau muie ffdshow):

Asta e, nu s-a sters. Lucrurile devin insa cu 57% mai interesante daca incercam sa stergem mai multe configuratii deodata:

Acum s-a reusit stergerea, dar ar fi fost un pic mai bine daca ar fi sters ce i-am zis sa stearga, nu ce doream sa pastram (de asemenea gasesc interesant cum in dropdown a ramas selectat “Debug2011”, desi cind il deschizi nu e acolo). Care sa fie explicatia?

Pai e destul de simplu, de fapt. Proiectul are doua configuratii care se cheama Debug5, doua Debug6 si asa mai departe, cite una pentru fiecare platforma (Win32 si x64). Acest lucru se poate observa cu ochiul liber in vcproj, unde nu exista Debug5, ci Debug5|Win32 si Debug5|x64. Mai departe in arhitectura lui VS, o configuratie trebuie sa existe in toate platformele – nu poti, de exemplu, sa ai Debug5 doar in Win32. Orice programator care a folosit vreodata platforme multiple in VC stie asta. Indianul care a implementat stergerea nu stie. Cind apesi pe “Remove”, el cauta prima configuratie care se cheama “Debug5” si o rade. In felul asta se sterge doar aia de Win32, dar cind deschide din nou dropdown-ul, o gaseste p-aia de x64 si o afiseaza acolo, iar acum proiectul e intr-o stare invalida, cu o configuratie care nu exista in toate platformele. Workaround-ul este sa stergi o configuratie, sa inchizi dialogul de edit, sa-l deschizi din nou si s-o stergi inca o data. In felul asta se sterge si Debug5|x64, si proiectul e utilizabil din nou.

Daca te aventurezi sa stergi mai multe configuratii de-odata, ca in al doilea filmulet, se evidentiaza o noua latura a retardarii indiene, aceasta ruda karmica a retardarii ardelene. Boul sterge configuratiile dupa index, nu dupa nume. Daca stersul propriu-zis i-ar fi mers, asta n-ar fi fost o problema. Din pacate insa, deoarece configuratia ramine acolo cind se manifesta prima parte a incompetentei, se fut maparile intre indecsi si nume, asa ca atunci cind dai sa stergi Debug6, se sterge de fapt altceva (jumate de altceva, mai exact). Mai departe retardarea 1 se compune cu retardarea 2 intr-un fel sublim, astfel incit tu dai sa stergi 6 din cele 8 configuratii, dar de fapt se sterg doar 3, printre care si singurele doua pe care vroiai de fapt sa le pastrezi.

VC are acest bug de la 2005, de cind a fost introdus noul configuration manager, cu platforme si cacat. De atunci au iesit 2005 SP1, 2008, 2008 SP1 plus o intreaga pleiada de hotfix-uri, dar nimeni nu s-a ostenit sa invete cum functioneaza de fapt configuratiile si cum se cauta intr-o lista. Bug-ul s-a rezolvat in sfirsit in 2010, pacat ca ala e inutilizabil gratie rescrierii editorului in dotniet (plus alte “goodies”, gen gunoiul ala de MSBuild).

Sa trecem acum in tabara adversa. Aparent exista o corelatie intre retardare si softurile de instalat alte softuri. Cel mai idiot lucru scos vreodata de Microsoft este MSI (chiar luind in considerare Songsmith si reclama pentru el). Cel mai idiot lucru scos de Apple este PackageMaker, echivalentul hipsteristic al lui MSI. Iata ce face PackageMaker cind vrei sa stergi un target din installer:

Pentru asta n-am o explicatie, caci nu inteleg cum functioneaza mintea oamenilor care programeaza pentru Apple. Cert e ca atunci cind stergi ceva, reuseste sa amestece restul target-urilor si chiar sa lase un fisier orfan, atasindu-l de root-ul proiectului. As dori sa mentionez ca in mod normal n-ai cum sa atasezi fisiere direct acolo, folosind UI-ul lui.

Workaround-ul este sa editezi proiectul de mina, caci este tinut in XML. In XML-uri, mai exact. Cite 2 XML-uri pentru fiecare target, plus un XML mare, to rule them all (in speta, 71 de fisiere in proiectul din film). Si aceste XML-uri sint scrise pe o singura linie, cum mentionam in post-ul despre XCode. Si numele lor conteaza, fiind prefixate cu un numar. Si alea in care zici ca vrei sa instaleze tot ce-i intr-un director contin si numele fisierelor din directorul ala, la momentul la care ai facut proiectul. Care nu folosesc la nimic, pentru ca daca adaugi un nou fisier intre timp, se va copia, asa cum iti doresti, dar nu va fi trecut in XML. Si asa mai departe, in pula mea.

Ca sa fiu perfect obiectiv ar trebui sa expun si o muie dintr-un IDE de Linux. Gluma asta se scrie singura, va las pe voi sa va imaginati ce vreti.

Oricum, ce vroiam sa spun e ca le doresc epidermoliza buloasa alora care-s responsabili de chestiile astea. Sau munca silnica pe viata in mina cu Silviu Ardelean ca team leader.

Update: am elucidat si misterul PackageMaker. Si aici retardarea este usor de inteles: gunoiul are un index.xml in care, odata ce-l formatam sa nu mai fie tot pe o singura linie, putem vedea chestii de genul:

<choice title="8.5 plug-in" id="choice210">
	<pkgref id="com.nextlimit.realflowPluginForMaya.realflow.pkg"/>
</choice>
<choice title="2008 plug-in" id="choice211">
	<pkgref id="com.nextlimit.realflowPluginForMaya.realflow-1.pkg"/>
</choice>
<choice title="2009 plug-in" id="choice212">
	<pkgref id="com.nextlimit.realflowPluginForMaya.realflow-2.pkg"/>
</choice>

Mai jos in fisier scrie si:

<item type="file">01realflow.xml</item>
<item type="file">02realflow.xml</item>
<item type="file">03realflow.xml</item>

Observam deja un design fabulos, caci corespondenta dintre “choice-urile” alea si fisierele in care se spune ce contin se face pe baza ordinii. Nu s-a putut pune nodul ala de item sub nodul de choice, sau ceva. Mai departe, daca privim in 01realflow.xml, vedem ca e scris package name ala, ba chiar are si un UUID dupa care ar putea fi identificat:

<pkgref spec="1.12" uuid="A23E47A9-3AB3-4619-847F-2104601981F9">
	<config>
		<identifier>com.nextlimit.realflowPluginForMaya.realflow.pkg</identifier>

Atingerea de geniu este ca muistul tine package name-urile alea acolo doar de decor. De fapt el se asteapta ca in primul fisier sa fie definit intotdeauna pachetul “com.nextlimit.realflowPluginForMaya.realflow.pkg”, in al doilea sa fie ala cu -1 in coada, in al treilea ala cu -2 etc. Nu conteaza ce scrie de fapt in fisier, iar UUID-ul ala nu e folosit la nimic.

Deci ce se intimpla cind dai click dreapta remove? Pai simplu, indianul care a implementat functia de sters nu stie ca numele sint hardcodate. El sterge nodul din XML, sperind ca potriveala se va face dupa nume. Din cauza ca se face dupa ordine, totul aluneca cu o pozitie in jos, deci pachetul de 2009 ajunge in choice-ul de 2008, ala de 2010 in choice-ul de 2009 etc. Primul pachet ramine orfan, iar ultimul choice ramine gol.

Solutia e sa stergi de mina XML-urile corespunzatoare target-urilor de care vrei sa scapi, dupa care sa iei la rind toate XML-urile ramase si sa cirpesti package name-urile alea, ca sa fie consecutive. Apropo, nu eu am dat numele alea care-s toate la fel, sint generate de el pe baza numelor fisierelor din target-uri, iar daca le schimbi se fute.

Uimitor. Fabulos. Nici la scoala ajutatoare nu vezi asemenea “design”. Ala care a facut cacatul asta n-a inteles nimic din programare.

Cum ziceam, epidermoliza buloasa.

Tags: , , , , , , , , , , , , , ,

Da-mi taticule si mie o lopata si-o mistrie

Posted in Slagare internationale, Stand-up philosophy on February 19th, 2011 by Mihnea

Cei ce trec prin viata doar in plimbare
Uita ca exista si penitenciare
Unde nu-i soare si nu creste-o floare
Si doare cind musti din covrig

Nenumarati contabili au flatulat de-a lungul timpului perspective numerologice asupra traiului de programator, de la regula 80/20, care a fost aplicata sistematic si contradictoriu tuturor perechilor posibile de cantitati numarabile (dar nu neaparat masurabile) din aceasta ingrata activitate, pina la transpiratul 10/50/30/10. Eu propun o simplificare a acestor dihotomii, triotomii si cvadruplotomii ciomuistice: programatorii isi petrec majoritatea timpului facind cacaturi care n-au absolut nici o legatura cu lucrul pe care vor sa-l programeze.

Cea mai de cacat situatie in care te poti gasi ca programator este atunci cind trebuie sa cooperezi cu niste cod facut de altcineva, adica intotdeauna. In general tu vrei sa faci o chestie clara, dar pina sa ajungi sa scrii alea 2 linii care te intereseaza pe tine trebuie sa te bati cu sute de muisme care iti stau in drum: compilatoare, IDE-uri, SDK-uri, framework-uri, biblioteci, OS-uri si asa mai departe. In loc sa te preocupi de ce face codul tau, te chinui sa intelegi care din mizeriile de lib-uri pe care le folosesti fragmenteaza memoria intr-un asemenea hal ca nu mai vrea OS-ul sa-ti dea 20 de amariti de megi de memorie, cind tu ai commit size pe la 700 MB.

Mai deunazi, in uzina la noi se barbota cu sirg la un plug-in de Max care trebuia sa coopereze cu un alt plug-in, ce nu era al nostru. Acest alt plug-in avea amabilitatea de a oferi chiar un SDK, adica niste headere cu nume de variabile din maxim 2 litere si fara documentatie, deci nu se intrevedeau incidente. Totul a mers ca uns pina in clipa in care am chemat o metoda numita “Eval” si am luat unresolved external.

Vedeti voi, plug-in-ul nostru nu poate fi linkat cu lib-urile celuilalt plug-in, caci trebuie sa se incarce si daca plug-in-ul alalalt nu exista. Daca punem o dependinta statica catre DLL-ul lor, al nostru nu mai merge decit daca e si al lor instalat. In concluzie, trebuie sa incarcam dinamic un pointer la metoda aia s-o apelam doar daca exista, lucru banal in teorie. Procesoarele, aceste chestii de care in zilele noastre sint mai preocupati gamerii decit programatorii, chiar au o instructiune pentru apelat functii, deci n-ar trebui sa existe articole pe blog-uri despre asta, nu? Ei bine, pina la CPU exista C++, compilator si Max, iar retardarile lor combinate dau o retardare mai mare decit suma partilor.

O metoda e o functie ordinara careia trebuie sa-i pasezi cumva si this-ul pe sub mina. In functie de platforma, dinsul trebuie sa fie pus intr-un registru sau pe stiva. Pe linga instructiunea de apelat functii, CPU-ul, acest lucru magic, are in mod surprinzator si instructiuni pentru pus chestii in registri sau pe stiva. Problema e cum il convingi pe compilator sa le genereze cind tu ai un pointer la functia aia – pe care l-ai luat cu GetProcAddress() – si pointerul la obiect.

Intr-o lume ideala s-ar face ceva de genul:

// In expozitiune:
#ifdef _WIN64
const char* methodName = "?Eval@ClasaMuista@@QEAAMM@Z";
#else
const char* methodName = "?Eval@ClasaMuista@@QAEMM@Z";
#endif
void* method = GetProcAddress(dll, methodName);

// In desfasurarea actiunii:
ClasaMuista* instPtr = ...;
float arg = 0.37f;
float retVal;
#ifdef _WIN64
__asm
{
	mov		rcx, instPtr
	movss	xmm1, arg
	call	method
	movss	retVal, xmm0
}
#else
__asm
{
	mov		ecx, instPtr
	push	arg
	call	method
	fst		retVal
}
#endif

Deoarece nu traim intr-o lume ideala, un mintos de la Microsoft care crede ca procesorul e o legenda urbana s-a decis ca Visual C++ nu va avea inline asm in 64-bit. La momentul la care a fost proclamata, aceasta prostie fenomenala a fost intimpinata cu urlete si mui de catre cei afectati, dar pe Microsoft i-a durut la penis. Motivul oficial este la fel de retardat ca decizia in sine: cica nu poti scrie acelasi asm pentru 32 de biti ca pentru 64. Si ce? D-aia avem ifdef, in mortii lui de ifdef. Nu inteleg ce ii fute pe ei grija ca eu trebuie sa fac gimnastica cu ifdef. Daca trebuie, o fac. Alternativele propuse de mintos sint ori sa te descurci cu intrinsics, ori sa scrii codul intr-un fisier separat si sa-l compilezi cu un assembler. Aia cu intrinsics nu se aplica aici, dar si in cazurile in care se aplica o mai da in bara, caci VC nu e tocmai Tata Lor™ la tradusul intrinsic-urilor in cod (ceea ce ma mira foarte, caci de pe banca de unde stau eu mi se pare destul de usor sa produci cod optim din asa ceva, sau cel putin mult mai usor decit alte optimizari pe care compilatorul lor le face bine). P-aia cu assembler-ul extern nu vreau s-o aplic pentru ca mi se pare insultator sa fiu nevoit sa instalez masm sau alta atrocitate ca sa chem o functie.

Un scurt moment am cochetat cu ideea de a face un const char* cu opcode-urile unei functii care pune argumentele unde trebuie si cheama cacatul de metoda, dupa care sa indrept un function pointer spre el si sa-l chem, da’ cirpeala e cam mare, asa ca am zis sa o luam totusi pe calea ortodoxa: facem un pointer la un member function si-l chemam p-ala. Adica:

// Expozitiune:
typedef float (ClasaMuista::*EvalFunc)(float);
union
{
	void*		voidPtr;
	EvalFunc	funcPtr;
} p;
p.voidPtr = GetProcAddress(dll, methodName);
EvalFunc method = p.funcPtr;

// Desfasurarea actiunii:
ClasaMuista* instPtr = ...;
float retVal = (instPtr->*method)(0.37f);

Observam tigania cu union-ul, care-i necesara pentru ca n-ai voie sa castezi void* la pointer la metoda. C++ e retardat si-ti permite sa derivezi din mai multe clase de-odata, caz in care pointerul ala trebuie sa mai contina niste cacat pentru ajustarea corespunzatoare a this-ului. Desi Java a transat problema asta acum vreo 15 ani (spre surprinderea tuturor, caci e singurul lucru facut bine in Java), C++ inca n-a inteles ca numai cretinii deriveaza din mai multe clase cu date in ele. Ba mai mult, ca sa le dea apa la moara cretinilor, ofera si virtual inheritance, acest goatse al OOP-ului care pretinde ca te scoate din cacat cind diagrama ta de clase seamana cu Coloana Infinitului, dar de fapt sileste compilatorul sa produca cod de o uritenie rar intilnita. In fine, C++ n-o sa primeasca interfete doar pentru ca ma oftic eu aici in fata poporului (la urma urmelor, se chinuie de vreo 12 ani sa-i creasca closures si inca nu e gata), dar in cazul de fata toate aceste griji pot fi date uitarii, caci clasa cu care trebuie sa cooperam nu e derivata din nimic, deci pointerii la metodele ei sint pointeri normali. Deci rulam codul si crapa. Cu WTF-ul pe buze, privim in disassembly si ne minunam:

000000013F191043  movss       xmm1,dword ptr [__real@3ebd70a4 (13F1921B4h)]
000000013F19104B  movsxd      rcx,dword ptr [rsp+28h]
000000013F191050  add         rcx,rbx ; Tu cine pula mea esti?
000000013F191053  call        rax

Ce o fi cu acea adunare de colea, ce seamana izbitor de tare cu o ajustare a this-ului pentru multiple inheritance? In acest moment, ne revine din negura vremurilor amintirea ca VC++ are un flag care-ti permite sa fortezi toti pointerii la metode sa foloseasca formatul extins, cu offseti si cacat. Cautam in MSDN si aflam ca flag-ul se cheama /vmg. Privim in project settings, dar nu-i. Mai privim odata in MSDN si observam ca exista si un pragma care face acelasi lucru, moment in care vedem rosu in fata ochilor.

In acest punct de turnura al naratiunii trebuie sa vorbim un pic despre 3D Studio Max si programatorii care lucreaza la el, caci poate nu-i un subiect familiar tuturor. La Max au lucrat de-a lungul timpului cei mai prosti programatori care au scris vreodata cod. Banuiala mea e ca singura cerinta de angajare la Discreet/Kinetix/Autodesk este sa fi lucrat in prealabil la Yahoo Messenger. Proportiile prostiei celor care au facut Max-ul nu se pot descrie in citeva rinduri, insa pentru a va forma o idee pot mentiona ca absolut toti cei care au pus vreodata mina pe SDK-ul de Max, indiferent de nivelul lor de pricepere, au sfirsit prin a dori moartea violenta a dobitocilor care au gindit si implementat acest program de cacat care nu mai moare odata. As dori sa va pot arata niste cod din matele Max-ului, dar cica n-am voie ca NDA si mui.

Cind am vazut ca exista acel pragma, m-am gindit instantaneu la MAXScript. Acest infect limbaj de scripting este facut de cineva cu stofa de Silviu Ardelean, deci am avut o banuiala ca pe undeva prin mecanismul de expus functii C++ in MAXScript voi gasi muia aia. Am dat cu pragma pointers_to_members in virful headerului precompilat, si prompt VC++ mi-a zis:

c:\program files\autodesk\3ds max 2011\maxsdk\include\maxscrpt\..\maxscript\macros\define_instantiation_functions.h(55) : error C2285: pointers to members representation has already been determined – pragma ignored

Muie Max. N-am rabdare sa vad de ce crede dinsul ca are nevoie de cacatul asta, dar va pot garanta ca e gresit. Sigur au futut ceva prin alta parte si au cirpit-o in felul asta. Din cauza imbecilitatii lor, eu nu pot sa chem un cacat de functie.

Ajungind aici, eu m-am enervat si am plecat acasa, dar jos8cal a mai dat un ochi in pagina aia din MSDN care zicea cum e cu pragma si /vmg si a vazut ca mai exista o cirja cu care se poate abuza limbajul: keyword-ul __single_inheritance. Chiar daca un retard ii spune compilatorului sa faca pointeri de cacat, poti reveni la pointeri ordinari pentru clasele care te intereseaza punind keyword-ul ala in declaratie. Deci l-a pus, si a mers. URA AM REUSIT SA CHEMAM O FUNCTIE!

Sau nu. A doua zi, a trebuit sa repetam manevra intr-un plug-in de Maya. Maya nu-i facut de handicapati, deci nu-i nevoie de pragme si keyword-uri si alte chestii d-astea. Din pacate insa, el ruleaza si pe OS-uri de hipsteri si comunisti, unde rolul Domnului Trandafir este jucat de GCC. GCC nu se osteneste sa faca pointerii la metode sa fie pointeri chiori in cazurile cu imbecilitate redusa; dinsul foloseste pointeri d-aia extinsi tot timpul si n-ai cum sa-l rogi sa faca exceptii. Deci ce cacat facem?

GCC are inline asm si in 32 de biti si in 64:

// Expozitiune:
void* method = dlsym(dll, "_ZN11ClasaMuista4EvalEf");

// Desfasurarea actiunii:
ClasaMuista* instPtr = ...;
float arg = 0.37f;
float retVal;

#ifdef __LP64__
__asm__ __volatile__(
	"call *%%rax"
	: "=Yz"(retVal)
	: "Yz"(arg), "D"(instPtr), "a"(method)
	: "memory"
);
#else
__asm__ __volatile__(
	"push (%2)\n"
	"push %3\n"
	"call *%%eax\n"
	: "=t"(retVal)
	: "a"(method), "r"(&arg), "r"(instPtr)
	: "memory"
);
#endif

Singura manevra aici este sa afli cum se cheama constraint-urile alea pentru template-urile de ASM pentru fiecare clasa de parametru care-ti trebuie. Daca il intrebi pe goagal de “gcc inline assembly constraints”, pagina relevanta (adica asta) nu e printre hit-uri (e o pagina care duce la o pagina care duce la asta, totusi). Sint alte pagini insa, scrise de firtati de-ai lui Silviu care simt nevoia sa se exprime desi habar n-au despre ce vorbesc, si care-ti spun, de exemplu, ca constraint-ul pentru rdi este “di”.

Trebuie notat totusi ca am avut bulan ca functia vrea un singur parametru de tip float. In Linux 64-bit, parametrii de tip float se dau in xmm0, xmm1 etc. Exista un constraint pentru xmm0 (numit intuitiv “Yz”, cum se vede mai sus), dar pentru restul nu mai exista (e doar “x”, care inseamna orice registru SSE, adica il lasi pe nenorocit sa aleaga). Ca sa-ti dai parametrii in cazul ala, ii pui in variabile, dai adresele variabilelor, faci lucru manual cu movss ca sa le incarci in registri si pui registrii respectivi in clobber list. Nu ca ce se intimpla deasupra ar fi optim sau ca ar conta, da’ totusi, obscen.

E de notat ca puteam folosi pointerul la metoda si in GCC, si in VC++ fara __single_inheritance, daca ne bateam capul sa scriem chestii in dinsul astfel incit sa faca call-ul care ne intereseaza (layout-ul e documentat pentru GCC si se gaseste pe net sau cu ochiu’ in dezasamblare pentru VC++). Mie asta mi se pare un hack si mai mare decit un const char* cu opcode-urile care fac ce doresc.

Dragi tovarasi, VICTORIE! Am reusit sa chemam o functie! Programarea e o meserie magica, plina de satisfactii, unde visele devin realitate prin simpla apasare a unor butoane!

PS: toata aceasta voma se putea evita daca aia care au facut celalalt plug-in se prindeau ca atunci cind vrei sa expui clase C++ dintr-un DLL, e o idee buna sa faci o interfata si s-o dai p-aia afara. Daca ClasaMuista::Eval() era virtuala, instanta aia de ClasaMuista pe care o primeam de la ei ar fi avut in ea un pointer la vtable, in care s-ar fi aflat si adresa functiei Eval() si ar fi mers totul ca prin minune. Runtime dispatch, chestii avansate nu gluma. Maxim 0.43% din totalul programatorilor de pe planeta asta sint in stare sa faca un API, dar din pacate un procent mult mai mare incearca.

Tags: , , , , , , , ,