C ++: метапрограммирование с помощью API компилятора, а не с функциями C ++

10

Это началось как вопрос SO, но я понял, что это довольно нетрадиционно и, исходя из фактического описания на веб-сайтах, он может быть более подходящим для программистов. Так как вопрос имеет большой концептуальный вес.

Я изучал clang LibTooling, и это очень мощный инструмент, способный разоблачить весь «мелкий элемент» кода дружественным образом, то есть семантическим способом, а не угадывая. Если clang может скомпилировать ваш код, то clang уверен в семантике каждого символа внутри этого кода.

Теперь позвольте мне сделать шаг назад.

Есть много практических проблем, которые возникают, когда кто-то участвует в метапрограммировании шаблонов C ++ (и особенно, когда выходит за рамки шаблонов на территорию умных, хотя и пугающих макросов). Честно говоря, для многих программистов, в том числе и для меня, многие из обычного использования шаблонов также несколько пугают.

Я думаю, хорошим примером были бы строки времени компиляции . Это вопрос, которому уже более года, но ясно, что C ++ на данный момент не делает это простым для простых смертных. Хотя просмотра этих вариантов недостаточно для того, чтобы вызвать у меня тошноту, тем не менее, я не уверен в том, что смогу создать волшебный, максимально эффективный машинный код, который бы подходил для любого модного приложения для моего программного обеспечения.

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

Введите clang и LibTooling, которые предоставляют абстрактное синтаксическое дерево (AST) исходного кода и позволяют простому пользовательскому приложению C ++ правильно и надежно манипулировать необработанным исходным кодом (используя Rewriter) вместе с богатой семантической объектно-ориентированной моделью всего в AST. Это обрабатывает много вещей. Он знает о расширениях макросов и позволяет вам следовать этим цепочкам. Да, я говорю о преобразовании исходного кода в исходный код или переводе.

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

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

Не проблема. Я полагаю, что некоторые неоперативные макро-функции могут справиться с этой задачей.

Другой способ взглянуть на то, что я описываю, - это реализовать конструкции метапрограммирования с использованием среды выполнения C ++, манипулируя AST нашего исходного кода (благодаря clang и его API) вместо того, чтобы реализовывать их с помощью более ограниченных инструментов, доступных в самом языке. Это также имеет явные преимущества в производительности компиляции (заголовки с большими шаблонами замедляют компиляцию пропорционально тому, как часто вы их используете. Множество скомпилированных материалов затем тщательно подбирается и отбрасывается компоновщиком).

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

Это не вся картина. Я совершенно уверен, что существует гораздо больший объем функциональности, который может быть получен при создании кода, что чрезвычайно сложно или невозможно с функциями основного языка. В C ++ вы можете написать шаблон или макрос или сумасшедшую комбинацию обоих, но в инструменте clang вы можете изменять классы и функции ЛЮБОМ способом, которого вы можете достичь с C ++, во время выполнения , имея полный доступ к семантическому контенту, кроме шаблонов и макросов и всего остального.

Итак, мне интересно, почему все еще не делают этого. Это то, что эта функциональность от Clang настолько новая, и никто не знаком с огромной иерархией классов AST Clang? Этого не может быть.

Возможно, я просто недооцениваю сложность этого, но «манипулирование строками во время компиляции» с помощью инструмента clang почти преступно просто. Это многословно, но безумно просто. Все, что нужно, - это набор неиспользуемых макрофункций, которые отображаются на реальные реальные std::stringоперации. Плагин clang реализует это путем извлечения всех соответствующих макро-вызовов no-op и выполняет операции со строками. Этот инструмент затем вставляется как часть процесса сборки. Во время сборки эти вызовы макрокоманд без операции автоматически оцениваются в их результатах, а затем вставляются обратно в виде простых старых строк времени компиляции в программе. Затем программа может быть скомпилирована как обычно. Фактически, эта результирующая программа также намного более переносима, в результате не требуется новый модный компилятор, поддерживающий C ++ 11.

Стивен Лу
источник
Это необычно длинный вопрос. Не могли бы вы, возможно, сократить это до ваших наиболее важных моментов?
Амон
Я пишу много длинных вопросов. Но особенно с этим, я думаю, что все части вопроса важны. Может быть, пропустить первые 6 абзацев? Ха - ха.
Стивен Лу
3
Звучит очень похоже на синтаксические макросы, впервые появившиеся в Лиспе и недавно подобранные Haxe, Nemerle, Scala и подобными языками. Существует довольно много рассуждений о том, почему макросы Lisp считаются вредными. Хотя я еще не слышал убедительных аргументов, вы можете найти причины, по которым люди не хотят добавлять их к каждому языку (кроме того факта, что это не обязательно прямо).
назад2
Да, это метафайинг C ++. Что может означать лучший, более быстрый код. Что касается этих языков. Ну, с чего мне начать. Что представляет собой видеоигра стоимостью в несколько миллионов долларов на любом из этих языков? Какой современный веб-браузер реализован на любом из этих языков? Ядро ОС? Хорошо, на самом деле кажется, что у Haxe есть некоторая тяга, но вы поняли идею.
Стивен Лу
1
@nwp, ну, я не могу не отметить, что вы, кажется, пропустили весь смысл поста. Строки времени компиляции - просто самый надуманный и минимальный конкретный пример возможностей, доступных нам сейчас.
Стивен Лу

