Assembly i umjetnost pregovaranja

Kad je čovjek prvi put rekao stroju 'ADD' umjesto da mu tetoviraš nule i jedinice u glavu.

poglavlje 17/3418 min čitanja∞ – 1969

Zamisli prostoriju veliku kao plesna dvorana, hladnu jer toplina kvari elektroniku, punu ormara koji zuje i trepere kao da međusobno nešto pregovaraju. To je računalo. Jedno. Cijelo. I zove se hram, ne bez razloga — jer u njega ulaziš tiho, s poštovanjem, i nikad ne diraš ništa bez dopuštenja svećenika u bijelom mantilu, koji se službeno zove operater.

Ti nisi svećenik. Ti si netko dolje, u drugoj zgradi, s olovkom u ruci i hrpom papira ispred sebe, i pišeš program — na papiru! — jer je strojno vrijeme skuplje od tvoje godišnje plaće i nitko, baš nitko, ne smije trošiti minute stroja na tvoje eksperimentiranje (strojno vrijeme se tada doslovno naplaćivalo po minuti, kao taksi, samo što je taksi bio veličine plesne dvorane i vozio je samo jednog putnika odjednom). Ispravljaš olovkom, brišeš gumicom, ručno računaš hoće li ti program stati u memoriju koja ima manje mjesta nego jedna današnja slika mačke. Kad završiš, predaješ papire. I onda čekaš.

Čekaš danima. Tvoj red na stroju dolazi kad dolazi — možda sutra, možda u srijedu, možda kad se stroj vrati s popravka. U međuvremenu piješ kavu, igraš šah s kolegom koji čeka isto što i ti, i prevrćeš strojne tablice — knjige debele kao telefonski imenik, pune brojeva u binarnom zapisu (to je onaj sustav brojanja gdje postoje samo dvije znamenke, 0 i 1, jer je struji, u suštini, svejedno je li nešto uključeno ili isključeno, a baš to je jedino što ta hladna zgrada razumije). Ne riječi. Ne slova. Brojeve, i samo brojeve, poredane u obrasce koje moraš pamtiti napamet ako želiš da stroj uopće primijeti da postojiš.

I baš tu, u toj rečenici izgovorenoj napola ozbiljno, napola kao provokacija za sljedeću turu kave, počinje sve ono što ćeš dalje čitati. Ne zato što je netko sjeo i rekao: sada ćemo elegantno i zauvijek riješiti problem komunikacije čovjeka i stroja. Nego zato što je netko, umoran od pamćenja binarnih obrazaca napamet, postavio pitanje koje bi svaki normalan čovjek postavio da sjedi u toj dvorani s tobom: zar stvarno moram do kraja života pamtiti da niz 01011000 znači „zbroji ova dva broja”, kad mogu jednostavno napisati ADD i pustiti nekog drugog da se muči s prijevodom? I dobro je da baš takva, naizgled naivna pitanja postavljaju ljudi koji ne moraju glumiti da već sve razumiju — jer ona, više nego bilo koje predavanje, tjeraju priču dalje.

Dobro, kaže laik, gledajući shemu procesora kao da gleda unutrašnjost sata — hrpa kukica, crtica i simbola koji nešto znače nekome, samo ne njemu. Imamo stroj koji zbraja brojeve. Zašto mu jednostavno ne mogu napisati „zbroji ova dva broja i spremi rezultat”? Ja bih to napisao na papiru u pet sekundi — zašto stroj to ne može pročitati?

Odgovor, koliko god zvučao kao izlika, jest — jer stroj ne zna čitati. Ne zna hrvatski, ne zna engleski, ne zna niti jednu riječ koju je ikad izgovorio čovjek. Stroj poznaje točno dvije stvari: struja ide ili struja ne ide. Uključeno ili isključeno. Jedan ili nula. To mu je materinji jezik, jedini koji je ikad progovorio, i drugog nema. Kad kažeš „zbroji”, za stroj je to gomila slova bez značenja — otprilike kao da nekome tko ne zna čitati daš papir prekriven mrljama od tinte i tražiš da izvrši naredbu.

Laik kima, ali onda postavlja pitanje koje zvuči glupo, a nije: dobro, ali netko je sigurno morao stroju reći što znače te nule i jedinice? Netko je stroju dao rječnik?

