Вряд ли сейчас кого-то удивишь тем, что развитием архитектуры IA-32 занимается не только Intel, но и такие компании, как AMD и VIA. Больше информации можно найти, например, в статье A. Fog'a. Сегодня я планирую рассказать об одном, на мой взгляд, не до конца продуманном изменении ISA, внесенном компанией AMD.
При мыслях о влиянии AMD на архитектуру IA-32 в первую очередь вспоминается REX префикс и поддержка 64-битного режима процессора. И это безусловно «положительный» эффект, который сделал IA-32 лучше. Однако были и другие интересные изменения, которые лично я положительными назвать не могу.
Кодировка системы команд IA-32 вследствие длительной эволюции превратилась в архисложную структуру (одни только префиксы чего стоят). Рассказывая о некоторых проблемах декодирования и их решениях в статьях «Правильно ли работает ваш дизассемблер?» и «Как справиться с IA-32 кодом или особенности декодера Simics», я забыл упомянуть несколько интересных фактов. Максимально возможная длина IA-32 инструкции — 15 байт. Префиксов в кодировке может присутствовать несколько и их количество фактически ограничено только условием на длину инструкции. При этом один и тот же префикс может встретиться несколько раз, или, например, могут встретиться префиксы, которые никак не могут повлиять на данную инструкцию. Все они будут просто проигнорированы.
На мой взгляд, неплохой пример, иллюстрирующий данную ситуацию, можно привести на основе инструкции NOP
(No OPeration — инструкция, которая не делает ничего. Кодировка 0x90
).
0x66 0x66 0x66 0x66 0x66 0x66 0x66 0x66 0x66 0x66 0x66 0x66 0x66 0x66 0x90
— это тоже инструкция NOP
, все 14 префиксов 0x66
просто игнорируются.
Это, безусловно, очень странная особенность, но от нее уже никуда не деться. А некоторые компиляторы вообще могут использовать префиксы для выравнивания кода.
На этом цветочки закончились, начинаются ягодки.
Уже много лет в интеловской архитектуре существует инструкция BSR
. Она впервые появилась в процессоре Intel 80386. Она находит порядковый номер наиболее значимого бита равного 1.
Например, для числа 0x11aa00bb
данная инструкция вернет 28.
Посмотрим, как ее можно закодировать:
Ничего интересного: 0x0F 0xBD
и Mod R/M байт для операндов.
А теперь давайте добавим к кодировке этой инструкции какой-нибудь префикс… Скажем 0xF3
. Получится валидная инструкция, префикс будет просто проигнорирован, так как относится к строковым операциям или инструкциям ввода/вывода. Никакого криминала.
Что, собственно, сделали товарищи из AMD?
Проведя некоторое исследование, они обнаружили, что комбинация префикса 0xF3
с инструкцией BSR
в программном обеспечении встречается очень редко, и перепрофилировали данную комбинацию в новую инструкцию — LZCNT
, которая вычисляет количество ведущих нулей.
Для того же входного числа 0x11aa00bb
в 32-битном режиме данная инструкция вернет не 28, а 3.
Появилась эта инструкция в составе расширения команд ABM (Advanced Bit Manipulation), состоящем из двух инструкций LZCNT
и POPCNT
(в этой команде, лично я ничего плохого не вижу), каждая из которых при этом имеет отдельный бит в CPUID.
Отключить эту инструкцию, к сожалению, никак нельзя.
Первым набор команд ABM
поддержал процессор AMD, основанный на микроархитектуре Barcelona. Компания Intel добавила инструкцию POPCNT
в набор команд процессора Nehalem. И можно было подумать, что Intel остановится на этом, но нет. Инструкция LZCNT
появилась в процессорах Haswell.
Чем же это плохо?
Во-первых, данное изменение, очевидно, нарушает обратную совместимость. Но это, на мой взгляд, не самое худшее ее свойство. Как упоминалось выше, согласно исследованиям AMD, инструкция BSR
с данным префиксом встречается крайне редко. И все-таки чисто теоретически такая ситуация возможна.
Но статья не об этом, так что давайте теперь отойдем немного от типичных потребностей рядового пользователя и посмотрим на нужды разработчиков.
Как известно большая часть программного стека пишется и отлаживается на симуляторе еще до выпечки самого чипа. Так что давайте посмотрим, как данное изменение может повлиять на скорость и точность симуляции.
Разумеется, всем хочется моделировать как можно быстрее. Скорости обычного интерпретатора никогда не хватает. Все хотят грузить BIOS за секунды, а операционную систему за считанные минуты. По этой причине модель значительно усложняется, появляется оптимизирующий двоичный транслятор, позволяющий сократить время работы симулятора. Но этого все равно недостаточно! Добавляют поддержку прямого исполнения гостевых инструкций на хосте, что еще усложняет модель, улучшая при этом производительность в несколько раз. Подробнее про различные режимы работы симулятора можно почитать в статье «Программная симуляция микропроцессора. Коробка передач».
Несложно догадаться, что ни в интерпретаторе, ни в трансляторе никаких проблем возникнуть не должно. Проблемы могут возникнуть в случае использования аппаратной виртуализации. Ни LZCNT
, ни, тем более, BSR
не вызывает выхода в монитор ВМ.
Это приводит к тому, что если вам понадобится симулировать Haswell+ процессор, то на более старом процессоре, например на Sandy Bridge, вы можете исполнить BSR
вместо LZCNT
. И наоборот, если вы захотите моделировать какой-нибудь более простой процессор, например, Quark на хосте с Haswell, вы рискуете получить противоположный эффект — LZCNT
вместо BSR
.
Они сломали виртуализацию!
Однако решение этой проблемы есть — предварительный просмотр страницы.
Существующий механизм виртуализации позволяет ограничить набор страниц памяти, к котором может обращаться гостевое ПО. Таким образом мы можем разрешить прямое исполнение кода, находящегося только на страницах не содержащих кодировок LZCNT
инструкции. А каждую новую страницу предварительно сканировать на наличие данных команд.
Такое изменение, разумеется, приводит к падению производительности и усложнению без того не простого симулятора. Именно в этом, как мне кажется, и заключается отрицательный эффект данных изменений.
P.S. Такая инструкция не единственная. Вместе с расширением BMI1 Intel добавил новую инструкцию TZCNT
, которая аналогичным образом связана с командой BSF
.
Автор: yulyugin