Я изучаю инструкции MIPS, и когда я тестирую инструкции I-типа, которые должны подписать, расширяют немедленно, я смущен следующими результатами (все они выполняются в MARS):
ori $s1, $s2, 0xfd10
, MARS дает базовую инструкцию на ассемблере ori $17, $18, 0x0000fd10
. Это ожидаемо, так как ori
должен обнулить 16-битное непосредственное значение. Если мы изменим только функцию ori
на andi
, то есть строку исходного кода andi $s1, $s2, 0xfd10
, MARS выдаст почти ту же базовую инструкцию ассемблера andi $17, $18, 0x0000fd10
. Однако, в отличие от ori
, andi
следует использовать расширение знака. Таким образом, основная инструкция на ассемблере должна быть andi $17, $18, 0xfffffd10
.andi
также следует использовать нулевое расширение! Пожалуйста, игнорируйте первый вопрос.
slti rt, rs, imm
, например, slti $s1, $s2, 0x8000
, MARS отказывается выполнять строку и выдает сообщение об ошибке "0x8000": operand is out of range
. Я не вижу причин, по которым непосредственное находится вне досягаемости. Если я немного изменю непосредственное значение, скажем, на slti $s1, $s2, 0x7fff
, оно сработает, и немедленное значение будет расширено до 0x00007fff
. Я ожидаю, что 0x8000
следует расширить до 0xffff8000
. Что-то не так с моим пониманием?Значения в исходном коде asm представляют собой фактические числовые значения, с которыми вы хотите работать, а не только битовые шаблоны, которые должны быть закодированы в инструкции.
0x8000
- это не то же самое число, что и 0xffff8000
, поэтому ассемблер не дает вам изменить ваше значение расширением знака. Если вы хотите, чтобы машинный код конечной инструкции кодировал значение 0xffff8000
, вы должны написать 0xffff8000
в исходном коде ассемблера для инструкций, которые расширяют свои непосредственные действия знаком.
В нашей системе записи позиционных значений для чисел существует бесконечное количество неявных старших 0
цифр слева от явных цифр. Итак, 0x8000
— это то же число, что и 0x00008000
, и это число, которое ассемблер пытается представить как 16-битное непосредственное расширение со знаком.
Вы подходите к этому с точки зрения того, как кодируются инструкции I-типа. Но ассемблеры предназначены для обработки деталей кодирования за вас. Это часть смысла его использования. Допустим, вы пишете addiu $t0, $t1, -123
, а ассемблер кодирует -123
как 16-битное непосредственное расширение со знаком.
Скажем, вы пишете ori $t0, $t0, -256
, чтобы установить все биты выше младшего байта. Но ассемблер отклоняет это, потому что это не может быть закодировано как прямое расширение с нулевым расширением для ori
, вместо того, чтобы молча оставлять старшие 16 битов неустановленными, как 0x0000ff00
. Таким образом, вам не нужно запоминать, как каждая инструкция относится к своему непосредственному; ассемблер проверит это за вас. Это преднамеренная функция и хороший дизайн.
Особенно, если у вас есть большая программа, которая определяет некоторые константы времени сборки, а затем использует их различными способами: если настройка одного из этих значений приводит к невозможности кодирования инструкции, вы хотите знать об этом, а не молчаливо получать неверные результаты.
(И поскольку я использовал десятичные примеры, запись чисел в виде шестнадцатеричных числовых литералов ничего не меняет в отношении того, как ассемблер должен их обрабатывать.)
Однако, в отличие от ori, в andi следует использовать расширение знака.
Нет, в MIPS все 3 побитовые логические инструкции (ori
/andi
/xori
) расширяют до нуля свои непосредственные. (Sign-extend был бы более полезен в большем количестве случаев для AND, разрешая маски только с несколькими нулями в младших битах, но это не то, как разработан MIPS. Хотя это сделало бы усечение ровно до 16 бит дороже.)
Документация типа https://ablconnect.harvard.edu/files/ablconnect/files/mips_instruction_set.pdf подтверждает andi
нулевое расширение. Я не проверял официальные документы MIPS, но эта информация широко распространена в Интернете; вы также можете проверить, чтобы компиляторы использовали его таким образом для реализации uint16_t
или чего-то еще.
Также инструкция andi vs. addi в MIPS с отрицательной непосредственной константой (покрывает MARS с включенными расширенными псевдоинструкциями, поэтому он создаст полное 32-битное значение в другом регистре, если вы используете andi
со значением, которое не кодируется как 16-битный с нулевым расширением (немедленный)
Большое спасибо. Что касается ori
, меня ввела в заблуждение конспект лекции с опечаткой. Я проверил различные таблицы mips, такие как inst.eecs.berkeley.edu//~cs61c/fa11/MIPS_Green_Sheet.pdf, и подтвердил, что ori
также использует нулевое расширение. Я до сих пор не очень понимаю часть slti
. slti $s1, $s2, 0xffff8000
также отказывается от MARS. И какое отрицательное число будет дополнено знаком slti
? Если я использую sltiu $s1, $s2, 0x8000
, ошибка та же. Я много искал, но не нашел объяснения процесса расширения знака числа, например 0x8000
вместо slti
или sltiu
.
@codekiwi: я только что попробовал в MARS 4.5, и slti $s1, $s2, 0xffff8000
собрался, как и ожидалось, в 0x2a518000 slti $17, $18, 0xffff8000
. Это работает, потому что 0xffff8000
можно представить как 16-битное целое число с расширенным знаком.
Спасибо. Я снова открыл свой MARS 4.5, и slti $s1, $s2, 0xffff8000
заработало. Я понимаю, что 0xffff8000
можно представить как 16-битное целое число с расширенным знаком. Мой вопрос: поскольку 0x1000
может быть автоматически расширен ассемблером до 0x00001000
, почему 0x8000
не может? Почему 0xffff8000
нужно вводить в источник явно вручную? Извините, если это глупый вопрос.
Потому что 0x1000 и 0x00001000 — это одно и то же число, а 0x8000 и 0xffff8000 — нет.
@codekiwi: Когда я пишу 0xffff
как числовую литеральную константу в C, ассемблере или чем-то еще, предполагается, что неявные старшие биты равны 0, т. е. это то же число, что и 0x0000ffff
, а не -1
. Вы пишете значение, которое хотите использовать в инструкции, а не кодировку, которую хотите использовать для инструкции MIPS I-типа. Вот что пытается объяснить первая часть моего ответа.
Большое спасибо, Питер. Я сравниваю slti
с lw
, addi
и начинаю понимать «значение, которое вы хотите, чтобы инструкция использовала», и «кодировку, которую вы хотите для инструкции MIPS I-типа». В этом листе данных просто говорится, что некоторые инструкции требуют расширения знака, и не дается никаких объяснений процесса, выполняемого ассемблером. В то время как учебник объяснил, что такое расширение знака, но не сочетает его с практикой. Спасибо за ваше терпение, помогая мне.
@codekiwi: Лист данных, конечно, документирует, как ЦП обрабатывает машинный код, не более того. Понимание философии дизайна исходного языка типичных ассемблеров - это отдельная и совершенно другая вещь, и да, обычно не прописанная в документации. Тот факт, что числа являются числами, является своего рода неявным. Разные ассемблеры могут делать разные выборы, например. следует ли предупреждать и усекать или выдавать ошибку, когда число не помещается в непосредственное.
№1 похоже на ошибку. Причина № 2 - избежать путаницы. Вы написали
0x8000
, значит, вы хотели0x8000
, а не0xffff8000
.