Upravo to. I taj rječnik zove se opcode — operacijski kod. Zvuči strašno, ali nije ništa drugo nego telefonski imenik s dvije kolone. U jednoj koloni piše obrazac od nula i jedinica, recimo 10110000. U drugoj koloni piše što taj obrazac znači za procesor — u ovom slučaju, sasvim konkretno, „stavi ovaj broj u ovaj registar”. Registar, da razjasnimo odmah, nije ništa mistično — to je mali pretinac unutar samog procesora, nešto kao džep na tvojoj jakni, samo što stane točno jedan broj, a procesor ga koristi da s njim odmah nešto izračuna, a da ne mora trčati do memorije i natrag. Kad procesor pročita 10110000, ne „razmišlja” o tome, ne interpretira, ne pogađa. On samo gleda u svoj ugrađeni imenik i vidi — aha, ovo znači to i to, izvrši. Cijela sofisticiranost procesora, sav taj mit o silicijskom mozgu koji misli — u osnovi je vrlo glupa sprava koja gleda tablicu i slijedi je bez pogovora. Poslušnija od najposlušnijeg vojnika.

Dobro, kaže laik, sad malo nervozniji — ali kako ja, čovjek, znam da 10110000 znači „stavi broj u registar”? Ja to ne pamtim. Nitko normalan to ne pamti.

I upravo u tom trenutku, negdje ranih pedesetih, netko je postavio isto pitanje i naljutio se dovoljno da nešto poduzme. Jer da, u samim počecima programer je doslovno morao pamtiti ili prepisivati iz priručnika binarne obrasce, ručno, za svaku pojedinu naredbu, u programu koji je često imao i tisuće takvih naredbi. Zamisli da pišeš pismo, ali svaku riječ prvo moraš potražiti u debelom rječniku i prepisati je kao niz brojeva. Za rečenicu „volim te” bi ti tako trebalo pola dana.

To je bio trenutak kad je taj posao prestao biti izdrživ za običan ljudski mozak. Problem nikad nije bio u tome što je stroj glup — stroj je uvijek bio glup, to mu je posao, za to je i napravljen. Problem je bio što je čovjek morao razmišljati na jeziku stroja, umjesto da stroj barem malo pokuša razmišljati na jeziku čovjeka. I baš tu, iz čistog očaja i trnaca u prstima od prepisivanja tablica, rodila se ideja koja će ovo poglavlje odvesti dalje — dati toj gomili nula i jedinica ime koje čovjek napokon može pamtiti.

Mnemonika: prvi ustupak čovjeku
§ 02

Mnemonika: prvi ustupak čovjeku

Dobro, kaže laik, shvatio sam da stroj razumije samo nule i jedinice i da je, recimo, 10110 neka naredba — ono što smo upravo upoznali kao opcode, broj koji stroju kaže „sad radi baš ovo, ni više ni manje”. Ali zašto je onda, pita, ikad ikome palo na pamet pisati 10110111000101 na papir i to zvati poslom? Zar to nije kao da diktiraš pismo brojem otkucaja telegrafa, umjesto da normalno izgovoriš riječi?

Točno je tako. I upravo je to razlog zašto su početkom pedesetih programeri — tada se to zvalo »koderi«, kao da rade s tajnim šiframa, što su, u biti, i radili — počeli tražiti prečicu. Ne zato što su bili lijeni, nego zato što je papir puno strpljiviji od čovjeka, a čovjek koji šest sati zaredom prepisuje nizove nula i jedinica prije ili kasnije zamijeni jednu jedinicu nulom, i onda cijeli stroj radi nešto sasvim drugo od onoga što je trebao. Ne loše. Drugo. A to je gore.

Pa što su onda smislili?

Smislili su nešto uvredljivo jednostavno, kao i svaka dobra ideja u ovoj knjizi do sada. Umjesto da pišeš 10110, pišeš ADD. Umjesto 01001, pišeš SUB. Umjesto niza nula i jedinica koji ti ništa ne govori, pišeš kraticu koju tvoj mozak — onaj isti mozak koji je milijun godina evoluirao da pamti riječi, a ne binarne uzorke — može zapamtiti bez muke. To se zove mnemonik; riječ dolazi od grčke Mnemosine, boginje pamćenja, jer to i jest — pamtilica, podsjetnik. ADD te podsjeća da zbrajaš, isto kao što te kvačica na kutiji od jaja podsjeća da su svježa, samo malo tehničkija verzija.

