Прежде всего, вы можете использовать псевдоинструкцию, например blt $9, $2, End
, чтобы записать ТА и BNEZ в одной строке исходного текста. Но я сомневаюсь, что вы это имеете в виду;остальная часть ответа касается только уменьшения количества аппаратных инструкций MIPS.
Если не указано иное, оптимизация безопасна / разрешена, только если она дает такое же поведение для каждые возможный ввод. Для оптимизации asm -> asm, вам нужно знать, какие части поведения желательны, а какие - просто детали реализации , которые не нужно сохранять.
В этом случае ядумаю, мы должны предположить, что создание $8 = $3+1
не является частью видимого / желаемого поведения. Я думаю, что смысл состоит в том, чтобы просто выполнить или выполнить менее 3 инструкций, независимо от того, какое временное действие мы создаем или нет. (например, subu $8, $2, $3
/ bgez
может быть вариантом.)
Вы можете оптимизировать / упростить это, только если измените поведение для $3 = INT_MAX
углового случая . (Или если у вас есть какие-то гарантии относительно возможных диапазонов ваших входных данных).
В оригинале MIPS addi
перехватывает переполнение со знаком . (Именно поэтому он обычно никогда не используется; addiu
переносит, как вы ожидаете, и в остальном идентичен.) Повышение этого исключения для $3 = 0x7FFFFFFF
, а не для любого другого случая, в значительной степени требует использования addi
снемедленное 1
. Это часть поведения исходной последовательности, и ничто в вопросе не говорит нам, что мы можем ослабить это.
Если вы использовали addiu
для реализацииjump if ($2 < $3+1)
с дополнением 2, тогда INT_MAX
все еще является особым случаем. Если x <= 0x7FFFFFFF
истинно для всех x, но x < -0x8000000
ложно для всех x
(32-битное дополнение 2, что реализует MIPS slt
). x < y+1
эквивалентно x <= y
, но только если y+1
не переносится.
Например, в C, целочисленное переполнение со знаком равно UB, но без знакового переноса. И правила для преобразования неподписанного в подписанное четко определены как уменьшающие по модулю таким образом, что это означает, что машины дополнения 2 (такие как MIPS) могут просто принимать битовый шаблон без знака как подписанный, то есть приведение является бесплатным и просто каламбуром.
// C equivalent to asm using `addiu`. C for MIPS uses 32-bit unsigned and 2's complement int
void foo_wrapping(int x, int y)
{
unsigned tmp = y;
tmp++; // wraps without UB
int wrapped_yp1 = tmp;
// y+1 with 2's complement wraparound, without using -fwrapv
if (! (x < wrapped_yp1))
sink = 0;
}
GCC5.4 для MIPS компилирует его следующим образом ( Godbolt ). Номера регистров отличаются от вашего вопроса, но схема идентична. (добавьте результат в правой части SLT, затем BNEZ в этом SLT-результате.)
(add
против addu
в некотором роде совпадает с переполнением знака C, являющимся UB, без знака)просто обтекание, но обратите внимание, что компиляторы C используют addu
/ addiu
даже для подписанного добавления, потому что UB разрешено выполнять только перенос: C не требует его обнаружения и сбоя,Весь смысл в том, что он позволяет оптимизатору предположить, что этого вообще не происходит.)
# gcc5.4 -O3
foo_wrapping(int, int):
addiu $5,$5,1
slt $4,$4,$5
bne $4,$0,$L7
... a store that it conditionally jumps over, then jr $ra
clang9.0 также выдает тот же код. Компиляторы, не находящие оптимизации, не являются доказательством того, что ни одна из них не возможна, но это хорошая проверка, что мои рассуждения выше верны.
Использование простого y+1
и компиляция с помощью gcc или clang -fwrapv
(для создания переполнения со знакомчетко определенное как обтекание дополнением 2) также дает тот же результат. И затем использование if (y == 0x7FFFFFFF) __builtin_unreachable();
работает правильно, обещая компилятору, что y!=INT_MAX
, позволяя оптимизировать компилятор. Компиляторы пропускают в версии, используя unsigned.
Если мы исключим INT_MAX
в качестве возможного ввода (т. Е. Нас не волнует случай, когда исходный код захвачен), то мы можем представить операциюв C как x < y+1
для подписанных int
переменных. В C переполнение со знаком является неопределенным поведением, поэтому оптимизирующим компиляторам разрешается предполагать, что y+1
не переполняется, и, таким образом, y!=INT_MAX
.
// This C is not equivalent to your asm.
// It doesn't trap on y==0x7FFFFFFF, and it doesn't necessarily wrap like addiu
int sink;
void foo_nooverflow(int x, int y) {
if (x < y+1) {
sink = 0; // a store can't be done branchlessly
}
}
## gcc5.4 -O3
foo_nooverflow(int, int):
slt $4,$5,$4
beq $4,$0,$L4
Таким образом, gcc / clang преобразуетусловие , как предлагает ответ Эрика Эйдта , сравнение в другом порядке и замена bne
на beq
как способ реализации jump if ($2 <= $3)
, поскольку MIPS имеет ограниченный выбор sXX
инструкций.
Версия, использующая unsigned x, y
, также должна addiu
отдельно от сравнения, а затем использует другую инструкцию сравнения: sltu
. slt
- это сравнение со знаком (дополнение 2).
Различие важно, когда на любом входе установлен старший бит.