Кіріспе

Компьютерлік бағдарлама, бір бағдарламалау тілінен екінші бағдарламалау тіліне кодты аударады. Компьютерлік есепте компилятор – бір бағдарламалау тілінде (көз тілі) жазылған компьютерлік кодты екінші тілге (мақсат тіліне) аударатын компьютерлік бағдарлама. "Компилятор" атауы көбінесе жоғары деңгейдегі бағдарламалау тілінен төмен деңгейдегі бағдарламалау тіліне (мысалы, ассемблер, нысандық код немесе машиналық код) көз кодын аударатын бағдарламаларға қолданылады, осы арқылы орындалатын бағдарлама жасалады. Әртүрлі пайдалы форматтарда шығыс беретін көптеген түрлі компиляторлар бар. Кросс-компилятор өзі іске қосылатын процессор немесе операциялық жүйеден өзге процессор немесе операциялық жүйе үшін код жасайды. Бутстрап компиляторы көбінесе уақытша компилятор болып табылады, ол тіл үшін тұрақтырақ немесе жақсырақ оңтайландырылған компиляторды компиляциялау үшін қолданылады. Байланысты бағдарламалық құралдарға декомпиляторлар (төмен деңгейлі тілдерден жоғары деңгейлі тілдерге аударатын бағдарламалар), көзден көзге компиляторлар немесе транспайлерлер (жоғары деңгейлі тілдер арасында аударатын бағдарламалар), тілді қайта жазушылар (әдетте, тілді өзгертпей өрнектердің пішінін аударатын бағдарламалар) және компиляторларды (немесе олардың бөліктерін) жасайтын компиляторлар кіреді, көбінесе оларды әртүрлі компиляторларды жасауға мүмкіндік беретін жалпы және қайта пайдалануға болатын тәсілмен. Компилятор келесі операциялардың бірін немесе барлығын орындайды, олар фазалар деп аталады: алдын ала өңдеу, лексикалық талдау, синтаксистік талдау, семантикалық талдау (синтаксис басқарылатын аударма), кіріс бағдарламаларын аралық өрнекке түрлендіру, кодты оңтайландыру және машинаға арналған кодты жасау. Компиляторлар әдетте осы фазаларды модульдік компоненттер ретінде жүзеге асырады, бұл тиімді жобалауға және көз кірісінен мақсатты шығысқа түрлендірудің дұрыстығына көмектеседі. Компилятордың дұрыс емес мінез-құлқынан туындаған бағдарламалық қателерді іздеу және оларды жою қиын болуы мүмкін; сондықтан компиляторды жасаушылар компилятордың дұрыстығын қамтамасыз ету үшін көп күш жұмсайды. Компиляторлар ғана емес, бастапқы бағдарламаларды түрлендіру үшін қолданылатын тілдік процессорлар бар. Интерпретатор – көрсетілген операцияларды түрлендіріп, орындайтын компьютерлік бағдарламалық құрал. Бэкюс-Наур формасы (BNF) тілдің "сөйлемдерінің" синтаксисін сипаттайды. Оны Джон Бэкюс әзірледі және Algol 60 синтаксисі үшін қолданды. Бұл идеялар тіл білімі маманы Ноам Чомскийдің контекстсіз грамматика тұжырымдамаларынан туындаған. "BNF және оның кеңейтімдері бағдарламалау нотацияларының синтаксисін сипаттау үшін стандартты құралға айналды. Көп жағдайда компиляторлардың бөліктері BNF сипаттамасынан автоматты түрде жасалады". 1942-1945 жылдар аралығында Конрад Цузе компьютерлер үшін алғашқы (алгоритмдік) бағдарламалау тілін жасады, ол Plankalkül ("Жоспарлау есептеуі") деп аталды. Цузе сондай-ақ бағдарламаның математикалық формуласын машинамен оқылатын перфорациялық пленкаға автоматты түрде аударуға арналған Planfertigungsgerät ("Жоспар құрастыру құрылғысы") туралы ойлады. APL – математикалық есептеулерге арналған тіл. 1949-1951 жылдар аралығында Хайнц Рутихаузер жоғары деңгейлі тіл және автоматты аудармашы болып табылатын Superplan ұсынды. COBOL (Common Business Oriented Language) бизнес-қосымшалар үшін басым жоғары деңгейлі тілге айналу үшін A0 және FLOW MATIC-тен дамыды. Символдық есептеуге арналған LISP (Тізімді өңдеу). Компилятор технологиясы цифрлық компьютер үшін жоғары деңгейдегі көз бағдарламасын төмен деңгейлі мақсатты бағдарламаға қатаң түрде түрлендіру қажеттілігінен пайда болды. Компиляторды көз кодын талдаумен айналысатын алдыңғы және талдауды мақсатты кодқа синтездеумен айналысатын артқы бөлік ретінде қарастыруға болады. Алдыңғы және артқы бөліктер арасындағы оңтайландыру тиімді мақсатты кодты жасайды. Компилятор технологиясының дамуындағы кейбір маңызды кезеңдер: 1952 жылдың мамыр айы: Ремингтон Рендтегі Грейс Хоппердің командасы A0 бағдарламалау тілі үшін компиляторды жазды (және оны сипаттау үшін "компилятор" терминін қолданды), бірақ A0 компиляторы толық компилятордың қазіргі түсінігіне қарағанда жүктеуші немесе байланыстырушы ретінде жұмыс істеді. 1952 жыл, қыркүйектен бұрын: Манчестер университетіндегі Манчестер Mark I компьютері үшін Алик Гленни жасаған Autocode компиляторы кейбір мамандар тарапынан алғашқы компиляцияланған бағдарламалау тілі деп есептеледі. 1954-1957 жылдар: Джон Бэкюс басқарыған IBM командасы FORTRAN-ды жасады, ол әдетте алғашқы жоғары деңгейлі тіл деп есептеледі. 1957 жылы олар FORTRAN компиляторын аяқтады, ол алғашқы толыққанды компиляторды жасады деп есептеледі. 1959 жыл: Дерек жүйелері тілі жөніндегі конференция (CODASYL) COBOL әзірлеуді бастады. COBOL дизайны A0 және FLOW MATIC-ке сүйенді. 1960-жылдардың басында COBOL бірнеше архитектураларда компиляцияланды. 1958-1960 жылдар: Algol 58, ALGOL 60-тың алдындағысы болды. Ол құрылымдық бағдарламалаудың дамуында маңызды қадам болған кодтық блоктарды енгізді. ALGOL 60 лексикалық ауқыммен ұялатылған функциялар анықтамаларын жүзеге асырған алғашқы тіл болды. Ол рекурсияны қамтыды. Оның синтаксисі BNF арқылы анықталды. ALGOL 60 одан кейін дамыған көптеген тілдерге әсер етті. Тони Хоар былай деді: "Бұл оның алдыңғыларынан ғана емес, сондай-ақ көптеген ізбасарларынан да жақсырақ болды". 1958-1962 жылдар: Джон Маккарти MIT-де LISP-ті жасады. Символды өңдеу мүмкіндіктері жасанды интеллект зерттеуі үшін пайдалы мүмкіндіктерді ұсынды. 1962 жылы LISP 1.5 шығарылымында бірнеше құралдарды атады: Стивен Рассел мен Дэниел Дж. Эдвардс жазған интерпретатор, Тим Харт пен Майк Левин жазған компилятор және ассемблер. Алғашқы операциялық жүйелер мен бағдарламалық құралдар ассемблер тілінде жазылған. 1960-жылдардың және 1970-жылдардың басында жүйелік бағдарламалау үшін жоғары деңгейлі тілдерді қолдану ресурстардың шектеулі болуына байланысты әлі де даулы болды, бірақ бірнеше зерттеу және өнеркәсіптік күш-жігерлер жоғары деңгейлі жүйелік бағдарламалау тілдеріне қарай өтуді бастады, мысалы, BCPL, BLISS, B және C. BCPL (Basic Combined Programming Language) 1966 жылы Кембридж университетіндегі Мартин Ричардс жасады, бастапқыда компилятор жасау құралы ретінде әзірленді. Бірнеше компиляторлар жасалды, Ричардстың кітабы тіл және оның компиляторы туралы түсініктер береді. BCPL ғана емес, әлі де зерттеуде қолданылатын ықпалды жүйелік бағдарламалау тілі болды, сонымен қатар B және C тілдерінің дизайнына негіз болды. BLISS (Basic Language for Implementation of System Software) W. A. Wulf басқаратын Карнеги Меллон университетінің (CMU) зерттеу тобы Digital Equipment Corporation (DEC) PDP 10 компьютері үшін жасады. CMU тобы бір жылдан кейін 1970 жылы BLISS 11 компиляторын жасады. Multics (Multiplexed Information and Computing Service), уақытты бөлісумен жұмыс істейтін операциялық жүйе жобасы, MIT, Bell Labs, General Electric (кейінірек Honeywell) қатысты және MIT-тен Фернандо Корбато басқарды. Multics IBM және IBM пайдаланушылар тобымен әзірленген PL/I тілінде жазылған. IBM-ның мақсаты бизнес, ғылыми және жүйелік бағдарламалау талаптарын қанағаттандыру болды. Талқылауға болатын басқа тілдер де болды, бірақ PL/I ең толық шешімді ұсынды, тіпті ол әлі іске асырылмаған болса да. Multics жобасының алғашқы бірнеше жылдарында тілдің кіші жиынтығы алдыңғы PL/I (EPL) компиляторымен ассемблер тіліне компиляцияланды, оны Дуг МакИлори мен Боб Моррис Bell Labs-тен жасады. EPL толық PL/I үшін бутстрап компиляторы жасалғанша жобаны қолдады. Bell Labs 1969 жылы Multics жобасынан шықты және BCPL тұжырымдамаларына негізделген жүйелік бағдарламалау тілі B жасады, оны Деннис Ричи және Кен Томпсон жазды. Ричи B үшін бутстрап компиляторын жасады және PDP 7 үшін Unics (Uniplexed Information and Computing Service) операциялық жүйесін B-де жазды. Unics ақырында Unix деп аталды. Bell Labs B және BCPL негізінде C дамыту және кеңейтуді бастады. BCPL компиляторы Bell Labs-пен Multics-ке тасымалданды және BCPL Bell Labs-те басымды тіл болды. Бастапқыда Bell Labs-тің B компиляторына алдыңғы бағдарлама C компиляторы жасалғанда қолданылды. 1971 жылы жаңа PDP 11 ресурсты...