Ali čekaj, kaže laik, stroj još uvijek ne zna što je ADD — on i dalje jede samo nule i jedinice.

Ne zna, i tu stiže drugi dio trika, onaj koji je zapravo pametniji od prvog. Netko je napisao program — jedan jedini program, jednom — koji čita ADD i sam ga pretvara u 10110. Taj program zove se asembler, a jezik mnemonika kojim mu se obraćaš, onih ADD i SUB naredbi, zove se assembly — isto kao što se, recimo, alat za rezanje kruha zove „sjeckalica”, a sam kruh koji sjecka nije sjeckalica, nego kruh. Asembler u svojoj srži nije ništa čarobnije od francusko-hrvatskog rječnika koji netko okreće stranicu po stranicu. Ti kažeš ADD, asembler pogleda u svoju tablicu, vidi da ADD odgovara 10110, i to upiše u strojni kod umjesto tebe. Ti pišeš čovjeku razumljivo, on to prevede u ono što stroj razumije. Ta binarna brojka kojoj mnemonik odgovara — da se podsjetimo, onaj isti opcode s početka poglavlja — u domaćem žargonu zove se opkod, i to je jedini jezik koji procesor stvarno govori, sve ostalo je prijevod za nas smrtnike. Cijela ta stvar, taj prvi asembler, u srcu je samo tablica zamjena: lijeva kolona mnemonici, desna kolona opkodovi, i program koji redak po redak gleda lijevo, upisuje desno.

I da, taj prvi prevoditelj morao je i sam biti napisan u sirovom strojnom kodu, ručno, redak po redak, bez pomoći — netko je morao sagraditi prvu ljestvu bez ljestve. Ali kad je jednom stajala, svi poslije njega mogli su se njome samo penjati. To je otprilike cijela povijest softvera u jednoj rečenici, ali strpi se, imamo još šesnaest poglavlja da to dokažemo.

Kako izgleda misliti u registrima
§ 03

Kako izgleda misliti u registrima

„Dobro”, kaže laik, „kužim mnemoniku, ADD zvuči ljepše od 10110. Ali što se stvarno događa kad stroj to izvrši? Gdje se ti brojevi drže dok ih zbrajaš?”

Odlično pitanje, i tu ulazimo u srce cijele priče. Da se podsjetimo, registri su te pregrade o kojima smo već govorili — ali zamisli ih sad ovako: nemaš papir, nemaš bilježnicu, nemaš ni jedan slobodan kutak na stolu — imaš samo šaku pregrada, recimo osam njih, svaka veličine kutije šibica, i to je sve što ti stoji na raspolaganju za cijeli izračun. Memorija je ladica u drugoj sobi, do koje moraš hodati i vraćati se, a to hodanje traje. Registri su ono što ti je fizički pod prstima, u samom procesoru, i baš ta brzina rada s njima razlog je zašto se cijela filozofija programiranja u početku vrtjela oko jednog pitanja: što je trenutno u registru, i kako ga se riješiti prije nego što mi zatreba drugi broj, a nemam ga gdje staviti.

— OK, dosta filozofije — kaže laik — daj mi konkretan primjer. Zbroji mi dva broja.

Idemo. Recimo da želiš zbrojiti 5 i 3. U assemblyju to ne izgleda kao 5 + 3 = 8, jer stroj ne zna što je »plus« u onom smislu u kojem ga učiš u drugom razredu osnovne škole. Izgleda ovako, red po red, gdje je svaki red jedna zapovijed koju stroj izvrši i zatim čeka sljedeću:

LOAD R1, 5 — uzmi broj 5 i stavi ga u registar broj jedan. LOAD R2, 3 — uzmi broj 3 i stavi ga u registar broj dva. ADD R3, R1, R2 — zbroji što je u registru jedan i registru dva, rezultat stavi u registar tri. STORE R3, 100 — uzmi što je u registru tri i odnesi to u ladicu na adresi 100, jer ćeš registar trebati za nešto drugo.

