Tehnički članak

Free Pascal Win32: dekoracija C simbola u HotPDF-u

Free Pascal na Win32 automatski dodaje vodeću donju crtu svakom cdecl; external importu, dok public name izvozi string koji ste napisali, znak po znak. HotPDF mora da zadovolji obe konvencije u istom stablu izvornog koda, jer Delphi build već isporučuje import deklaracije u kojima je donja crta upisana rukom. Ako ovu asimetriju pogrešite, dobijate link greške koje navode simbol koji niko nije napisao

Proširenje Delphi biblioteke na Free Pascal obično se opisuje kao problem portabilnosti, i na Win64 to uglavnom i jeste to. Win32 je druga priča. 32-bitni x86 Windows ABI nosi trideset godina nagomilanih konvencija o tome kako se C simboli pišu, ko čisti stek i koje kompajlerske privatne helper-e sme prevodna jedinica da pretpostavi, i svaka od tih tačaka je mesto gde se dva Pascal kompajlera koja se slažu o jeziku i dalje mogu razilaziti oko objektne datoteke

Zašto se isti simbol razrešava na Win64 a pada na Win32?

Zato što je prefiks donje crte 32-bitna konvencija koju Free Pascal primenjuje na importove, ali ne i na izvoze. Deklarišete function deflate(...): Integer; cdecl; external; i FPC na Win32 traži _deflate u objektnoj datoteci, a na Win64 traži deflate. To je ispravno ponašanje i poklapa se sa onim što C kompajler emituje. Zamka je na drugoj strani mosta: rutina označena sa public name 'deflate' izvozi tačno deflate na obe mete, bez dodalog prefiksa

Dodajte sada istorijski detalj koji to čini konkretnim. Delphi build već deklariše neke od tih ulaznih tačaka sa donjom crtom upisanom u ime, jer to je ono što njegove sopstvene objektne datoteke sadrže. Dajte istu deklaraciju FPC-u na Win32 i kompajler joj savesno doda prefiks iznova, pa linker traži __deflate, simbol koji ništa ne izvozi. Intuitivna popravka, dodavanje jedne donje crte svuda, lomi importove koji su već bili ispravno napisani

Radi par prefiks konstanti, a ne jedna. HPDFFPCZLib i HPDFFPCCodecStubs koriste jedan prefiks za obične C importove i drugi za importove koji već nose Delphi stranu prefiksa, a na Win64 su obe konstante prazne pa postojeća link imena preživevaju netaknuta. Dve konstante umesto jedne je cela popravka, i ona je očigledna tek kad ste odvojili pravilo importa od pravila izvoza

Iste C simbol deklaracije razrešene od strane Free Pascal-a i Delphi-ja na Win64 i Win32: cdecl importovi dobijaju donju crtu samo na 32-bitnoj meti, Delphi deklaracija već napisana sa donjom crtom postaje __deflate i ne može se povezati, dok public name izvozi ostaju doslovni na obe arhitekture
Jedna prefiks konstanta ne može da služi oba pravila: obični cdecl importovi i importovi koji već nose Delphi donju crtu dekoriraju se različito pod FPC-om na Win32, pa HotPDF drži dva i ostavlja oba prazna na Win64
// Dva prefiksa, ne jedan: obični C importovi i importovi koji već nose
// ručno napisan Delphi prefiks dekoriraju se različito pod FPC/Win32
const
{$IF DEFINED(FPC) and DEFINED(CPU32)}
  CPrefix     = '_';   // FPC to sam dodaje za cdecl external
  DelphiCName = '';    // donja crta već upisana u izvornom kodu
{$ELSE}
  CPrefix     = '';
  DelphiCName = '';
{$IFEND}

// Izvozna strana: 'public name' je doslovan na svakoj meti
procedure hpdf_codec_free(P: Pointer); cdecl;
  public name 'hpdf_codec_free';

WIN32 vam kaže arhitekturu, a ne ABI

Ovo je greška u uslovnom kompajliranju sa najdužim debug repom, i vredi je reći ravno: WIN32 i WIN64 opisuju ciljnu arhitekturu i ništa ne kažu o tome koji kompajlerski privatni runtime helper-i postoje. Free Pascal definiše oba simbola na odgovarajućim Windows metama, baš kao i Delphi. Guard napisan kao {$IFDEF WIN32} oko koda koji zove Delphi runtime helper zato se kompajlira pod FPC-om i pada u trenutku povezivanja

Konkretno, tri familije koda upadaju u ovu zamku. Delphi 64-bitne celobrojne trampoline do kojih se stiže kroz System.@_ll helper-e, MSVC Win32 asemblerske podrške rutine, i import slotovi koji idu uz njih — sve postoji da bi služilo prekompajliranim C objektima koje Delphi build povezuje. Free Pascal ne povezuje te objekte, pa mu ništa od te mehanike ne treba, i svaka referenca na nju mora da nestane. Suptilnost je što i deklaracija i implementacija moraju biti izbačene zajedno. Izbacite samo jednu i kompajler izbacuje nekorišnu poruku o identifikatoru koji ne ume da upari ni sa čim

Pravilo koje odatle ispadne kratko je: uslovljavajte kompajlerom kad je pitanje ABI ili runtime podrška, arhitekturom kad je pitanje širina pointera ili broj registara, i nikada ne dozvolite da jedno zamenuje drugo

Uslovljavanje deklaracija i implementacija zajedno