Компилятор құрылымы

Компилятор жоғары деңгейдегі бастапқы бағдарламаны төмен деңгейдегі мақсатты бағдарламаға формалды түрлендіруді іске асырады. Компиляторды жобалау толыққанды шешімді анықтауға немесе басқа компиляция құралдарымен, мысалы, алдын ала өңдеушілермен, құрастырушылармен, байланыстырғыштармен интерфейс жасайтын белгілі бір кіші жиынтықпен жұмыс істеуге мүмкіндік береді. Жобалау талаптары компилятор компоненттері арасындағы және қолдау құралдары жиынтығымен арасындағы қатаң анықталған интерфейстерді қамтиды. Бастапқы кезеңдерде компиляторды жобалауға қолданылған тәсіл өңделетін компьютер тілінің күрделілігіне, оны жобалаған адамның тәжірибесіне және қолданыстағы ресурстарға тікелей байланысты болды. Ресурстардың шектелуі бастапқы кодты бірнеше рет қарау қажеттілігіне әкелді. Бір адам жазған салыстырмалы түрде қарапайым тіл үшін компилятор бір монолиттік бағдарламалық құрал бола алады. Дегенмен, бастапқы тілдің күрделілігі артқан сайын, жобалау бір-біріне тәуелді бірнеше кезеңге бөлінуі мүмкін. Жеке кезеңдер компиляция процесіндегі функцияларға назар аудара отырып, жобалауды жақсартуға мүмкіндік береді.

