Как упростить MIPS ADD 1 / SLT / BNE в меньшее количество инструкций? - PullRequest
1 голос
/ 01 октября 2019

Как эти инструкции MIPS можно сократить до меньшего количества инструкций?

addi  $8, $3, 1
slt   $9, $2, $8
bne   $9, $0, End

Ответы [ 2 ]

1 голос
/ 01 октября 2019

Как упростить эти инструкции MIPS?

slt $9, $3, $2
beq $9, $0, End

Вот один из способов объяснить это. Вы хотите сделать:

if ( $2 < $3+1 ) goto End;

Мы конвертируем это, чтобы удалить дополнение:

if ( $2 <= $3 ) goto End;

Но у нас нет <= на MIPS, поэтому мы обращаемсяусловие и отменяют его . Это двойное отрицание отменяет, поэтому все еще представляет ту же логику. Это удаляет компонент равенства сравнения:

if ( ! ( $2 > $3 ) ) goto End;

теперь мы меняем операнды ... поскольку MIPS также не имеет >:

if ( ! ( $3 < $2 ) ) goto End;

(NB:Этот обмен операндами не отменяет условие: для этого вида обмена мы оставляем компонент равенства таким же (здесь, отсутствует) при переключении оператора, тогда как при отрицании, как на предыдущем шаге, мы переворачиваем оператор итакже измените его компонент равенства .)

Хорошей новостью является то, что мы можем выполнить это только с двумя инструкциями , поскольку отрицание можно сложить в инструкцию ветвления с помощью beq (ответвление на false) вместо bne (ответвление на true).

Фактически, если вы используете псевдоинструкцию ble, вы получите ту же последовательность из двух инструкций, что и выше.

ble $2, $3, End

Кстати, sle - плохой вариант, в зависимости от ваших критериев.

MIPS не имеет sle в качестве инструкции, это псевдо-инструкциячто делает:

sle $9, $2, $3 генерирует:

slt $9, $3, $2    # generate the opposite condition
ori $1, $0, 0x1   # generate the constant 1
sub $9, $1, $9    # generate 1 - "the opposite condition"

Как вы можете видеть, он дает несколько дополнительных инструкций для получения точного ответа 1 против 0, который мы должны получить для sle, на который выВсе равно придется добавить инструкцию ветки, так что получается 4 инструкции! (И мы могли бы перейти на false после начальной инструкции этого расширения.) Также не существует «немедленного обратного вычитания», поэтому вычитание R-типа используется с отдельно сгенерированной константой.

0 голосов
/ 01 октября 2019

Прежде всего, вы можете использовать псевдоинструкцию, например 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).

Различие важно, когда на любом входе установлен старший бит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...