To je sve. Nema skrivenog koraka, nema sitnih čarolija u pozadini. Stroj ne „zna” da zbraja ništa više nego što lopata „zna” da kopa — samo izvršava, red po red, jedno po jedno, i ne pamti ništa što mu nisi eksplicitno rekao da zapamti. Ako sljedeći red slučajno prepišeš preko pregrade jedan, broj 5 je zauvijek nestao. Nema undo, nema „ups, čekaj malo”. To je razlika između pisanja u bilježnicu i pisanja u pijesak dok se plima penje.

— Čekaj — kaže laik — a zašto ne može samo direktno zbrojiti 5 i 3, bez tog prebacivanja u pregrade? Čemu taj cirkus?

Zato što aritmetička jedinica u procesoru — ona sitna komponentica koja stvarno zna zbrajati, na razini strujnih kola — razgovara isključivo s registrima, tim istim pregradama o kojima smo govorili. Ona ne zna sama posegnuti u memoriju i pročitati broj odande gdje bi tebi bilo zgodno da ga pročita. Ima strogo određena „vrata” na koja pristaje strogo određen broj utikača, i tvoj je posao, kao programera, da sve dovučeš do tih vrata, u pravom redu, prije nego zatražiš zbrajanje. To je ono „pregovaranje” iz naslova ovog poglavlja — ne pregovaraš sa strojem o tome što želiš, on to ne pregovara, nego pregovaraš sam sa sobom o tome kako organizirati te tri-četiri pregrade tako da sve stane, a da ništa ne izgubiš usput.

Sad, bonus sloj — ne moraš ovo znati da bi shvatio poentu, ali ne škodi. Broj registara u pravim strojevima te ere bio je bolno mali — nekoliko komada, ne osam kao u mom primjeru, često samo jedan ili dva opće namjene, plus akumulator, poseban registar koji je radio većinu posla: gotovo svaka aritmetička operacija svoj rezultat ostavljala je baš tu, kao neka vrsta glavne pregrade kroz koju je prolazilo sve važno. Zato je pisanje asemblera bilo pola matematika, a pola Tetris: stalno računaš koja vrijednost ti više ne treba, koju možeš prepisati, koju moraš prije toga spremiti u memoriju da je ne izgubiš. Cijela grana programerske vještine — »alokacija registara« — postoji i danas, samo je danas posao kompajlera. Kompajler je, ako to još nismo spomenuli, program koji uzme kod napisan u nekom višem jeziku — recimo Pythonu ili C-u, onom čitljivom, ljudskom zapisu — i sam ga prevede u strojni kod, u one gole nule i jedinice koje procesor zapravo razumije. Taj kompajler tu igru Tetrisa igra automatski, milijunima poteza u sekundi, dok ti pišeš x = a + b i ne pomišljaš na pregrade.

— Dakle — kaže laik polako — kad ja danas u Pythonu napišem zbroj = a + b, negdje duboko ispod se ipak događa cijela ta priča s pregradama?

Točno to. Svaki jezik koji koristiš, koliko god visok i uljuđen izgledao, na kraju lanca prevođenja mora nekome — kompajleru, interpreteru ili virtualnoj mašini — prepustiti isti posao: strpati brojeve u registre, zatražiti zbrajanje, izvući rezultat, spremiti ga negdje. Interpreter je, ako je kompajler onaj koji ti cijelu knjigu prevede unaprijed pa ti preda gotov prijevod, prevoditelj koji stoji pored tebe i prevodi rečenicu po rečenicu, u hodu, dok čitaš — sporiji, ali zato praktičniji za neke stvari. Ništa od toga nije zastarjelo, ništa nije zamijenjeno — samo je pokriveno sve debljim slojevima jastuka da ti to ne moraš gledati. Assembly nije muzej. Assembly je podrum. I kad nešto u kući škripi, tehnički razlog je skoro uvijek tamo dolje.

Prve biblioteke i makroi
§ 04

Prve biblioteke i makroi

— Dobro, sad kužim mnemonike, kužim registre. Ali reci mi jedno — je li ikad ikome dosadilo pisati isti komad koda po deseti put?

Jest. I to je pitanje koje je, koliko god banalno zvučalo, pokrenulo jednu od najvažnijih ideja u povijesti softvera. Odgovor se zove David Wheeler, Cambridge, rane pedesete, stroj EDSAC — jedno od prvih pravih računala na svijetu, sagrađeno na Cambridgeu početkom pedesetih, veliko koliko soba, glasno kao tvornica, ali po prvi put stvarno programirljivo. Wheeler je jedan od onih ljudi za koje nikad nisi čuo, a bez njega bi programiranje danas izgledalo kao da svaki put kad želiš otvoriti vrata prvo moraš sam istopiti željezo za kvaku.