Ответы:

7

Да, Вирджиния, есть Санта-Клаус.

Идея использования программ для модификации программ существует уже давно. Первоначальная идея пришла от Джона фон Неймана в виде компьютеров с хранимой программой. Но машинный код, модифицирующий машинный код произвольным образом, довольно неудобен.

Люди обычно хотят изменить исходный код. В основном это реализуется в форме программных систем преобразования (PTS) .

Как правило, PTS предлагает, по крайней мере для одного языка программирования, возможность анализировать AST, манипулировать этим AST и восстанавливать действительный исходный текст. Если на самом деле вы копаетесь, для большинства основных языков кто-то создал такой инструмент (Clang является примером для C ++, компилятор Java предлагает эту возможность в качестве API, Microsoft предлагает Rosyln, Eclipse JDT, ...) с процедурным API, который на самом деле довольно полезен. Для более широкого сообщества почти каждое языковое сообщество может указывать на что-то подобное, реализованное с различными уровнями зрелости (обычно скромное, многие «просто парсеры, производящие AST»). Удачного метапрограммирования.

[Существует сообщество, ориентированное на рефлексию, которое пытается осуществлять метапрограммирование изнутри языка программирования, но добивается лишь модификации поведения во время выполнения, и только в той степени, в которой языковые компиляторы сделали некоторую информацию доступной для рефлексии. За исключением LISP, всегда есть детали о программе, которые недоступны для отражения («Люк, тебе нужен источник»), которые всегда ограничивают то, что может сделать отражение.]

Более интересные PTS делают это для произвольных языков (вы даете инструменту описание языка в качестве параметра конфигурации, включая как минимум BNF). Такие PTS также позволяют вам выполнять преобразование «источник-источник», например, указывать шаблоны непосредственно, используя синтаксис поверхности целевого языка; используя такие шаблоны, вы можете кодировать интересующие фрагменты и / или находить и заменять фрагменты кода. Это гораздо удобнее, чем API программирования, потому что вам не нужно знать все микроскопические детали об AST, чтобы выполнять большую часть вашей работы. Думайте об этом как мета-метапрограммирование: -}

Недостаток: если PTS не предлагает различные виды полезного статического анализа (таблицы символов, контроль и анализ потока данных), сложно написать действительно интересные преобразования таким образом, потому что вам нужно проверять типы и проверять потоки информации для большинства практических задач. К сожалению, эта возможность на самом деле редка в общем PTS. (Это всегда недоступно с когда-либо предложенным «Если бы у меня только что был парсер ...» Смотрите мою биографию для более длинной дискуссии «Жизнь после разбора»).

Есть теорема, которая говорит, что если вы можете сделать перезапись строки [таким образом, переписывание дерева], вы можете сделать произвольное преобразование; и поэтому многие PTS полагаются на это, чтобы утверждать, что вы можете метапрограммировать все, что только предлагает переписанное дерево. Хотя теорема является удовлетворительной в том смысле, что вы теперь уверены, что можете сделать что-либо, она неудовлетворительна так же, как способность машины Тьюринга что-либо делать не делает программирование машины Тьюринга предпочтительным методом. (То же самое относится и к системам с только процедурными API, если они позволят вам вносить произвольные изменения в AST [и на самом деле я думаю, что это не относится к Clang]).

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

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

Если вы не хотите сами писать описания языков и статические анализаторы (для C ++ это огромный объем работы, поэтому Clang создавался как компилятор и как основа общего процедурного метапрограммирования), вам понадобится PTS со зрелыми описаниями языков. уже доступно. В противном случае вы будете тратить все свое время на настройку PTS, и никто не будет выполнять ту работу, которую вы на самом деле хотели сделать. [Если вы выбираете случайный, не основной язык, этот шаг очень трудно избежать].

Rascal пытается сделать это, используя кооптирование "OPP" (парсеры других людей), но это не помогает в части статического анализа. Я думаю, что у них достаточно хорошо Java, но я уверен, что они не делают C или C ++. Но это инструмент академических исследований; их сложно винить.

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

Счастливое мета-метапрограммирование.

