C ++: Метапрограмування за допомогою API компілятора, а не з функціями C ++


10

Це почалося як питання SO, але я зрозумів, що це досить нетрадиційно, і, виходячи з фактичного опису на веб-сайтах, він може бути краще підходить для програмістів.se, оскільки питання має велику концептуальну вагу.

Я вивчав кланг LibTooling, і це дуже потужний інструмент, здатний викрити всю " солону крупу " коду по-дружньому, тобто семантично , і не здогадуючись. Якщо clang може скласти ваш код, тоді clang певний щодо семантики кожного окремого символу всередині цього коду.

Тепер дозвольте мені на мить відступити.

Існує багато практичних проблем, які виникають, коли людина займається метапрограмуванням шаблонів на C ++ (і особливо, коли заходить за рамки шаблонів на територію розумних, хоча і жахливих макросів). Якщо чесно, для багатьох програмістів, в тому числі і я, багато звичайних способів використання шаблонів теж дещо жахливі.

Думаю, хорошим прикладом можуть бути рядки часу компіляції . Це питання, якому зараз вже понад рік, але зрозуміло, що C ++ на даний момент не спрощує простого смертного. Незважаючи на те, що ці параметри недостатньо для того, щоб викликати у мене нудоту, вона все ж залишає мене невпевненою в тому, що я можу створити магічний, максимально ефективний машинний код, щоб відповідати будь-яким фантазійним додаткам для мого програмного забезпечення.

Я маю на увазі, визнаймо, люди, струнні досить прості та основні. Деякі з нас просто хочуть зручного способу випромінювання машинного коду, який має певні рядки, «запечені» значно більше, ніж ми отримуємо при простому кодуванні. У нашому C ++ коді.

Введіть clang та LibTooling, який розкриває абстрактне синтаксичне дерево (AST) вихідного коду та дозволяє простому користувальницькій програмі C ++ правильно та надійно маніпулювати необробленим вихідним кодом (використовуючи Rewriter) поряд із багатою семантичною об'єктно-орієнтованою моделлю всього в AST. З ним обробляється багато речей. Він знає про розширення макросів і дозволяє вам слідкувати за цими ланцюгами. Так, я кажу про перетворення вихідного коду або переклад.

Моя основна теза тут полягає в тому, що кланг тепер дозволяє нам створювати виконувані файли, які самі можуть функціонувати як ідеальні спеціальні етапи препроцесора для нашого програмного забезпечення C ++, і ми можемо реалізувати ці етапи метапрограмування за допомогою C ++. Нас просто обмежує той факт, що цей етап повинен приймати вхід, який є дійсним кодом C ++, і виробляти як вихід більш дійсний код C ++. Плюс будь-які інші обмеження, застосовані вашою системою збирання.

Вхід повинен бути принаймні дуже близьким до дійсного коду C ++, тому що, нарешті, clang - це перехідник компілятора, і ми просто роздивляємось та творчим з його API. Я не знаю, чи є якесь положення про можливість визначення нового синтаксису для використання, але явно ми повинні розробити способи його правильного розбору та додати його до проекту clang, щоб це зробити. Очікувати більше - це щось у проекті "clang", що виходить за межі сфери.

Не проблема. Я б міг уявити, що деякі неопераційні макро функції можуть впоратися з цим завданням.

Ще один спосіб розглянути те, що я описую, - це реалізувати конструкції метапрограмування за допомогою програми C ++, маніпулюючи AST нашим вихідним кодом (завдяки кланг та його API), а не реалізовувати їх, використовуючи більш обмежені інструменти, доступні в самій мові. Це також має чіткі переваги для компіляції (шаблони-важкі заголовки сповільнюють компіляцію пропорційно тому, як часто ви їх використовуєте. Багато складених матеріалів потім ретельно узгоджується і викидається лінкером).

Це, однак, пов'язано з тим, щоб запровадити додатковий крок або два в процесі збирання, а також вимагати написання деякого (правда,) дещо більш детального програмного забезпечення (але принаймні це просто C ++) під час виконання нашого інструменту .

Це не вся картина. Я майже впевнений, що існує набагато більший простір функціональності, який можна отримати від створення коду, що є надзвичайно складним або неможливим за допомогою основних мовних особливостей. У C ++ ви можете написати шаблон або макрос або шалену комбінацію обох, але в інструменті clang ви можете змінювати класи та функції будь-яким способом, якого ви можете досягти за допомогою C ++ під час виконання , маючи повний доступ до смислового вмісту, крім шаблону та макросів та всього іншого.

Отже, мені цікаво, чому всі вже не роблять цього. Це те, що ця функція від clang є такою новою, і ніхто не знайомий з величезною ієрархією класів AST? Це не може бути.

