Фактический 8086 вообще не конвейеризован (кроме предварительной выборки); это микрокодировано Он завершает обратную запись одной инструкции перед началом декодирования следующей; единственный опасный эффект - отбрасывание буфера предварительной выборки после ветвлений.
x86 инструкции могут быть трудно переданы (особенно в память-назначение); это было до 486 / Pentium, когда это действительно было сделано, и тогда сложные инструкции остановили конвейер порядка (в основном, опасность внутри одной инструкции, например add [edx], eax
или pop eax
). Только в Pentium Pro (микроархитектура P6) даже такие инструкции можно было эффективно обрабатывать (декодируя до 1 или более мопов и обрабатывая их через exe-order exe c). См. Руководство по микроархитектуре Agner Fog https://agner.org/optimize/
(Реальное семейство P6 и другие вышедшие из строя exe c x86 скрывают опасности WAW и WAR путем переименования регистров. См. Почему mulss занимает всего 3 цикла в Haswell, в отличие от таблиц инструкций Агнера? (Развертывание циклов FP с несколькими аккумуляторами) и Оптимизация программы для конвейера в процессорах семейства Intel Sandybridge .)
Код, который вы показали, не строго x86; здесь нет мнемоники; Чистая инструкция загрузки x86 называется mov
. Кроме того, LOAD DL, BL
не имеет смысла; ни один из операндов не может обращаться к памяти; это только 8-битные регистры. Если вы имели в виду копирование между регистрами, это также mov dl, bl
.
Я хотел бы видеть пример обеих опасностей (управляющая опасность + опасность для данных) в одном и том же коде
Простым примером будет косвенная ветвь (управляющая опасность), цель которой была недавно написана (истинная зависимость от RAW-данных).
например, если мы примем 16-битный режим (так как вы упомянули 8086):
push offset target ; modifies SP (the stack pointer), then stores to memory at SS:SP
ret ; ordinary near return = pop ip
target:
push 123
ret
имеет 2 входа:
- регистр SP (только что записанный pop: опасность RAW)
- память, на которую указывает SP ( также только что написано pop, также опасно для памяти в формате RAW.
RET пишет SP (опасность WAR, хотя сам RET был последним читателем). Также WAW, если мы считаем, что pu sh и ret оба пишут SP.
RET выполняет косвенный переход (в основном pop ip
), используя адрес, загруженный из памяти (управляющая опасность для конвейера, если есть). Все текущие процессоры будут неверно прогнозировать ret
, потому что у них есть специальный стек предикторов call / ret, который предполагает, что ret
перейдет к адресу возврата совпадающего call
, как в обычном коде. (http://blog.stuffedcow.net/2018/04/ras-microbenchmarks/)
push 123
по заданному адресу возврата
- читает и записывает SP (RAW и WAR опасности )
- записывает в память, что предыдущий pu sh написал (опасность памяти WAW), и какой RET только что прочитал (опасность памяти WAR).
Я положил push
после ret
на тот случай, если вы хотите посмотреть только на пару ret / pu sh, с pu sh в «тени» возможно ошибочно предсказанной ветви.
Конечно, буфер хранилища с пересылкой хранилища скрывает / обрабатывает опасности данных в памяти, эффективно переименовывая расположение памяти / кэша. (Модель упорядочения памяти x86 в основном соответствует программному порядку + буфер хранилища с пересылкой хранилища: ядрам разрешается перезагружать свои собственные хранилища, прежде чем они станут глобально видимыми.)
Современные процессоры x86 обрабатывают цепочку зависимостей данных RAW посредством указатель стека с «механизмом стека», который может отслеживать несколько смещений указателя стека за такт. (И, что не менее важно, устраняет необходимость в дополнительном мопе для фактического добавления E / RSP в бэк-энде, поэтому push
/ pop
может быть единичным мопом.) Таким образом, это эффективный механизм альтернативной нулевой задержки для выполнения частей модификации указателя стека инструкций стека. Непосредственное использование E / RSP (например, mov bp, sp
) приводит к стеку syn c uop (на процессорах Intel), который обнуляет сохраненное смещение и применяет его к значению сервера. Если смещение было ненулевым.