— Čekaj, što je konkretno smislio?

Potprogram. Ili, ako želiš zvučati kao da znaš o čemu govoriš na sljedećem rođendanu: »subroutine«. Ideja je bila otprilike ovakva — imaš komad koda koji, recimo, računa kvadratni korijen. Trebaš ga na pet mjesta u programu. Do tad si taj kod prepisivao pet puta, riječ po riječ, opcode po opcode — onaj isti sirovi numerički kod koji smo upoznali na početku poglavlja — kao monah koji ručno prepisuje istu molitvu na pet stranica. Wheeler je smislio nešto puno pametnije: napiši ga jednom, stavi ga na jedno mjesto u memoriji, i onda samo — skoči tamo, obavi posao, i vrati se točno na mjesto odakle si otišao.

— To zvuči... logično? Kao, zar to nije očito?

Danas ti je to očito, jer ti je usađeno u mozak prije nego što si napisao prvi „Hello World”. Ali 1951. je to bio problem sam po sebi — kako se stroj strojno vrati točno na tu jednu instrukciju gdje si stao, a ne pola metra dalje? Trebalo je smisliti mehanizam koji pamti adresu povratka. Zove se, ako baš hoćeš znati, Wheeler Jump. I to nije samo trik — to je temelj svega što danas zoveš funkcijom, metodom, procedurom. Svaki put kad u kodu pozoveš nešto i ono se, poslušno, vrati nazad kad završi — zahvali se čovjeku koji je to riješio dok su računala bila veličine kuhinje.

— OK, ali to je i dalje samo — nemoj pisati dvaput isti kod?

Upravo to. I to je instinkt star sedamdesetak godina koji nikad nije umro, samo je mijenjao odjeću. Danas ga zoveš DRY — »Don't Repeat Yourself«, odnosno »ne ponavljaj se«, ispišeš to na majici i osjećaš se pametno na konferenciji. Godine 1951. se to zvalo „zašto bih trošio vrpcu i vrijeme stroja da pet puta ukucavam isto”. Ista poanta, drugo stoljeće mode.

— Dobro, dobro. A onda su ljudi počeli i te potprograme razmjenjivati među sobom?

Točno to. I tu dolazimo do druge, možda još veće ideje — biblioteke. Kad već imaš potprogram za kvadratni korijen koji radi, zašto bi ga ti, na svom stroju, u svom timu, pisao iznova? Wheeler i njegovi kolege na EDSAC-u počeli su skupljati te gotove komade koda na papirnim vrpcama (dugim trakama papira s izbušenim rupicama, koje su tada bile ono što je danas USB stick — samo puno sporije i puno lakše za zagužvati), u fizičkim ladicama (da, ladicama, s naljepnicama), i posuđivati ih jedni drugima. To je bila prva softverska biblioteka u povijesti, i bila je doslovno biblioteka, s karticama i posudbom, kao knjižnica u tvom gradu, samo umjesto romana posuđivao si funkciju za sinus.

— I to je... bilo normalno? Da uzmeš tuđi kod?

Jest, i to je ta rečenica koja je tiho promijenila cijelu struku: tuđi kod smijem koristiti. Zvuči trivijalno, ali prije toga to nije bilo očito — svaki je stroj bio svoj mali svijet, svaki programer svoj mali kult, i ideja da tvoj rad nije samo tvoj, nego dio zajedničke zalihe koju drugi mogu uzeti, popraviti, vratiti bolju — to je sjeme iz kojeg je izrastao baš sav open source koji danas u projekt navučeš jednom naredbom u terminalu (»npm install«, ako te ovo ništa ne govori — zamisli da umjesto da sam sebi pečeš kruh, samo odeš u pekaru i kupiš ga, samo što je ovaj kruh besplatan i svi ga dijele). Wheeler nije znao da postavlja temelj GitHubu. Samo je htio da ne mora još jednom pisati isti prokleti kvadratni korijen.

Tko danas piše assembly i zašto
§ 05

Tko danas piše assembly i zašto