Можливо, я трохи недооцінюю складність цього, але робити "маніпуляцію з рядком під час компіляції" інструментом "Кланг" майже кримінально просто. Це багатослівно, але шалено прямо. Все, що потрібно, - це купа макрофункціональних функцій, які відображають реальні std::stringоперації. Плагін clang реалізує це, отримуючи всі відповідні неопераційні виклики макрокоманд і виконує операції з рядками. Потім цей інструмент вставляється як частина процесу збирання. Під час складання ці неопераційні виклики макро-функцій автоматично оцінюються в їхніх результатах, а потім вставляються назад як звичайні старі рядки часу компіляції в програму. Потім програма може бути складена як завжди. Насправді ця отримана програма також набагато більш портативна, як результат, не вимагає нового модного компілятора, що підтримує C ++ 11.


Це незвично довге питання. Не могли б ви звести його до найбільш релевантних точок?
амон

Я розміщую багато довгих питань. Але особливо з цим, я думаю, всі частини питання важливі. Може пропустити перші 6 абзаців? Ха-ха.
Стівен Лу

3
Дуже схоже на синтаксичні макроси, що з'явилися в Ліспі, і нещодавно їх підхопили Хакс, Немерле, Скала та подібні мови. Про те, чому макроси Lisp вважаються шкідливими, є чимало. Хоча я ще не чув переконливого аргументу, ви, можливо, знайдете причини, через які люди неохоче додають їх до кожної мови (окрім того, що це не обов'язково прямо).
back2dos

Так, його мета-ifying C ++. Що може означати кращий, швидший код. Що стосується цих мов. Ну з чого я почну. Що багатомільйонна відеоігра реалізована на будь-якій із цих мов? Що сучасний веб-браузер, реалізований на будь-якій з цих мов? Ядро ОС? Гаразд, насправді здається, що у Хакс є деяка кількість тяги, але ви розумієте.
Стівен Лу

1
@ nwp, Ну, я не можу не зазначити, що ви, здається, пропустили всю точку публікації. Строки часу компіляції - це просто надуманий і мінімально конкретний приклад можливостей, доступних нам зараз.
Стівен Лу

Відповіді:


7

Так, Вірджинія, є Санта-Клаус.

Поняття про використання програм для зміни програм існує давно. Початкова ідея виникла від Джона фон Ноймана у вигляді комп'ютерів із збереженою програмою. Але модифікація машинного коду машинним кодом довільними способами є досить незручною.

Люди, як правило, хочуть змінити вихідний код. Здебільшого це реалізується у вигляді систем перетворення програм (PTS) .

Як правило, PTS пропонують, принаймні для однієї мови програмування, можливість аналізувати AST, маніпулювати цим AST та регенерувати дійсний вихідний текст. Якщо насправді ви копаєтесь, для більшості основних мов хтось створив такий інструмент (Clang є прикладом для C ++; компілятор Java пропонує цю можливість як API, Microsoft пропонує Rosyln, JDT Eclipse, ...) з процедурною процедурою API, який насправді дуже корисний. Для широкої спільноти майже кожна спільнота, що відповідає мові, може вказувати на щось подібне, реалізоване з різним рівнем зрілості (як правило, скромно, багато "просто парсери, що виробляють AST"). Щасливе метапрограмування.

[Є спільнота, орієнтована на рефлексію, яка намагається здійснювати метапрограмування всередині мови програмування, але лише досягає модифікації поведінки у режимі "виконання", і лише в тій мірі, коли компілятори мови надавали деяку інформацію за допомогою відображення. За винятком LISP, завжди є деталі про програму, які недоступні за допомогою роздумів ("Лука, тобі потрібне джерело"), які завжди обмежують те, що може відображати.]

Більш цікаві PTS роблять це для довільних мов (ви даєте інструменту опис мови як параметр конфігурації, включаючи як мінімум BNF). Такі PTS також дозволяють зробити перетворення "джерело в джерело", наприклад, вказати шаблони безпосередньо, використовуючи поверхневий синтаксис цільової мови; використовуючи такі шаблони, ви можете кодувати фрагменти, що цікавлять, та / або знаходити та замінювати фрагменти коду. Це набагато зручніше, ніж API програмування, оскільки вам не потрібно знати всі мікроскопічні деталі про AST, щоб зробити більшу частину вашої роботи. Подумайте про це як мета-метапрограмування: -}

Мінус: якщо PTS не пропонує різні види корисних статичних аналізів (таблиці символів, контроль управління та потоки даних), важко написати таким чином дійсно цікаві перетворення, тому що вам потрібно перевірити типи та перевірити потоки інформації для більшості практичних завдань. На жаль, ця здатність насправді є рідкісною для загальної ПТС. (Він завжди недоступний із запропонованим постійно "Якби я просто проаналізував ..." Дивіться мою біографію для більш тривалого обговорення "Життя після розбору").

