Подтвердить что ты не робот

CLR-реализация вызовов виртуального метода для членов интерфейса

Из любопытства: как виртуальный метод отправки CLR вызывает для членов интерфейса правильную реализацию?

Я знаю о VTable, который CLR поддерживает для каждого типа с слотами для каждого метода, а также тот факт, что для каждого интерфейса он имеет дополнительный список слотов методов, которые указывают на реализацию связанных интерфейсных методов. Но я не понимаю следующее: как CLR эффективно определяет, какой список слотов метода интерфейса выбрать из типа VTable?

Статья Сверлить внутри .NET Framework Internals, чтобы посмотреть, как среда CLR создает объекты времени выполнения в выпуске журнала MSDN за май 2005 года. таблицу сопоставления уровня процесса IVMap, индексированную по идентификатору интерфейса. Означает ли это, что все типы в одном и том же процессе имеют один и тот же указатель на один и тот же файл IVMap?

В нем также указано, что:

Если MyInterface1 реализуется двумя классами, будет два записи в таблице IVMap. Запись вернется к началу суб-таблицы, встроенной в таблицу методов MyClass.

Как CLR знает, какую запись выбрать? Выполняет ли линейный поиск, чтобы найти запись, соответствующую текущему типу? Или бинарный поиск? Или какое-то прямое индексирование и иметь карту с возможно большим количеством пустых записей в ней?

Я также прочитал главу о интерфейсах в CLR через С# 3rd edition, но об этом не говорит. Поэтому ответы на этот другой вопрос не отвечают на мой вопрос.

4b9b3361

Ответ 1

.NET Stack

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

Означает ли это, что все типы одного и того же процесса имеют один и тот же указатель на один и тот же файл IVMap?

Да, поскольку он находится на уровне домена, это означает, что все в этом AppDomain имеет тот же самый IVMap.

Как CLR знает, какую запись выбрать? Выполняет ли линейный поиск, чтобы найти запись, соответствующую текущему типу? Или бинарный поиск? Или какое-то прямое индексирование и иметь карту с возможно большим количеством пустых записей в ней?

Классы выкладываются с помощью смещений, поэтому все имеет относительно установленную область, где бы это было. Это облегчает поиск методов. Он будет искать таблицу IVMap и найти этот метод из интерфейса. Оттуда он переходит к MethodSlotTable и использует реализацию этого класса интерфейса. Карта inteface для класса содержит метаданные, однако реализация рассматривается так же, как и любой другой метод.

Снова с сайта, который вы связали:

Каждая реализация интерфейса будет иметь запись в IVMap. Если MyInterface1 реализуется двумя классами, в таблице IVMap будет две записи. Запись укажет на начало подкатегории, встроенной в таблицу методов MyClass.

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


EDIT: чтобы предоставить дополнительную информацию о IVMap.

Опять же, из ссылки в OP:

Первые 4 байта первой записи InterfaceInfo указывают на TypeHandle MyInterface1 (см. рис. 9 и рис. 10). Следующий WORD (2 байта) занят Flags (где 0 наследуется от родителя, а 1 - в текущем классе). СЛОВО сразу после того, как флаги являются начальным слотом, который используется загрузчиком классов для размещения подэлемента реализации интерфейса.

Итак, у нас есть таблица, где число является смещением байтов. Это всего лишь одна запись в IVMap:

+----------------------------------+
| 0 - InterfaceInfo                |
+----------------------------------+
| 4 - Parent                       |
+----------------------------------+
| 5 - Current Class                |
+----------------------------------+
| 6 - Start Slot (2 Bytes)         |
+----------------------------------+

Предположим, что в этом AppDomain есть 100 записей интерфейса, и нам нужно найти реализацию для каждого из них. Мы просто сравниваем 5-й байт, чтобы узнать, соответствует ли он нашему текущему классу, и если да, то мы переходим к коду в 6-м байте. Поскольку каждая запись имеет длину 8 байтов, нам нужно будет сделать что-то вроде этого: (Psuedocode)

findclass :
   if (!position == class) 
      findclass adjust offset by 8 and try again

Пока это линейный поиск, на самом деле он не займет много времени, так как размер повторяющихся данных не огромен. Надеюсь, что это поможет.


EDIT2:

Итак, посмотрев на диаграмму и задавшись вопросом, почему нет слота 1 в IVMap для класса на диаграмме, я перечитаю раздел и нашел это:

IVMap создается на основе информации о интерфейсной карте, встроенной в таблицу методов. Интерфейсная карта создается на основе метаданных класса во время процесса компоновки MethodTable. Как только заполнение набора завершено, в диспетчерах метода используется только IVMap.

