Как Swift достигает расширений на уровне машинного кода? - PullRequest
2 голосов
/ 22 октября 2019

Я сейчас бездельничаю с LLVM, и у меня возник вопрос: как Swift может достигать расширений на двоичном уровне?

В Swift можно предоставить базовое расширение, которое просто добавляетметоды, но они также могут предоставить расширение, соответствующее другому протоколу. Этот класс затем распознается как имеющий указанный родительский протокол, и они могут быть отлиты друг от друга. Как это достигается?

Я знаю о vtables и о том, что они могут определять расположение определений функций и тому подобное, но, насколько мне известно, они имеют фиксированную длину, правильно? Может ли Swift достичь этой функциональности с помощью своей библиотеки времени выполнения или типы сопоставлены с LLVM более низкого уровня, и он каким-то образом манипулирует виртуальными таблицами всякий раз, когда определяется новое расширение?

1 Ответ

2 голосов
/ 23 октября 2019

Ответы, которые вы ищете, находятся в документации Swift ABI , в частности TypeMetadata.rst и TypeLayout.rst .

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

Когда у нас есть переменная, тип которой является existential (т.е. статически неизвестно), Swift сохраняет эту переменную в структуре времени выполнения, называемой экзистенциальный контейнер . TypeLayout.rst описывает формат экзистенциального контейнера:

Непрозрачные экзистенциальные контейнеры

Если для протокола или типа композиции протокола нет ограничения класса,Экзистенциальный контейнер должен принимать значение произвольного размера и выравнивания. Для этого используется буфер фиксированного размера , который имеет три указателя по размеру и выровнен по указателю. Это либо непосредственно содержит значение, если его размер и выравнивание меньше или равно буферу фиксированного размера, либо содержит указатель на распределение сторон, принадлежащее экзистенциальному контейнеру. Тип содержащегося значения определяется его записью метаданных типа, и в него включены таблицы-свидетели для всех требуемых соответствий протокола. Компоновка как будто объявлена ​​в следующей структуре C:

struct OpaqueExistentialContainer {
  void *fixedSizeBuffer[3];
  Metadata *type;
  WitnessTable *witnessTables[NUM_WITNESS_TABLES];
};

Массив witnessTables содержит одну запись для каждого протокола, который наш экзистенциальный статически , как известно, соответствует- поэтому, если наша переменная имеет тип P1 & P2, экзистенциал будет содержать ровно два указателя таблицы-свидетеля (экзистенциальный контейнер специфичен для его ограничений протокола, поэтому любые дополнительные протоколы, которым соответствует конкретный тип, игнорируются). Пока у нас есть таблица свидетелей, описывающая соответствие типа протоколу, мы можем создать экзистенциальный контейнер, передать его и использовать таблицу свидетелей для вызова методов протокола.

Итак, как нам добавить соответствиек типу через расширение? Ну, на самом деле нам не нужно изменять какие-либо свойства самого типа;нам просто нужно создать новую таблицу свидетелей. Чтобы реализовать динамическое приведение между экзистенциальными типами, нам нужен какой-то способ зарегистрировать соответствие среде выполнения Swift;это можно сделать, поместив запись о соответствии протокола в назначенный раздел двоичного файла, где среда выполнения может его найти:

записи о соответствии протокола

Запись о соответствии протокола указывает, что данный тип соответствует определенному протоколу. Записи о соответствии протокола передаются в отдельный раздел, который при необходимости сканируется средой выполнения Swift (например, в ответ на запрос swift_conformsToProtocol ()). Каждая запись соответствия протокола содержит:

  • Дескриптор протокола , описывающий протокол соответствия, представленный в виде (возможно косвенного) 32-битного смещения относительно поля. Младший бит указывает, является ли это косвенным смещением;второй младший бит зарезервирован для будущего использования.

  • Ссылка на соответствующий тип , представленный как 32-битное смещение относительно поля. Два младших бита указывают, как представлен соответствующий тип:

    • 0: прямая ссылка на дескриптор номинального типа.
    • 1: косвенная ссылка на дескриптор номинального типа.
    • 2: зарезервировано для будущего использования.
    • 3: ссылка на указатель на объект класса Objective-C.
  • The поле таблицы свидетелей , которое обеспечивает доступ к таблице свидетелей, описывающей само соответствие, представленное в виде прямого 32-битного относительного смещения. Два младших бита указывают, как представлена ​​таблица свидетелей:

    • 0: Поле таблицы свидетелей является ссылкой на таблицу свидетелей.
    • 1: Поле таблицы свидетелей является ссылкой на функцию доступа к таблице свидетелей для безусловного соответствия.
    • 2: Поле таблицы свидетелей является ссылкой на функцию доступа к таблице свидетелей для условного соответствия.
    • 3: Зарезервировано для будущего использования.
  • A 32-битовое значение зарезервировано для будущего использования.

...