Бір-жолақты және көп-жолақты компиляторлар

Компиляторларды өту саны бойынша жіктеу компьютерлердің аппараттық ресурстарының шектеулілігіне байланысты туындаған. Компиляция көп еңбек талап етеді және алғашқы компьютерлерде осы еңбектің бәрін атқаратын бір бағдарламаны сақтауға жеткілікті жад болмады. Осының салдарынан компиляторлар кішкентай бағдарламаларға бөлінді, олардың әрқайсысы бастапқы кодтан (немесе оның бір бейнесінен) өтіп, қажетті талдаулар мен аудармалардың бір бөлігін жүзеге асыратын. Бір рет өту арқылы компиляциялау мүмкіндігі классикалық түрде артықшылық ретінде қарастырылады, себебі ол компиляторды жазу жұмысын жеңілдетеді және бір рет өту арқылы компиляциялайтын компиляторлар көбінесе көп рет өту арқылы компиляциялайтындардан жылдам жұмыс істейді. Осылайша, алғашқы жүйелердің ресурстық шектеулерінің ықпалымен көптеген ерте тілдер бір рет өту арқылы компиляцияланатындай етіп әзірленді (мысалы, Паскаль). Кейбір жағдайларда тілдің мүмкіндігінің дизайны компилятордың бастапқы кодтан бірнеше рет өтуін қажет етуі мүмкін. Мысалы, 10-жолдағы түйіннің аудармасына әсер ететін 20-жолдағы жарияланымды қарастырайық. Бұл жағдайда, бірінші өтуде оларға әсер ететін түйіндерден кейін пайда болатын жарияланымдар туралы ақпарат жинақталуы керек, ал нақты аударма келесі өту кезінде жүзеге асырылады. Бір рет өту арқылы компиляциялаудың кемшілігі – жоғары сапалы кодты жасау үшін қажетті көптеген күрделі оптимизацияларды жүзеге асыру мүмкін емес. Оптимизациялайтын компилятордың қанша рет өтуін дәл санау қиын болуы мүмкін. Мысалы, оптимизацияның әртүрлі кезеңдері бір өрнекті бірнеше рет талдауы мүмкін, бірақ басқа өрнекті бір рет қана талдауы мүмкін. Компиляторды кішкентай бағдарламаларға бөлу – дәлелмен дұрыс компиляторларды жасауға қызығушылық танытқан зерттеушілер қолданатын әдіс. Кішкентай бағдарламалар жиынтығының дұрыстығын дәлелдеу көбінесе үлкен, біртұтас, баламалы бағдарламаның дұрыстығын дәлелдеуден аз күш-жігерді қажет етеді.