U uslovni blok u interface sekciji lako je upasti a da to ne primetite, i dobijena poruka o grešci pokazuje bilo gde osim na uzrok. Dodajete deklaraciju metode na interface klase i prirodno mesto za nju je pored srodnih metoda, što je sasvim u redu sve do trenutka kad ti susedi slučajno sede unutar postojećeg {$IFDEF} bloka. Uslovne direktive se ne uvlače, pa je blok koji je otvoren četrdeset linija iznad suštinski nevidljiv dok čitate okolne deklaracije

Ono što sledi jeste kompajliranje koje uspeva na jednoj alatki i proizvodi lavinu na drugoj. Ako je okolni guard Delphi provera verzije koju Free Pascal ne zadovoljava, deklaracija nestaje za FPC dok bezuslovna implementacija ostaje, i kompajler prijavljuje dug spisak prigovora o identifikatorima metoda koje je očekivao a našao nije. Nijedna poruka ne pominje uslovni blok koji je to izazvao

Dve navike sprečavaju celu tu klasu otkaza. Pre umetanja u interface sekciju, pogledajte naviše za najbližim otvorenim uslovom umesto da verujete vizuelnom grupisanju. I zeleni Delphi test suite tretirajte samo kao dokaz o Delphi-ju: Free Pascal build biblioteke je zasebna kapija, i jedini način da znate da prolazi jeste da pokrenete build-Win32-Lib-FPC.cmd i build-Win64-Lib-FPC.cmd kao deo iste izmene

Šta puca u 32-bitnom aritmetičkom kodu

Jedno jezičko ograničenje pojavljuje se upravo u kodu najmanje sklonom promenama: 32-bitni Free Pascal neće prihvatiti UInt64 kao upravljačku promenljivu for petlje. U unit-ima eliptičkih krivih koji nose X25519 i X448, petlje koje prelaze preko limb nizova pisane su sa 64-bitnim brojačima samo zato što je sve ostalo u datoteci 64-bitno

Popravka mora biti hirurška, jer je u field aritmetici širina promenljive deo argumenta o ispravnosti. Petljani indeksi postaju Integer, pošto limb niz ima šaku elemenata i nijedan indeks nikada ne priđe 32-bitnom opsegu. Sve što učestvuje u aritmetici — sami limb-ovi, propagacija prenosa i maske — ostaje UInt64, jer sužavanje bilo kog od njih tiho menja rezultat po modulu prostog broja polja

// 32-bit FPC odbacuje UInt64 petljanu promenljivu. Suzi samo indeks;
// limb-ovi, maske i prenosi čuvaju širinu ili se field matematika menja
var
  I: Integer;                 // bilo je UInt64
  Carry, Mask: UInt64;
begin
  Carry := 0;
  for I := 0 to High(Limbs) do
  begin
    Limbs[I] := Limbs[I] + Carry;
    Carry := Limbs[I] shr 51;
    Limbs[I] := Limbs[I] and Mask;
  end;
end;

Verifikacija za ovakvu izmenu ne može biti round-trip test. Enkripcija i dekripcija sa istom pokvarenom implementacijom savršeno se slažu sama sa sobom, i zato su known-answer vektori ovde nepregovarljivi: pokrenite objavljene X25519 i X448 test vektore i uporedite tačne izlazne bajtove. To je jedina provera koja razdvaja ispravnu implementaciju od samokonzistentno pogrešne, i podjednako važi za simetrične primitivke o kojima je bilo reči u članku o Free Pascal deflate i AES codec granicama

Dve tačke loma HotPDF Win32 Free Pascal build-a: {$IFDEF WIN32} guard oko Delphi runtime helper-a koji se kompajlira ali pada pri povezivanju osim ako se deklaracija i implementacija izbace zajedno, i UInt64 petljana promenljiva u X25519 i X448 limb prelazima sužena na Integer dok limb-ovi, prenosi i maske čuvaju svoju širinu
Guard-ujte kompajlerom kad je pitanje ABI ili runtime podrška, arhitekturom kad je širina pointera, a aritmetičke izmene dokazujte protiv objavljenih known-answer vektora umesto round-trip testova

Koliko vredi Win32 Free Pascal build

Praktična dobit je to što Lazarus aplikacija ciljana na 32-bitni Windows dobija isti engine za dokumente kao njen Delphi pandan, bez odvojenog binarnog ugovora koji treba održavati. To je najvažnije za deployment-e o kojima se retko govori: industrijski kontroleri, POS terminali i dugovečni poslovni softver gde 32-bitni runtime nije legacy izbor nego hardversko ograničenje

Win64 priča je došla prva i opisana je u članku o Free Pascal i Lazarus podršci na Win64. Win32 nije njeno ponavljanje. Win64 ima jednu calling convention, bez name decoration-a i bez Delphi privatnih celobrojnih helper-a za koje bi trebale zaobilazne rutine, pa je skoro sve u ovom članku specifično za 32-bitnu metu. Aritmetički unit-i koji su trebali izmenu petljane promenljive isti su oni opisani u Montgomery aritmetici nad NIST krivama, gde je disciplina širine objašnjena dublje

Opšta lekcija je da cross-kompajlerski rad na portabilnosti nije pre svega o jezičkim mogućnostima. Oba kompajlera ovde prihvataju isti Object Pascal. Razlikuje se objektna datoteka: kako se simboli pišu, koje helper rutine se pretpostavlja da runtime pruža, i koji se prekompajlirani objekti nalaze u linku. HotPDF isporučuje Free Pascal i Lazarus pakete uz Delphi i C++Builder pakete u HotPDF Delphi PDF komponenti, pa isto stablo izvora hrani svaku alatku umesto da se račva po kompajleru