Існує теорема, яка говорить про те, що якщо ви можете зробити переписування рядків [таким чином переписування дерев], ви можете зробити довільну трансформацію; і, таким чином, ряд PTS спирається на це, щоб стверджувати, що ви можете перепрограмувати що завгодно, лише пропонуючи переписані дерева, які вони пропонують. Хоча теорема задовольняє в тому сенсі, що ви зараз впевнені, що можете зробити що завгодно, це незадовільно таким же чином, що здатність машини Тьюрінга робити що-небудь не робить програмування машини Тьюрінга способом вибору. (Це ж справедливо і для систем з просто процедурними API, якщо вони дозволять вам внести довільні зміни в AST [і насправді я думаю, що це не стосується Clang]).

Те, що ви хочете, - найкраще з обох світів - система, яка пропонує вам спільність типу параметрів мови, що параметризується мовою (навіть обробка декількох мов), з додатковими статичними аналізами, можливість поєднання перетворень джерела на джерело з процедурними API. Я знаю лише двох, які роблять це:

  • Rascal (MPL) Мова метапрограмування
  • наш інструментарій для реінжинірингу програмного забезпечення DMS

Якщо ви не хочете писати описи мови та статичні аналізатори самостійно (для C ++ це величезна кількість роботи, саме тому Кланг був побудований і як компілятор, і як загальна процедура процедурного метапрограмування), вам потрібно буде PTS зі зрілим описом мови вже в наявності. Інакше ви витратите весь свій час на налаштування PTS, і ніхто не виконує ту роботу, яку ви насправді хотіли зробити. [Якщо ви виберете випадкову мову, яка не є основною, цього кроку дуже важко уникнути].

Раскаль намагається зробити це, вибравши "OPP" (Other People Parsers), але це не допомагає в частині статичного аналізу. Я думаю, що у них досить добре в руках Java, але я впевнений, що вони не роблять C або C ++. Але, це академічний інструмент дослідження; важко звинуватити їх.

Підкреслюю, у нашого [комерційного] інструмента DMS доступні повні передні кінці Java, C, C ++. Що стосується C ++, він охоплює майже все в C ++ 14 для GCC і навіть варіації Microsoft (і ми зараз поліруємо), розширення макросів та умовне управління, контроль на рівні методів та аналіз потоку даних. І так, ви можете задати граматичні зміни практичним способом; ми створили власну систему VectorC ++ для клієнта, який докорінно розширив C ++, щоб використовувати суму, яка відповідає операціям з паралельним даним F90 / APL. DMS використовувався для виконання інших масштабних завдань метапрограмування у великих системах C ++ (наприклад, архітектурна перестановка додатків). (Я архітектор DMS).

Щасливе мета-метапрограмування.


Класно, я думаю, що Clang і DMS, хоча вони мають певні можливості перекриття, є частинами програмного забезпечення, які насправді не в одній категорії. Я маю на увазі, одне, мабуть, смішно дороге, і я, мабуть, ніколи не можу виправдати ресурси, які знадобиться для отримання доступу до нього, а інший - необмежений вільний відкритий код. Це величезна різниця ... Частина того, що мене хвилює від цих захоплюючих можливостей метапрограмування, насправді полягає в тому, що дозволено не тільки вільно користуватися ним, але і вільно поширювати бінарні інструменти на основі кланг.
Стівен Лу

Все, що продається на комерційній основі, "надзвичайно дорого" порівняно з безкоштовним. Невимірна вартість - це не проблема; Що важливо, це те, що для деяких людей окупність придбання комерційного продукту вище, ніж окупність безкоштовного артефакту, інакше комерційного програмного забезпечення не буде. Це очевидно залежить від ваших конкретних потреб. Clang є цікавим моментом у просторі інструментів, і, безумовно, матиме точки корисного застосування. Я хотів би подумати (оскільки я архітектор DMS), що DMS має більш широкі можливості. Clang навряд чи підтримуватимуть інші мови, крім C ++, як приклад.
Іра Бакстер

Звичайно. Немає сумніву в тому, що DMS неймовірно потужний, майже до точки магії (à la Arthur C. Clarke), і, хоч кланг чудовий, це справді просто добре написаний C ++ фронтенд, якого дуже багато. Дуже багато невеликих кроків вперед, і все-таки це було б не дуже справедливо порівнювати його з DMS. На жаль, навіть маючи такі потужні інструменти в нашому розпорядженні, робоче програмне забезпечення не пише себе. Це все ще має існувати шляхом ретельного перекладу з використанням інструментів або (майже завжди найкращий варіант), написаного свіжим.
Стівен Лу

