Статьи

Лоран Сансонетти о RubyMotion Internals

Если бы мы могли открыть RubyMotion и заглянуть внутрь, что бы мы увидели?

Вчера я опубликовал первую половину моего интервью с Лораном Сансонетти о RubyMotion, реализации Ruby, предназначенной для мобильной платформы Apple iOS. Если вы не очень знакомы с RubyMotion, не забудьте прочитать это в первую очередь. У нас была возможность обсудить основы RubyMotion: что это такое, как начался проект и чем написание приложения на RubyMotion отличается от написания стандартного приложения на Ruby с использованием MRI.

В этой статье, во второй половине нашего интервью, у меня была возможность спросить Лорана о внутренней работе RubyMotion: как RubyMotion компилирует код Ruby? Что это значит, точно? Как ваш код преобразуется из Ruby в родной машинный язык, понятный вашему iPhone или iPad? Чем RubyMotion отличается от MacRuby и Rubinius ?

Когда в прошлом году я писал Ruby Under the Microscope , я не смог включить какую-либо информацию о RubyMotion, поскольку это не проект с открытым исходным кодом. Возможность поговорить напрямую с Лораном была для меня и для вас отличным способом узнать о внутренностях RubyMotion. Это действительно уникальная реализация Ruby, о которой должны знать все разработчики Ruby, даже если они в настоящее время не занимаются разработкой для iOS.

RubyMotion и LLVM

Q: Я где-то читал на вашем веб-сайте, что вы используете LLVM в качестве технологии вашего компилятора. Мне интересно, как это работает, на высоком уровне. Вы компилируете код Ruby в набор команд LLVM IR?

RubyMotion, как MacRuby и Rubinius,
использует компилятор LLVM внутри

Да, мы делаем это. Компилятор RubyMotion собирается проанализировать исходный код Ruby в AST , и здесь мы фактически используем анализатор Ruby 1.9.

Q: Вы взяли файл parse.y из МРТ?

Да. Мы используем синтаксический анализатор Ruby 1.9, а затем получаем дерево узлов AST для грамматики файла Ruby. И затем мы перебираем каждый из них и создаем эквивалент на языке LLVM.

LLVM можно объяснить двумя вещами:

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

  • Вторая часть проекта LLVM — это компилятор для этого языка. Это набор модулей, который можно использовать для перевода этих инструкций LLVM, который также называется IR или «внутренним представлением», в сборку.

Ваш код Ruby трансформировался несколько раз, прежде чем
RubyMotion развертывает его на целевом устройстве

Q: То есть виртуальной машины нет? В «LLVM» нет «VM»? Это одна из самых запутанных вещей в LLVM, не так ли?

Абсолютно; на самом деле в LLVM нет виртуальной машины. Иногда люди думают, что это замена для JVM. Они говорят: «Нам нужно перенести этот язык с JVM на LLVM». Но LLVM — это просто компилятор. Там нет времени выполнения там.

Вы можете использовать LLVM двумя способами — первый — статический, опережающий компилятор. Вы передаете ему IR LLVM, и он выдает файл с битовым кодом. Бит-код на самом деле является двоичным представлением языка. И затем из битового кода у вас есть инструменты низкого уровня, которые вы можете использовать для компиляции инструкций по сборке для конкретного процессора и архитектуры. Вы можете использовать Intel 32-bit, 64-bit, ARM, PowerPC и т. Д. LLVM поддерживает множество процессоров.

Q:… включая те, что на устройствах iOS?

Абсолютно.

Я думаю, что это наиболее часто используемый способ использования LLVM. Тогда есть способ, которым проект Clang использует LLVM. Clang — это новый компилятор уровня C от Apple. Предполагается, что он заменит gcc и использует LLVM таким же образом, как RubyMotion.

Второй способ использования LLVM — это компилятор JIT или Just In Time. Это то, что делает Рубиниус, насколько я знаю, и это то, что делает MacRuby.

Q: Так как это отличается?

Единственное отличие состоит в том, что LLVM имеет C ++ API для выполнения процесса компиляции во время выполнения. Вы создаете инструкции IR, а затем вызываете этот конкретный API и получаете указатель на машинный код, который вы можете просто вызвать внутри своей программы.

Q: То есть это тот же компилятор, но вы запускаете его в другое время?

Точно. Вы запускаете его во время выполнения. В MacRuby это режим по умолчанию. Когда вы запустите «macruby foo.rb», он проанализирует, выполнит, а затем выполнит своевременную компиляцию всего файла и выполнит его во время выполнения.

Я считаю, что Рубиниус делает то же самое; Возможно, я ошибаюсь, потому что я не особо слежу за проектом Рубиниуса в эти дни. Я думаю, что они используют LLVM таким образом. В MacRuby мы использовали LLVM очень рано, я думаю, в то же время, что и Рубиниус. Я думаю, что у Рубиниуса был прототип, в какой-то момент, который был очень медленным. В MacRuby мы попробовали LLVM чуть позже, и он работал на нас. Затем Эван Феникс сказал мне, что им действительно удалось заставить его работать. В то время LLVM была очень незрелой; было много ошибок, и это было очень нестабильно.