Үш сатылы компилятор құрылымы

Компиляторды құрастырудағы фазалардың нақты санына қарамастан, фазаларды үш кезеңнің біріне жатқызуға болады. Бұл кезеңдер: фронтенд, миддлтенд және бэкенд. Фронтенд кірісті сканерлеп, белгілі бір бастапқы тілге сәйкес синтаксистік және семантикалық дұрыстығын тексереді. Статикалық типтеу тілдерінде типтік ақпаратты жинақтап, типтік тексеруді жүргізеді. Егер кіріс бағдарламасы синтаксистік қатеге тап болса немесе типтік қатесі болса, ол қате және/немесе ескерту хабарламаларын шығарады, әдетте, қате анықталған бастапқы кодтың орнын көрсетеді; кейбір жағдайларда нақты қате бағдарламаның (әлдеқайда) бұрын болған болуы мүмкін. Фронтендтің құрамына лексикалық талдау, синтаксистік талдау және семантикалық талдау кіреді. Фронтенд кіріс бағдарламасын миддлтендпен одан әрі өңдеу үшін аралық өрнеуге (IR) түрлендіреді. Бұл IR әдетте бастапқы кодқа қарағанда бағдарламаның төменгі деңгейлі өрнеуі болып табылады. Миддлтенд мақсатты процессор архитектурасына тәуелсіз IR-де оптимизацияларды жүргізеді. Бұл бастапқы кодтан/машиналық кодтан тәуелсіздік, компилятордың әртүрлі тілдерді және процессорларды қолдайтын нұсқалары арасында жалпы оптимизацияларды пайдалануға мүмкіндік береді. Миддлтендтегі оптимизацияның мысалдары: пайдасыз кодты (өлі кодты жою) немесе қолжетпеуге болатын кодты (жетуді талдау) жою, тұрақты мәндерді табу және тарату (тұрақты тарату), есептеуді сирек орындалатын жерге жылжыту (мысалы, циклден тыс) немесе есептеуді контекстке сәйкес ету, нәтижеде "оптимизацияланған" IR шығарылады, ол бэкендпен қолданылады. Бэкенд миддлтендтен оптимизацияланған IR-ді қабылдайды. Ол мақсатты процессор архитектурасына тән қосымша талдаулар, түрлендірулер мен оптимизацияларды орындай алады. Бэкенд мақсатты құрастыру кодын жасайды, сонымен қатар регистрлерді бөлуді жүзеге асырады. Бэкенд нұсқауларды жоспарлайды, яғни нұсқауларды қайта реттейді, параллель орындалатын бірліктерді бос емес ұстау үшін кешіктіру ұяларын толтырады. Көптеген оптимизация мәселелері NP-қиын болғанымен, оларды шешуге арналған эвристикалық әдістер жақсы дамыған және өндірістік сападағы компиляторларда жүзеге асырылған. Әдетте, бэкендтің шығысы белгілі бір процессор мен операциялық жүйеге арналған машиналық код болып табылады. Бұл фронтенд/миддлтенд/бэкенд тәсілі миддлтендтің оптимизацияларын бөлісе отырып, әртүрлі тілдер үшін фронтендтерді және әртүрлі процессорлар үшін бэкендтерді біріктіруге мүмкіндік береді. Бұл тәсілдің практикалық мысалдары: GNU Compiler Collection, Clang (LLVM негізіндегі C/C++ компиляторы) және Amsterdam Compiler Kit, олардың бірнеше фронтендтері, ортақ оптимизациялары және бірнеше бэкендтері бар.

