Иногда термин «BTB» используется вместе для обозначения всех буферов, используемых блоком прогнозирования ветвлений.Однако на самом деле существует несколько буферов, каждый из которых используется в каждом цикле для прогнозирования цели и направления.В частности, BTB используется для прогнозирования прямых ветвлений, ITB (косвенный целевой буфер) используется для прогнозирования косвенных ветвлений, за исключением возвратов, а RSB используется для прогнозирования возвратов.ITB также называется IBTB или косвенным целевым массивом.Все эти термины используются разными поставщиками и исследователями.Как правило, BTB используется для создания начальных прогнозов для всех видов команд ветвления, когда другие буферы отсутствуют.Но позже предиктор узнает больше о ветвях, и другие буферы вступают в игру.Если несколько динамических экземпляров одной и той же косвенной ветви имеют одну и ту же цель, то вместо ITB может также использоваться BTB.ITB гораздо точнее, когда одна и та же ветвь имеет несколько целей и предназначена специально для работы с такими ветвями.См .: Ветвление предсказания и выступления переводчиков - Не верь фольклору .Первым процессором Intel, в котором реализованы отдельные структуры BTB и ITB, является Pentium M. Все более поздние процессоры Intel Core имеют выделенные ITB.
Эксплойт Spectre V1 основан на обучении BTB с использованием атакующего.запрограммируйте так, чтобы, когда жертва выполняла ветвь, которая совмещала одну и ту же запись BTB, процессор обманчиво умело выполнял инструкции (называемые гаджетом) для утечки информации.Эксплойт Spectre V2 аналогичен, но вместо этого основан на обучении ITB.Принципиальное отличие состоит в том, что в V1 процессор неверно прогнозирует направление ветви, а в V2 процессор неверно прогнозирует target ветви (и, в случае условногокосвенная ветвь, направление тоже, потому что мы хотим, чтобы это было принято).В программах, которые интерпретируются, JIT-компилируются или используют динамический полиморфизм, может быть много косвенных ветвей (кроме возвратов).Конкретная непрямая ветвь никогда не может быть предназначена для перехода в какое-то место, но, неправильно обучив предиктора, она может быть сделана, чтобы прыгнуть куда угодно.Именно по этой причине V2 очень мощный;независимо от того, где находится гаджет и какие бы намеренные потоки управления программы не находились, вы можете выбрать одну из косвенных ветвей и заставить ее умозрительно перейти к гаджету.
Обратите внимание, что обычно это линейный адресцель статической прямой ветки остается неизменной на протяжении всего жизненного цикла программы.Есть только одна ситуация, когда это может быть не так: динамическая модификация кода.Так что, по крайней мере теоретически, эксплойт может быть разработан на основе target неправильного предсказания прямых ветвей.
Относительно утилизации LFB, я не очень понимаю, что вы говорите.Когда запрос на загрузку, который пропустил L1D, получает данные в LFB, данные немедленно передаются в обходное соединение конвейера.Должен быть способ определить, какой тип загрузки запросил эти данные.Возвращаемые данные должны быть помечены UOP ID загрузки.Источники мопов в RS, которые ожидают данные, представлены в виде идентификаторов моп нагрузок.Кроме того, запись ROB, в которой хранится загрузка, должна быть помечена как завершенная, чтобы ее можно было удалить, а в pre-SnB возвращенные данные необходимо записать в ROB.Если при очистке конвейера невыполненный запрос на загрузку в LFB не отменяется, и если идентификатор загрузки uop повторно использовался для некоторого другого uop, когда данные поступают, он может быть неправильно перенаправлен на все новые операции в настоящее время в конвейере,тем самым портит микроархитектурное состояние.Таким образом, должен быть способ гарантировать, что это не произойдет ни при каких обстоятельствах.Очень возможно отменить невыполненные запросы на загрузку и спекулятивные RFO при очистке конвейера, просто пометив все действительные записи LFB как «отмененные», просто чтобы данные не возвращались в конвейер.Тем не менее, данные все еще могут быть получены и заполнены в один или несколько уровней кэшей.Запросы в LFB идентифицируются физическими адресами с выравниванием строк.Возможны и другие варианты.
Я решил провести эксперимент, чтобы точно определить, когда LFB будут освобождены на Haswell.Вот как это работает:
Outer Loop (10K iterations):
Inner Loop (100 iterations):
10 load instructions to different cache lines most of which miss the L2.
LFENCE.
A sequence of IMULs to delay the resolution of the jump by 18 cycles.
Jump to inner.
3 load instructions to different cache lines.
LFENCE.
Jump to outer.
Чтобы это работало, необходимо отключить гиперпоточность и оба средства предварительной выборки L1, чтобы гарантировать, что мы владеем всеми 10 LFB L1.
Инструкции LFENCE
гарантируют, что мы не исчерпаем LFB при выполнении на правильно предсказанном пути.Ключевая идея здесь заключается в том, что внутренний скачок будет неверно предсказан один раз за внешнюю итерацию, поэтому в LFB можно выделить до 10 нагрузок внутренней итерации, которые находятся на неверно предсказанном пути.Обратите внимание, что LFENCE
предотвращает распределение нагрузок от более поздних итераций.Через несколько циклов внутренняя ветвь будет разрешена, и произойдет неправильное предсказание.Конвейер очищается, а внешний интерфейс извлекается для извлечения и выполнения команд загрузки во внешнем цикле.
Возможны два результата:
- LFB, которые были выделены для нагрузок на неверно предсказанном пути, немедленно освобождаются как часть операции очистки конвейера и становятся доступными для других нагрузок.,В этом случае не будет остановок из-за недоступности LFB (подсчитывается с использованием
L1D_PEND_MISS.FB_FULL
). - LFB освобождаются только тогда, когда нагрузки обслуживаются независимо от того, были ли они на неверно предсказанном пути.
Когда во внутреннем цикле после внутреннего скачка есть три нагрузки, измеренное значение L1D_PEND_MISS.FB_FULL
примерно равно числу внешних итераций.Это один запрос на итерацию внешнего цикла.Это означает, что когда три нагрузки на правильном пути передаются на L1D, нагрузки от неверно указанного пути все еще занимают 8 записей LFB, что приводит к событию заполнения FB для третьей загрузки.Это говорит о том, что нагрузка в LFB будет де-лакирована только тогда, когда нагрузка действительно завершится.
Если я добавлю менее двух загрузок во внешний цикл, в основном не будет событий FB full.Я заметил одну вещь: для каждой дополнительной нагрузки во внешнем контуре, превышающей три нагрузки, L1D_PEND_MISS.FB_FULL
увеличивается примерно на 20 КБ вместо ожидаемых 10 КБ.Я думаю, что происходит то, что, когда запрос на загрузку UOP нагрузки выдается в L1D впервые, и все LFB используются, он отклоняется.Затем, когда LFB становится доступным, две нагрузки, ожидающие в буфере загрузки, отправляются на L1D, одна будет распределена в LFB, а другая будет отклонена.Таким образом, мы получаем два события LFB full на каждую дополнительную нагрузку.Однако, когда во внешнем цикле три нагрузки, только третья будет ожидать LFB, поэтому мы получаем одно событие на каждую итерацию внешнего цикла.По существу, буфер загрузки не может различить наличие одного LFB или двух LFB;он только узнает, что по крайней мере один LFB свободен, и поэтому он пытается отправить два запроса на загрузку одновременно, так как есть два порта загрузки.