Ира Бакстер
источник
Круто, я думаю, что Clang и DMS, хотя у них есть некоторые перекрывающиеся возможности, являются частями программного обеспечения, которые на самом деле не относятся к той же категории. Я имею в виду, что один, вероятно, смехотворно дорог, и я, вероятно, никогда не смогу оправдать ресурсы, которые потребуются для получения доступа к нему, а другой - неограниченный бесплатный открытый исходный код. Это огромная разница ... Часть того, что меня воодушевляет эти захватывающие возможности метапрограммирования, - это факт, что допустимо не только использовать его свободно, но и свободно распространять бинарные инструменты на основе clang.
Стивен Лу
Все, что продается в коммерческих целях, «дорого обходится» по сравнению с бесплатным. Сырая стоимость не проблема; важно то, что для некоторых людей срок окупаемости коммерческого продукта выше, чем срок окупаемости бесплатного артефакта, иначе не было бы коммерческого программного обеспечения. Это, очевидно, зависит от ваших конкретных потребностей. Clang - интересная точка в пространстве инструментов, и у нее наверняка будут точки полезного применения. Я хотел бы думать (так как я - архитектор DMS), что у DMS есть более широкие возможности. Например, Clang вряд ли будет хорошо поддерживать языки, отличные от C ++.
Ира Бакстер,
Безусловно. Нет сомнений в том, что DMS невероятно мощен, почти на грани магии (а-ля Arthur C. Clarke), и хотя clang великолепен, он на самом деле является просто хорошо написанным C ++-интерфейсом, которого много. Огромное количество маленьких шагов вперед, и все равно было бы несправедливо сравнивать его с DMS. Увы, даже с такими мощными инструментами, которые есть в нашем распоряжении, работающее программное обеспечение не пишет себя. Он все еще должен возникать благодаря тщательному переводу с использованием инструментов или (почти всегда превосходного варианта) написанного заново.
Стивен Лу
Вы не можете позволить себе создавать такие инструменты, как Clang или DMS из свежих. Также вы не можете позволить себе бросить приложение, которое вы написали, командой из 10 человек в течение 5 лет. Мы будем нуждаться в таких инструментах все чаще и чаще, так как размеры и срок службы программного обеспечения продолжают расти.
Ира Бакстер,
@StevenLu: Хорошо, DMS благодарит вас за комплимент, но в этом нет ничего волшебного. Преимущество DMS - это почти 2 линейных десятилетия разработки и чистой архитектурной платформы (оу, shucks, YMMV), которая выдержала довольно неплохо. Точно так же в Clang есть много хороших разработок. Я согласен, они не были предназначены для решения точно такой же проблемы ... Область действия DMS явно предназначена для того, чтобы быть большей, когда речь идет о символических манипуляциях с программами, и намного меньше, когда речь идет о том, чтобы быть рабочим компилятором.
Ира Бакстер
4

Метапрограммирование в C ++ с API компиляторов (вместо использования шаблонов) действительно интересно и практически возможно. Так как метапрограммирование (пока) не стандартизировано, вы будете привязаны к определенному компилятору, что не имеет место с шаблонами.

Итак, мне интересно, почему все еще не делают этого. Это то, что эта функциональность от Clang настолько новая, и никто не знаком с огромной иерархией классов AST Clang? Этого не может быть.

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

Кстати, поскольку мы упоминаем Common Lisp и его макросистему (см. Ответ Basile), я должен сказать, что только вчера Clasp был выпущен (я не аффилирован):

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

  • Во-первых, это означает, что вы можете писать на CL и больше не использовать C ++, за исключением случаев использования его библиотек (и, если вам нужны макросы, используйте макросы CL).

  • Во-вторых, вы можете написать инструменты на CL для существующего кода C ++ (анализ, рефакторинг, ...).

CoreDump
источник
3

Некоторые компиляторы C ++ имеют более или менее документированный и стабильный API, в частности большинство компиляторов свободного программного обеспечения.

Clang / LLVM - это в основном большой набор библиотек, и вы можете использовать их.

Последние GCC принимает плагины . В частности, вы можете расширить его, используя MELT (который сам по себе является мета-плагином, предоставляя вам язык более высокого уровня для расширения GCC).

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

Возможно, вас заинтересуют многоэтапные языки и компиляторы, см., Например , статью « Реализация многоэтапных языков с использованием ASTs», «Gensym» и «Reflection», написанную C.Calcagno et al. и работать вокруг MetaOcaml . Вам, безусловно, стоит заглянуть внутрь макро-возможностей Common Lisp . И вас могут заинтересовать библиотеки JIT, такие как libjit , GNU lightning , даже LLVM , или просто - во время выполнения! - сгенерируйте некоторый код C ++, разбейте его компиляцию в динамическую библиотеку разделяемых объектов, а затем dlopen (3), которая разделяет объект. Блог Дж. Питрата также связан с такими рефлексивными подходами. А также RefPerSys .

Василий Старынкевич
источник
Интересно. Это очень приятно видеть, что GCC продолжает развиваться здесь. Это не ответ, который касается всего, что я спросил, но, тем не менее, мне это нравится.
Стивен Лу
Re: ваши новые правки ... Это хорошая точка зрения о переписывании кода. Это действительно начинает приносить такие возможности метапрограмм на C ++, гораздо более доступный, чем раньше, что также довольно интересно.
Стивен Лу