Таким образом, IVMap для класса загружается только интерфейсами, которые наследует определенный класс. Похоже, что он копирует из домена IVMap, но поддерживает только интерфейсы, на которые указывают. Это вызывает другой вопрос, как? Скорее всего, это эквивалент того, как С++ делает vtables, где каждая запись имеет смещение, а карта интерфейса содержит список смещений, которые должны быть включены в IVMap.

Если мы посмотрим на IVMap, который может быть для всего этого домена:

+-------------------------+
| Slot 1 - YourInterface  |
+-------------------------+
| Slot 2 - MyInterface    |
+-------------------------+
| Slot 3 - MyInterface2   |
+-------------------------+
| Slot 4 - YourInterface2 |
+-------------------------+

Предположим, что в этом домене всего 4 реализации интерфейсной карты. Каждый слот будет иметь смещение (подобное записи IVMap, которую я опубликовал ранее), и IVMap для этого класса будет использовать эти смещения для доступа к записи в IVMap.

Предположим, что каждый слот имеет 8 байтов с слотом 1, начинающимся с 0, поэтому, если мы хотим получить слот 2 и 3, мы сделаем что-то вроде этого:

mov ecx,edi
mov eax, dword ptr [ecx]
mov eax, dword ptr [ecx+08h] ; slot 2
; do stuff with slot 2
mov eax, dword ptr [ecx+10h] ; slot 3
; do stuff with slot 3

Извините мой x86, поскольку я не знаком с ним, но я попытался скопировать то, что у них есть в связанной статье.

Ответ 2

Эта статья более 10 лет, и с тех пор многое изменилось.

IVMaps теперь заменены Диспетчером виртуального буфера.

Диспетчер виртуальной блокировки (VSD) - это метод использования заглушек для вызовов виртуальных методов вместо традиционной таблицы виртуальных методов. Раньше диспетчер интерфейса требовал, чтобы интерфейсы имели уникальные идентификаторы процесса и каждый загруженный интерфейс был добавлен в глобальную карту виртуальной таблицы интерфейса.

Пойдите, прочитайте эту статью, у нее есть больше деталей, которые вам когда-либо понадобится знать. Он исходит из Book of the Runtime, который был документацией, первоначально написанной разработчиками CLR для разработчиков CLR, но теперь опубликован для всех. Это в основном описывает мужество времени выполнения.

Здесь нет смысла дублировать статью, но я просто укажу основные моменты и то, что они подразумевают:

  • Когда JIT видит вызов члена интерфейса, он компилирует его в заглушку для поиска. Это часть кода вызовет общий распознаватель.
  • Общий распознаватель - это функция, которая будет определять способ вызова. Это самый общий и, следовательно, самый медленный способ вызова такого метода. Когда вызывается в первый раз из очереди просмотра, он будет исправлять эту заглушку (переписать ее код во время выполнения) в заглушку отправки. Он также генерирует заглушку решения для последующего использования. В этот момент заглушка поиска исчезнет.
  • Отправляемая заглушка - это самый быстрый способ вызвать член интерфейса, но есть улов: он оптимистичен в отношении того, что вызов мономорфный, что означает, что он оптимизирован для случая, когда всегда вызов интерфейса разрешается одному и тому же конкретному типу. Он сравнивает таблицу методов (т.е. Конкретный тип) объекта с ранее увиденным (который жестко закодирован в заглушку) и вызывает кешированный метод (адрес которого также также заблокирован), если сравнение выполняется успешно. Если он терпит неудачу, он возвращается к заглушке разрешения.
  • Завершение решения обрабатывает полиморфные вызовы (общий случай). Он использует кеш, чтобы найти способ вызова. Если метод не находится в кеше, он вызывает общий преобразователь (который также записывает в этот кеш).

И здесь важное соображение, прямо из статьи:

Когда шлейф диспетчера выходит из строя достаточно часто, сайт вызова считается полиморфным, и заглушка разрешения вернет исправление сайта вызова, чтобы указать прямо на заглушку решения, чтобы избежать накладных расходов на постоянный отказ от отправки. В точках синхронизации (в настоящее время конец GC) полиморфные сайты будут случайным образом продвигаться обратно на мономорфные сайты вызовов в предположении, что полиморфный атрибут сайта вызова обычно является временным. Если это предположение неверно для какого-либо конкретного сайта вызова, оно быстро инициирует обратный вызов, чтобы снова понизить его до полиморфизма.

Среда исполнения действительно оптимистична в отношении мономорфных сайтов вызовов, что имеет большой смысл в реальном коде, и она будет стараться как можно больше избежать разрешенных окурков.

Ответ 3

Из первой статьи, которую вы связали:

Если MyInterface1 реализуется двумя классами, будет два записи в таблице IVMap. Запись вернется к началу таблицы, встроенной в таблицу методов MyClass, как показано в Рисунок 9

и

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

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

Может быть, все неправильно.