— Dobro, ali ozbiljno, tko još danas piše ovo? Mislim, imamo Python, imamo AI koji ti napiše cijelu funkciju dok trepneš. Tko sjedi i broji registre kao neki mučenik iz pedesetih?

Postoji nekoliko sekti, da ih tako zovem, i svaka ima svoj razlog za postojanje. Prva su optimizatori — ljudi koji pišu firmware (softver ugrađen direktno u uređaj) za mikrokontrolere, sitne i jeftine čipove koji se ugrađuju u razne uređaje i imaju smiješno malo memorije za rad, gdje na raspolaganju imaš 2 kilobajta prostora i ni bajta više, ili pišu grafičke drivere gdje jedna loša instrukcija znači da igra cima na ekranu umjesto da klizi glatko. Kompajler je danas nevjerojatno dobar, bolji od 95 % programera u pisanju efikasnog koda — ali za onih 5 % slučajeva, gdje brojiš cikluse procesora kao da su zlato, čovjek još mora zaviriti pod haubu i ručno posložiti instrukcije. To je vrhunski, dosadan, precizan posao. Nitko ga ne radi za zabavu. Rade ga zato što nitko drugi ne može.

— A druga sekta?

Emulatori. Ako želiš da tvoj laptop glumi da je Nintendo iz osamdesetih, netko mora znati točno što je taj stari procesor radio, instrukciju po instrukciju — jer je stara igra pisana direktno za taj čip i ne zna ništa o apstrakcijama koje ti danas koristiš. Emulacija je u biti prevođenje jednog jezika stroja u drugi, uživo, u letu, kao simultani prevoditelj koji mora znati i mrtvi jezik i onaj kojim se danas govori. Bez ljudi koji razumiju assembly tih starudija cijela retro-gaming scena, cijela nostalgija za pikselima, ne postoji. Netko mora čitati mrtav jezik da bi mrtvi strojevi opet živjeli.

Treća sekta je možda najčudnija — demoscena. Ljudi koji za zabavu, bez ikakve koristi, ikakvog projekta i ikakve plaće pišu programe koji na najstarijem mogućem hardveru proizvode nevjerojatne vizualne i zvučne efekte. Natjecanje je tko će u 64 kilobajta — a ovaj tekst koji čitaš zauzima daleko manje od toga — složiti trodimenzionalnu animaciju s glazbom. Ovo je čisti sport, čista umjetnost, i piše se skoro isključivo u assembleru — dakle u samom jeziku mnemonike, ne u programu koji ga prevodi u nule i jedinice — jer nijedan viši jezik ti ne daje tu razinu kontrole nad svakim jebenim bajtom.

I četvrta, najmračnija sekta — sigurnosni istraživači. Kad hakiraš neki program, kad tražiš rupu koju možeš iskoristiti, ne gledaš izvorni kod jer ga nemaš — dobiješ samo binarnu datoteku, onu gomilu nula i jedinica koju je kompajler ostavio za sobom, bez imena varijabli, bez komentara, bez ičega ljudskog. Da bi je razumio, moraš je pretvoriti nazad u assembly — taj postupak se zove reverzno inženjerstvo — i cijela industrija digitalne forenzike i etičkog hakiranja stoji na ljudima koji tečno čitaju mnemoniku kao što ti čitaš ovu rečenicu.

— Dobro, shvatio sam. Ali zašto mi ti pričaš o ovome? Ja neću pisati assembly. Nikad. Vjerojatno.

Ne treba. Ali zapamti ovu jednu stvar, jer će te pratiti kroz cijelu ovu enciklopediju: svaki jezik koji ćeš učiti, svaki framework, svaka moderna čarolija — sve to na kraju siđe dolje, do ovih instrukcija koje smo danas upoznali. Apstrakcije koje graditelji jezika slažu jedna na drugu nisu betonski zid. Cure. Pukotine se otvore baš kad ih najmanje očekuješ — kod curenja memorije, kod čudne greške koju kompajler ne može objasniti, kod hakerskog napada koji iskorištava ono što se krije ispod tvog urednog koda. I onda, htio ili ne, netko mora sići stepenicama do podruma i pogledati što se stroju stvarno događa. Assembly je taj podrum. Nikad ga nisi posjetio, ali cijela kuća stoji na njemu.