Ви не можете дозволити собі створювати такі інструменти, як Clang або DMS. Ви також не можете дозволити собі кинути ту заявку, яку ви написали з командою, яка склала 10 років. Такі засоби нам знадобляться все частіше, оскільки розміри та тривалість життя програмного забезпечення продовжують зростати.
Іра Бакстер

@StevenLu: Ну, DMS дякує за комплімент, але в цьому немає нічого магічного. DMS має перевагу майже 2 лінійних десятиліття інженерної роботи та чисту архітектурну платформу (aw, shucks, YMMV), яка досить добре тримається. Аналогічно, у Кланг багато хорошої інженерії. Я погоджуюся, вони не були розроблені для вирішення точно тієї самої проблеми ... Обсяг DMS явно призначений бути більшим, що стосується символічного маніпулювання програмою, і набагато меншим, якщо мова йде про компілятор виробництва.
Іра Бакстер

4

Метапрограмування в C ++ за допомогою API компіляторів (замість використання шаблонів) дійсно цікаво і практично можливо. Оскільки метапрограмування ще не є (поки) стандартизованим, ви будете прив'язані до конкретного компілятора, що не стосується шаблонів.

Отже, мені цікаво, чому всі вже не роблять цього. Це те, що ця функція від clang є такою новою, і ніхто не знайомий з величезною ієрархією класів AST? Це не може бути.

Багато людей це роблять, але іншими мовами. На мою думку, більшість розробників C ++ (або Java, або C) не бачать потреби в цьому (можливо, правильно) або не звикли до метапрограмування підходів; Я також думаю, що вони задоволені особливостями рефакторингу / генерації коду у своїх IDE, і що б то не було фантазії, було видно занадто складне / важке для підтримки / важке для налагодження. Без належних інструментів це може бути правдою. Ви також повинні враховувати інерційність та інші нетехнічні проблеми, наприклад, наймання та / або навчання людей.

До речі, оскільки ми згадуємо про Common Lisp та його макросистему (див. Відповідь Басіля), я мушу сказати, що тільки вчора Clasp був звільнений (я не пов'язаний):

Clasp має намір бути відповідною реалізацією Common Lisp, яка компілюється в LLVM IR. Більше того, він відкриває розробнику бібліотеки Clang (AST, Matcher).

  • По-перше, це означає, що ви можете писати в CL і більше не використовувати C ++, за винятком випадків, коли використовуєте його бібліотеки (і якщо вам потрібні макроси, використовуйте макроси CL).

  • По-друге, ви можете написати інструменти в CL для вашого існуючого коду C ++ (аналіз, рефакторинг, ...).


3

Кілька компіляторів C ++ мають деякі більш-менш задокументовані та стабільні API, зокрема більшість компіляторів безкоштовного програмного забезпечення.

Clang / LLVM - це здебільшого великий набір бібліотек, і ви можете ними користуватися.

Останні GCC приймають плагіни . Зокрема, ви можете розширити його за допомогою MELT (який сам по собі є мета-плагіном, надаючи вашій доменній мові вищого рівня для розширення GCC).

Зауважте, що синтаксис C ++ не легко розширюється в GCC (і, мабуть, ні в Clang), але ви можете додати свої власні прагми, вбудовані елементи, атрибути та переходи компілятора, щоб робити те, що ви хочете (можливо, також надаючи кілька макросів препроцесора, що викликають ці речі надати синтаксис, зручний для користувачів).

Можливо, вас зацікавлять багатоступеневі мови та компілятори, див., Наприклад, реалізація багатоетапних мов із використанням ASTs, Gensym та Reflection paper від C.Calcagno та ін. і працювати навколо MetaOcaml . Ви, звичайно, повинні заглянути все в макро-об'єкти Common Lisp . І вас можуть зацікавити такі бібліотеки JIT, як libjit , блискавка GNU , навіть LLVM , або просто - під час виконання! - генеруйте якийсь код C ++, розкладіть компіляцію його в динамічну бібліотеку спільного об'єкта, а потім dlopen (3), що поділилася об’єкт. Блог Дж. Пітрата також пов'язаний з такими рефлексивними підходами. А також RefPerSys .


Цікаво. Це дуже добре, коли GCC продовжує розвиватися тут. Це не відповідь, яка стосується всього, що я запитав, але мені це все одно подобається.
Стівен Лу

Re: ваші нові зміни ... Це хороший момент щодо самого переписування коду. Це насправді починає приносити такі можливості мета-програми також і на C ++, набагато доступніше, ніж раніше, що також досить цікаво.
Стівен Лу
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.