При создании моего ассемблера для x86 платформы я встретился с некоторыми проблемами с кодированием JMP
инструкция:
OPCODE INSTRUCTION SIZE
EB cb JMP rel8 2
E9 cw JMP rel16 4 (because of 0x66 16-bit prefix)
E9 cd JMP rel32 5
...
(с моего любимого x86 веб-сайта инструкции, http://siyobik.info/index.php?module=x86&id=147)
Все - относительные переходы, где размер каждого кодирования (операция + операнд) находится в третьем столбце.
Теперь мой оригинал (и таким образом дают сбой из-за этого) дизайн зарезервировал максимальное (5-байтовое) пространство для каждой инструкции. Операнд еще не известен, потому что это - переход к все же неизвестному местоположению. Таким образом, я реализовал "переписать" механизм, который переписывает операнды в корректном месте в памяти, если местоположение перехода известно и заполняет остальных NOP
s. Это - несколько серьезное беспокойство в жестких циклах.
Теперь моя проблема со следующей ситуацией:
b: XXX
c: JMP a
e: XXX
...
XXX
d: JMP b
a: XXX (where XXX is any instruction, depending
on the to-be assembled program)
Проблема состоит в том, что я хочу самое маленькое кодирование для a JMP
инструкция (и нет NOP
заполнение).
Я должен знать размер инструкции в c
прежде чем я смогу вычислить относительное расстояние между a
и b
для операнда в d
. То же касается JMP
в c
: это должно знать размер d
прежде чем это сможет вычислить относительное расстояние между e
и a
.
Как существующие ассемблеры решают эту проблему, или как Вы сделали бы это?
Это - то, что я думаю, который решает проблему:
Сначала закодируйте все инструкции к кодам операций между
JMP
и это - цель, если этот регион содержит код операции переменного размера, используйте максимальный размер, например.5
для aJMP
. Затем закодируйте родственникаJMP
к он - цель путем выбора самого маленького размера кодирования (3, 4 или 5), и вычислите расстояние. Если какой-либо код операции переменного размера кодируется, измените все абсолютные операнды прежде и все относительные инструкции, который перескакивает через эту закодированную инструкцию: они повторно кодируются, когда их операнд изменяется для выбора самого маленького размера. Этот метод, как гарантируют, закончится, поскольку коды операций переменного размера только могут уменьшиться (потому что он использует максимальный размер их).
Интересно, возможно, это - сверхспроектированное решение, вот почему я задаю этот вопрос.
Вот один из использованных мной подходов, который может показаться неэффективным, но оказывается не подходящим для большинства реального кода (псевдокода):
IP := 0;
do
{
done = true;
while (IP < length)
{
if Instr[IP] is jump
if backwards
{ Target known
Encode short/long as needed }
else
{ Target unknown
if (!marked as needing long encoding) // see below
Encode short
Record location for fixup }
IP++;
}
foreach Fixup do
if Jump > short
Mark Jump location as requiring long encoding
PC := FixupLocation; // restart at instruction that needs size change
done = false;
break; // out of foreach fixup
else
encode jump
} while (!done);
На первом проходе у вас будет очень хорошее приближение к тому, какой код jmp
использовать, используя пессимистический подсчет байтов для всех инструкций перехода.
На втором проходе вы можете заполнить переходы выбранным пессимистическим кодом операции. Затем можно было переписать очень небольшое количество переходов, чтобы использовать на один или два байта меньше, только те, которые были очень близки к пороговому значению перехода 8/16 бит или 16/32 байта изначально. Поскольку все кандидаты представляют собой переходы из многих байтов, они с меньшей вероятностью окажутся в критических ситуациях цикла, поэтому вы, вероятно, обнаружите, что дальнейшие проходы мало или совсем не дают преимущества по сравнению с двухпроходным решением.