Артқы жағы

Артқы бөлік CPU архитектурасына тән оптимизациялармен және кодты құрумен айналысады. Маңызды мысал – бұл қысқа нұсқаулар тізбектерін тиімдірек нұсқауларға қайта жазатын "көз тесігінен қарау" оптимизациясы. Кодты құру: түрлендірілген аралық тіл шығыс тіліне, әдетте жүйенің түпкілікті машина тіліне аударылады. Бұл ресурс пен жадты бөлу туралы шешімдерді қамтиды, мысалы, қай айнымалыларды тіркелгілерге және жадқа орналастыруды, сондай-ақ тиісті машиналық нұсқауларды олардың сәйкес адрестеу режимдерімен бірге таңдау және жоспарлау (Сethi–Ullman алгоритміне де қараңыз). Дебюгтеуді жеңілдету үшін дебюгтеу деректерін жасау қажет болуы мүмкін.

Компилятордың дұрыстығы

Компилятордың дұрыстығы – компилятордың тілдік сипаттамасына сәйкес жұмыс істейтінін дәлелдеуге бағытталған бағдарламалық инженерия саласы. Бұл үшін ресми әдістерді қолдана отырып компиляторды жасау, сондай-ақ қолданыстағы компиляторды қатаң сынау (әдетте компиляторды тексеру деп аталады) сияқты тәсілдер қолданылады.

Жиналған және аударған тілдер

Жоғары деңгейдегі бағдарламалау тілдері әдетте аударма түрін ескере отырып жасалады: компиляцияланатын немесе интерпретацияланатын тіл ретінде. Дегенмен, практикада, тілдің тек компиляциялануын немесе тек интерпретациялануын талап ететін ештеңе сирек кездеседі, бірақ орындау кезінде қайта интерпретацияға негізделген тілдерді жобалау мүмкін. Мұндай жіктеу әдетте тілдің ең танымал немесе кең таралған нұсқаларын көрсетеді. Мысалы, BASIC кейде интерпретацияланған тіл деп аталады, ал C – BASIC компиляторлары мен C интерпретаторларының болуына қарамастан, компиляцияланған тіл. Интерпретация толыққанды компиляцияны алмастырмайды. Ол оны пайдаланушыдан жасырып, бірте-бірте жүзеге асырады. Интерпретатордың өзі интерпретацияланса да, орындалу стегінің төменгі жағында тікелей орындалатын машиналық нұсқаулар жиынтығы қажет (машина тіліне қараңыз). Сонымен қатар, оңтайландыру үшін компиляторлар интерпретатор функционалдығын қамтуы мүмкін, ал интерпретаторларға алдын ала компиляциялау әдістері енгізілуі мүмкін. Мысалы, егер өрнекті компиляция кезінде орындауға болып, нәтижелерді шығару бағдарламасына қосуға болады, онда ол бағдарлама әр орындалған сайын қайта есептеуді болдырмайды, бұл соңғы бағдарламаның жылдамдығын арттырады. Қазіргі заманғы дереу компиляциялау (just-in-time compilation) және байт-кодты интерпретациялау тенденциялары компиляторлар мен интерпретаторлардың дәстүрлі жіктелуін одан әрі бұлыстырады. Кейбір тілдік сипаттамаларда нұсқалар компиляция құралын қамтуы тиіс делінеді, мысалы, Common Lisp. Алайда, Common Lisp анықтамасында оны интерпретациялауға кедергі келтіретін ештеңе жоқ. Басқа тілдерде интерпретаторда іске асыру өте оңай, бірақ компиляторды жазуды әлдеқайда қиындататын мүмкіндіктер бар; мысалы, APL, SNOBOL4 және көптеген сценарийлік тілдер бағдарламаларға жүйелі жол операциялары арқылы орындалу кезінде кездейсоқ бастапқы кодты құруға мүмкіндік береді, содан кейін оны арнайы бағалау функциясына жіберу арқылы орындайды. Мұндай мүмкіндіктерді компиляцияланған тілде іске асыру үшін бағдарламаларға әдетте компилятордың өзінің нұсқасын қамтитын орындалу уақыты кітапханасы қосылуы керек.