Посмотрим, как фреймворк компилятора для программно генерирования машинного кода облегчил существование и работу с новыми языками.
Новые языки и рост уже существующих,-урожай на эволюционном поле. Rust, jetbrain's Kotlin, Apple's Swift, и мн. др. языки предлагают разработчикам обновленный ряд возможностей для ускорения, мощности, безопасности, удобства и мобильности.
И почему сейчас? Есть одна серьезная причина,-новый инструментарий для создания языков, в частности, компиляторов. Главенствующим среди них является LLVM (Low-Level Virtual Machine),- open source-проект, первоначально разработан автором Swift Крисом Латтнером в виде исследовательского проекта в Университете Иллинойса.
LLVM не только облегчает генерацию новых языков, но и расширяет существующие. Он предлагает инструменты для автоматизации множества самых неблагодарных частей процесса создания языка: создание компилятора, перенос выводимого кода на несколько архитектур и платформ, написание кода для обработки таких общих метафоров, как исключения. Либеральное лицензирование позволяет свободно использовать его в качестве программного компонента или развертывать как сервис.
Перечень языков, использующих LLVM, включает много знакомых имен. Swift использует LLVM в качестве фреймворка, а Rust использует LLVM в качестве основного компонента в цепочке своих инструментов. Помимо этого, многие компиляторы имеют такой выпуск LLVM, как Clang,-компилятор C / C ++ (это имя, «C-lang»), а сам проект, тесно связанный с LLVM. Kotlin, номинально язык JVM, разрабатывает версию языка под названием Kotlin Native, использующую LLVM для компиляции кода.
Сущность LLVM
Ядро LLVM это библиотека для программной реализации кода. Разработчик пользуется API для генерации инструкций в формате IR. LLVM может скомпилировать IR в автономный двоичный файл или выполнить компиляцию JIT для запуска кода в контексте другой программы, такой как интерпретатор языка. LLVM-интерфейсы предоставляют основу для разработки многих общих шаблонов и структур на имеющихся языках. К примеру, почти каждый язык имеет понятие функции и глобальной переменной. LLVM содержит функции и глобальные переменные, как стандартные элементы в своем IR, поэтому вместо того, чтобы изобретать велосипед, используйте реализации LLVM и сфокусируйтесь на тех частях вашего языка, которые требуют особого внимания.
Пример LLVM-ного IR(intermediate representation). Справа-простая программа на C, а слева — тот же код, переведенный в LLVM IR, Clang-компилятором.
LLVM: Предназначен для портативности
Представить, что такое LLVM можно, сравнив его с языком С: иногда С описывается как портативный язык ассемблера высокого уровня. Имея в себе конструкции, которые могут быть тесно связаны с системным оборудованием, он был привнесен почти в каждую системную архитектуру. Но C работает только как портативный язык ассемблера, как побочный эффект того, как это работает; и не являлось одной из целей разработки.
В свою очередь, LLVM IR изначально разработан, чтобы быть портативной сборкой. Одним из путей достижения такой мобильности является создание примитивов, не зависящих от конкретной, машинной архитектуры. К примеру, integer types не ограничены максимальной шириной бита базового оборудования (32 или 64 бита). Можно генерировать примитивные целочисленные типы, используя столько битов, сколько потребуется (например 128-битное целое число). Не нужно также беспокоиться об обработке выходных данных в соответствии с набором команд конкретного процессора; LLVM позаботится об этом вместо вас.
Если хотите увидеть реальные примеры того, как выглядит LLVM IR, перейдите на сайт ELLCC Project и попробуйте live demo преобразование кода C в LLVM IR прямо в браузере.
Каким образом языки программирования пользуются LLVM
Наиболее распространенный вариант использования для LLVM-это компилятор с опережением времени AOT. Но LLVM делает возможными и другие вещи.
Just-in-time компиляция с LLVM
В некоторых ситуациях требуется, чтобы код генерировался «на лету» во время выполнения, а не компилировался раньше времени. Язык Julia, например, JIT-компилирует свой код, поскольку нужно быстро работать и взаимодействовать с пользователем через REPL (read-eval-print loop) или interactive prompt. Mono, разработка .Net, может компилировать собственный код с помощью LLVM-back end.
Пакет математического ускорения Numba для Python, JIT-компилирует выбранные функции Python код. Также он может заранее компилировать код, обработанный Numba, но (как и Julia) Python предлагает быстрое развитие, будучи интерпретированным языком. Использование компиляции JIT для создания такого кода дополняет интерактивный рабочий процесс Python лучше, чем компиляция в режиме обгона. Другие экспериментируют с необычными способами использования LLVM в качестве JIT, например, компиляция запросов PostgreSQL, что дает пятикратное увеличение производительности.
Numba использует LLVM для jit компиляции и ускорения выполнения числового кода. Jit ускоренная sum2d функция заканчивает свое выполнение примерно в 139 раз быстрее, чем обычный Python-код.
Автоматическая оптимизация кода с LLVM
LLVM не просто компилирует IR в собственный код. Можно также программно направить его для оптимизации кода с высокой степенью детализации, вплоть до процесса связывания. Оптимизация можно проводить в довольно агрессивной форме, включая такие моменты, как встроенные функции, удаление мертвого кода (неиспользуемые типы объявлений и аргументы функции) и цикл-развертку.
И в самом деле, мощь в том, что ненужно осуществлять весь процесс самостоятельно. LLVM может обработать все, или можно направить его, чтобы переключить, когда необходимо. К примеру, если требуется, чтобы меньшие двоичные файлы зависели от некоторой производительности, вы могли бы заставить свой front end компилятора рассказать LLVM об отключении разворота цикла.
Специфические доменные языки с LLVM
LLVM была использована для создания компиляторов для многих языков общего назначения, но это столь же полезно и для создания языков, которые являются высокоуровневыми или эксклюзивными для проблемного домена. В какой-то мере, именно здесь LLVM проявляет себя мощнее, потому как избавляет от массы нудных операций при создании такого языка и делает это хорошо.
К примеру, проект Emscripten, принимает LLVM IR-код и конвертирует его в JavaScript, теоретически позволяя любому языку с LLVM back end экспортировать код, работающий в браузере. Долгосрочный план заключается в том, чтобы иметь LLVM-based back ends, которые могут создавать WebAssembly. Emscripten является хорошим примером того, насколько высок потенциал гибкости LLVM.
Еще один плюс в использовании LLVM, это возможность добавления доменных расширений к существующим языкам. Nvidia использовала LLVM, и создала компилятор Nvidia CUDA. Он позволяет языкам добавлять встроенную поддержку CUDA, которая компилируется как часть генерируемого кода, вместо того, чтобы вызываться через библиотеку.
LLVM и работа на разных языках
Стандартный способ работы с LLVM,-использование кода на удобном для вас языке (и, естественно, поддержка библиотек LLVM). Два варианта выбора языка C и C ++. Чаще всего, разработчики LLVM, по умолчанию используют один из двух по следующим причинам:
LLVM написан на C ++.
API LLVM доступны в воплощениях C и C ++.
Значительное развитие языка, происходит с C / C ++ базово.
Несмотря на это, два данных языка не являются единственным выбором. Изначально, многие языки ссылаются на библиотеки C, следовательно, теоретически возможно выполнение LLVM-разработки на любом подобном языке. Но это помогает иметь фактическую библиотеку на языке, упакованном API LLVM. К счастью, многие языки и их среды исполнения имеют такие библиотеки, включающие C # /. Net / Mono,Haskell,Rust,OCAML, Node.js,Python и Go. Важное предостережений заключается в том, что некоторые языковые привязки к LLVM могут оказаться менее полными. С Python, к примеру, существует множество вариаций, но каждая отличается степенью полноты и полезности:
LLVM поддерживает собственный набор привязок к API C LLVM, но на сегодняшний день они не имеют поддержки.
llvmpy выпал из обслуживания в 2015 году — плохо для любого программного проекта, но это нормально при работе с LLVM, учитывая количество изменений, происходящих в каждом выпуске LLVM.
llvmlite, разработанный командой, создавшей Numba, стал действующим претендентом на работу с LLVM в Python. Но он реализует только подмножество функциональности LLVM, как это диктуется потребностями Numba. Но и это подмножество удовлетворяет подавляющее большинство потребностей LLVMпользователей.
llvmcpy стремится своевременно обновить привязки Python для библиотеки C, сохранить их в автоматическом режиме и сделать их доступными, используя родные идиомы Python.
llvmcpy все еще на ранних стадиях, но уже может выполнять некоторые рудиментарные задачи с API LLVM.
Если вас заинтересовал принцип использования библиотеки LLVM для создания языка, у создателей LLVM есть tutorial на C ++ и OCAML, который поможет создать простой язык Kaleidoscope. Этот язык был перенесен в другие:
Haskell: прямой порт оригинального tutorial.
Python: один такой порт внимательно следит за tutorial, а другой — более продвитнутый переписывает с интерактивной командной строкой. Оба используют llvmlite-привязки к LLVM.
Rust and Swift: неизбежно получили порты tutorial на обоих языках, которые сама LLVM и помогла создать.
Что LLVM не делает совсем
Наряду с тем, что действительно LLVM предоставляет, полезно будет знать, чего он не может. К примеру, не анализирует грамматику языка. Многие инструменты, такие какlex/yacc, flex/bison, и ANTLR уже выполняют эти задачи. Но, синтаксический анализ и компиляция должны быть отделены, поэтому ничего удивительного, что LLVM не пробует решить ни одного из этих вопросов. Кроме прочего, LLVM напрямую не затрагивает большую культуру программного обеспечения на конкретном языке. Относительно установки двоичных файлов компилятора, управления пакетами при установке и обновления цепочкой инструментов,-это придется сделать самостоятельно.
И наконец, самое главное, есть общие части языков, на которые LLVM примитивов не предоставляет. Многие языки имеют некий способ управления сбором мусора, либо как основной способ управления памятью, или как дополнение к стратегиям, таким как RAII(использующий C ++ и Rust). LLVM не предоставляет механизм сбора мусора, но предлагает инструменты для реализации сбора. Позволяет коду маркироваться метаданными, а это упрощает написание мусоросборщиков. Но, ничто из перечисленного не исключает возможности LLVM добавления собственных механизмов реализации сбора мусора. LLVM очень быстро растет, с основным выпуском каждые 6 месяцев или около того. Темп развития ускорится благодаря тому, что многие современные языки отдали LLVM центральное место в процессах разработки.
Комментарии (4)
FenixFly
08.02.2018 16:45Многие языки имеют некоторую маску сбора памяти, собираемую с помощью мусора, либо как основной способ управления памятью, или как дополнение к стратегиям, таким как RAII(использующий C ++ и Rust).
Совсем не понятно, что за маска сбора памяти?Tlaskaltek Автор
08.02.2018 16:47Скорее всего, имеется в виду некий способ управления сборкой мусора.
m1n7
Это не перевод, а девелопмент креация эдишна машкода.