Q: Это в 2007 или 2008 году?

Это был 2009 год. В то время разработчики LLVM говорили, что LLVM — отличный компилятор, работающий точно в срок, потому что Apple, я думаю, использовал его в стеке OpenGL. Языки программирования должны фактически использовать его как JIT. И это то, что мы сделали для MacRuby и Rubinius. И был проект, который использовал его от Google, реализация Python под названием Unladen Swallow .

Через несколько лет стало ясно, что LLVM не является хорошим компилятором. Это очень медленно, а также немного нестабильно. Также очень утомительно правильно обрабатывать исключения. В то же время LLVM была и остается отличной платформой для создания статических компиляторов, опережающих компиляторов, и именно так мы используем ее в RubyMotion.

В: Я думаю, что вы сделали удивительно: взять динамический язык, такой как Ruby, и скомпилировать его в инструкции на машинном языке — это впечатляет. Могли бы вы показать нам, как RubyMotion преобразует простой метод Ruby в инструкции LLVM IR, а затем в язык ассемблера?

Конечно, абсолютно. Если мы начнем с простого файла hello.rb:

… Компилятор сгенерирует следующий бит-код LLVM (инструкции для
Язык LLVM IR):

Здесь интересным является построение интерполированной строки (вызовы rb_str_new *) и отправка сообщения #puts (вызов vm_dispatch).

Из этого LLVM IR компилятор будет генерировать сборку. Вот версия i386 (для симулятора):

Оптимизация локальных переменных и базовая арифметика

Q: Ранее в первой части вы говорили, что ненавидите метод связывания Ruby Proc # . Почему ты так ненавидишь?

Я думаю, что каждый, кто реализует Ruby, действительно ненавидит этот метод. Лучшим аргументом, вероятно, выступил Чарльз Наттер . Этот метод злой, потому что он делает невозможным оптимизацию локальных переменных. Вот пример:

Здесь невозможно узнать, что возвращает метод. Обычно он должен возвращать 3, но если метод bar написан так:

Q: То есть он оценивает код «x = 42» в контексте этой привязки?

Да, поскольку метод bar оценивается в привязке proc, он будет изменять значения локальных переменных. Так что в RubyMotion, если вы попытаетесь использовать привязку Proc #, это вызовет исключение и скажет, что мы не поддерживаем это.

Благодаря этому мы можем оптимизировать локальные переменные.

Q: Понятно. Это интересно, потому что отличает RubyMotion от JRuby , Rubinius или других рубинов, так как вы меняете язык этими способами. JRuby и Rubinius пытаются сохранить язык, идентичный MRI, используя спецификацию RubySpec .

То же самое было и в MacRuby; мы также поддержали привязку Proc #. Вы правы, в RubyMotion я пытался использовать другой подход. Ну, эти методы на самом деле не используются. Насколько я знаю, они не используются в Rails, и Rails — самый большой пример использования Ruby. Они почти все используют, но не используют привязку Proc #. Кроме того, привязка Proc # имеет последствия для безопасности, которые были недавно вызваны.

Я решил удалить его из RubyMotion, потому что производительность имеет значение, и это позволяет нам оптимизировать локальные переменные. В то же время в RubyMotion нельзя переопределять операторы класса Fixnum. Вы не можете открыть класс Fixnum и переопределить plus, чтобы сделать что-то еще.

Q: Потому что вы хотели позволить нативному коду добавлять данные?

Собственно, чтобы компилировать арифметические операции как можно быстрее. Мы не можем позволить себе проверить, был ли метод Fixnum переопределен. В MacRuby мы поддерживаем все это, но в RubyMotion я удалил некоторые функции Ruby, которые фактически замедляли бы процесс.

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

Единственные жалобы, которые мы получаем, касаются «требовать». Они говорят, что у нас должно быть что-то подобное, поэтому мы могли бы действительно что-то сделать в этом отношении.

Как работает отладка RubyMotion

Q: Что такое DWARF?

DWARF — это формат отладки для языков уровня C. Файл DWARF содержит аннотации для каждого адреса в двоичном файле. Например, это функция … в этой области мы сохраняем локальные переменные определенного типа … эта область содержит класс C ++.

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

Q: То есть вы включаете эту информацию в каждый двоичный файл? Или только когда у вас есть определенные флаги?

Он используется только для разработки, и файл DWARF фактически не является частью двоичного файла; это на самом деле в отдельном файле.

Поэтому, когда вы подключаете GDB к приложению RubyMotion, вы фактически можете увидеть информацию о файле и строке для кадров обратной трассировки и можете использовать «next» для перехода к следующей строке Ruby, что действительно здорово! Это отлично подходит для людей, которые знают, как использовать GDB, но большинству разработчиков Ruby действительно тяжело с GDB. Один из наших планов — написать на самом деле высокоуровневый отладчик Ruby.

Q: Каково будущее отладки в RubyMotion?

В данный момент RubyMotion очень сложно профилировать свои приложения, так как вам нужно использовать GDB, MallocDebug, GuardMalloc, sample, и это инструменты очень низкого уровня. Это не легко. Итак, мы хотим создать какую-то абстракцию поверх этого.