Почему инструкции перехода / вызова x86 используют относительные смещения вместо абсолютных назначений?

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

Я понимаю, что CPU выполняет код последовательно, и если мы хотим изменить поток кода, мы бы хотели, чтобы IP указывал на новый / старый адрес, на котором находится интересующий нас код.

Теперь мой вопрос: почему мы (я имею в виду процессор) не просто идем и обновляем IP с адресом, соответствующим метке, когда мы сталкиваемся с инструкцией перехода?

Зачем нужно иметь смещение, которое добавляется к IP, когда мы сталкиваемся с инструкцией перехода?

по моему мнению

  1. вычисление смещения (т.е. расстояние от метки перехода до следующей инструкции после перехода) и
  2. затем, принимая комплимент 2 смещения,
  3. который, наконец, добавляется к IP, так что IP указывает на адрес / инструкцию, указанную меткой

Для меня это звучит как большая работа, чем просто обновление IP с адресом, соответствующим метке. Но я уверен, что должна быть причина, по которой все делается, просто я не осознаю.

Что послужило причиной такого выбора дизайна в 8086 году?

1 ответ

Решение

Вы значительно переоцениваете стоимость процессора при декодировании относительного скачка.

  1. вычисление смещения (т.е. расстояние от метки перехода до следующей инструкции после перехода)
  2. затем, принимая комплимент 2 смещения,

Машинный код должен содержать результат шага 2 (целочисленное относительное смещение со знаком), поэтому все это делается во время сборки. А в ассемблере вычитание двух целочисленных адресов уже дает вам необходимое смещение дополнения со знаком 2.

Использование относительных смещений имеет реальные преимущества, поэтому усугубление ISA только для упрощения написания ассемблера не имело бы никакого смысла. Вам нужно написать ассемблер только один раз, но все, что работает на машине, выигрывает от более компактного кода и независимости позиции.

Относительные смещения ветвей абсолютно нормальны и используются также в большинстве других архитектур (например, ARM: https://community.arm.com/processors/b/blog/posts/branch-and-call-sequences-explained, где исправлено инструкция width делает невозможным прямое абсолютное кодирование ветви). Это сделало бы 8086 странным, чтобы не использовать относительную кодировку ветвлений.

обновление: возможно, не совсем нечетное. MIPS использует rel16 << 2 за beq / bne (Инструкции MIPS имеют фиксированную ширину 32 бита и всегда выровнены). Но безоговорочно j (переход), интересно использовать псевдо-прямое кодирование. Он сохраняет высокие 4 бита ПК и напрямую заменяет PC[27:2] биты со значением, закодированным в инструкции. (Опять же, младшие 2 бита счетчика программы всегда 0.) Таким образом, в пределах одной 1/16 адресного пространства, j инструкции являются прямыми переходами и не дают независимого от позиции кода. Это относится к jal (переход и ссылка = call), что делает вызовы функций из кода PIC менее эффективными:( Linux-MIPS раньше требовал наличия двоичных файлов PIC, но, по-видимому, сейчас этого не происходит (но разделяемые библиотеки все еще должны быть PIC).


Когда процессор работает eb feвсе, что нужно сделать, это добавить смещение к IP вместо замены IP, Поскольку инструкции без перехода уже обновлены IP добавив длину инструкции, аппаратное обеспечение сумматора уже существует.

Обратите внимание, что расширение 8-разрядного смещения знака до 16-разрядного (или 32- или 64-разрядного) тривиально в аппаратном обеспечении: расширение знака дополнения до 2 просто копирует знаковый бит, который не требует логических элементов, а просто подключается к подключите один бит к остальным. (например 0xfe становится 0xfffe, в то время как0x05становится0x0005.)


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

Например, две формы относительногоjmp существовал, один с rel8 (короткий) и один с rel16 (рядом). (В 32 и 64-битном режиме, представленном в более поздних процессорах, E9 код операции jmp rel32 вместо rel16, но EB все еще jmp rel8 потому что переходы внутри функции часто находятся в пределах -128/+127).

Но нет особого сокращения для callпотому что это не будет много пользы большую часть времени. Так почему же все же беспокоит относительное смещение вместо абсолютного?

Ну, x86 имеет абсолютные переходы, но только для косвенных или дальних переходов. (В другой сегмент кода). Например, EA код операции jmp ptr16:16 : "Перейти далеко, абсолют, адрес указан в операнде".

Чтобы сделать абсолютный ближний прыжок, просто mov ax, target_label / jmp ax, (Или в синтаксисе MASM, mov ax, OFFSET target_label).


Относительные смещения не зависят от позиции

Комментарии к вопросу подняли это.

Рассмотрим блок машинного кода (уже собранный) с некоторыми переходами внутри блока. Если вы скопируете весь этот блок на другой начальный адрес (или измените CS базовый адрес, так что один и тот же блок доступен с другим смещением относительно сегмента), тогда только относительные переходы будут работать.

Чтобы метки + абсолютные адреса решали одну и ту же проблему, код должен быть повторно собран с другим ORG директивы. Очевидно, что это не может произойти на лету, когда вы меняете CS с большим JMP!

Другие вопросы по тегам