ARM четность младших 8 бит регистра - PullRequest
1 голос
/ 06 мая 2020

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

        NAME main
        PUBLIC main
        SECTION .text: CODE (2)
        THUMB

main    
        LDR R4, =0x0097         ; R4 = 97 in hex 
        BL SUBROUTINE           ; Go to Subroutine

STOP    B STOP

SUBROUTINE
        MOV R1, #1              ; Initialize R1 to 1        
        MOV R2, #0              ; Initialize R2 to 0        
        MOV R0, #0              ; Initialize R0 to 0        
        PUSH {R4}               

LOOP
        CMP R0, #8              ; Bits counter
        BEQ DONE                ; Go to DONE R0 = 8
        ADD R0, R0, #1          ; Calculates the bits
        AND R3, R4, R1          ; Checks if R3 = R4
        CMP R3, #1              ; Comparing result with 1
        BEQ ONE                 ; Jump to ONE
        LSR R4, R4, #1          ; Right shift by 1
        B LOOP

ONE
        ADD R6, R6, #1          ; Saving #1 in R6
        LSR R4, R4, #1          ; Right shift by 1
        B LOOP

RETURN0
        MOV R2, #0              
        POP {R4}
        B STOP

RETURN1
        MOV R2, #1
        POP {R4}
        B STOP

DONE
        CMP R6, #2
        BEQ RETURN0
        CMP R6, #4
        BEQ RETURN0
        CMP R6, #6
        BEQ RETURN0
        CMP R8, #8
        BEQ RETURN0
        B RETURN1

        END

Задача заключается в следующем: подпрограмма имеет входной параметр в регистре R4 и выдает возвращаемое значение в регистре R2. Подпрограмма проверяет четность 8 младших битов входного параметра. Если четность четная, возвращается значение 0, если четность нечетная, возвращается значение 1. Четность означает, что количество единиц четное, а нечетная четность количества единиц нечетная.

Заранее спасибо

Ответы [ 2 ]

3 голосов
/ 06 мая 2020

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

Я писал этот ответ, исходя из предположения, что вы программируете в режиме ARM. Однако большая часть советов применима и к режиму Thumb. Я полагаю, вы не можете использовать инструкции Thumb 2. c рекомендации для большого пальца отмечены наклонным шрифтом.

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

Прежде всего, вы используете эту последовательность, чтобы изолировать младший значащий бит R4, чтобы затем проверить, не равен ли он нулю:

        ADD R0, R0, #1          ; Calculates the bits
        AND R3, R4, R1          ; Checks if R3 = R4
        CMP R3, #1              ; Comparing result with 1
        BEQ ONE                 ; Jump to ONE

Это можно сделать более эффективно. Во-первых, обратите внимание, что вы можете использовать немедленно с инструкцией AND, поэтому нет необходимости хранить 1 в регистре только для этого:

        AND   R3, R4, #1

next, вместо сравнения результата побитового AND с #1, вы можете указать процессору, чтобы он установил флаги непосредственно из результата инструкции AND. Это устанавливает нулевой флаг, если результат равен нулю (и, возможно, некоторые другие флаги, не слишком заботьтесь об этом), поэтому вы можете сразу перейти к результату.

        ANDS  R3, R4, #1        ; check if least significant bit set in R4
        BNE   ONE               ; jump to ONE if it is

Теперь это ANDS выполняет работу, но без необходимости записывает свой результат в R3. Нам это там особо не нужно. Беглый взгляд на ссылку на набор инструкций говорит нам, что TST делает то же самое, что и ANDS, но отбрасывает результат, устанавливая только флаги. Это именно то, что мы хотим.

        TST   R4, #1            ; check if least signficant bit set in R4
        BNE   ONE               ; jump to ONE if it is

Теперь следующее, что мы можем сделать, это избавиться от этой условной ветки. Единственная разница между кодом в ветке ONE состоит в том, что он увеличивает R6. Вместо условного перехода мы можем просто использовать функцию ARM условное выполнение для выполнения инструкции ADD только при установленном нулевом флаге:

        TST   R4, #1             ; check if least significant bit set in R4
        ADDNE R6, R6, #1         ; increment R6 if it is

Это делает код весьма интересным. немного эффективнее! Мы можем улучшить его еще больше, объединив TST с инструкцией LSR. Видите ли, если мы укажем LSR установить флаги, он установит флаг переноса на последний бит, который был сдвинут. Это именно то, что нам интересно! Таким образом, мы можем просто сделать

        LSRS  R4, R4, #1         ; shift R4 to the right and set flags
        ADDCS R6, R6, #1         ; increment R6 if a 1 was shifted out

Обратите внимание, что на других архитектурах, где условное выполнение недоступно, вы можете достичь эффекта, аналогичного ADDCS R6, R6, #1, с помощью инструкции добавления с переносом:

        ADC   R6, R6, #0         ; add 1 to R6 if carry is set

Это то, что я бы сделал и в режиме большого пальца. Поскольку в режиме большого пальца нет немедленного операнда ADC, вы должны сохранить один регистр равным нулю.

        MOVS  R1, #0
        ...
        LSRS  R4, R4, #1
        ADCS  R6, R1, #0         ; add carry to R6

Помимо установки флага переноса, LSRS также устанавливает нулевой флаг если результат нулевой. Таким образом, мы можем избавиться от счетчика l oop, если просто будем выполнять итерацию до тех пор, пока все биты в R4 не будут сдвинуты, что сэкономит нам регистр и набор инструкций. Обратите внимание, что это может не дать правильных результатов, если какие-либо дополнительные биты (кроме как минимум 8 битов, которые вы проверяете) установлены в R4, поэтому вы можете сначала замаскировать их с помощью AND R4, R4, #0xff. А вот код:

LOOP:   LSRS  R4, R4, #1         ; shift R4 to the right and set flags
        ADDCS R6, R6, #1         ; increment R6 if a 1 was shifted out
        BNE   LOOP               ; loop until R4 is 0.

К сожалению, все инструкции большого пальца устанавливают флаги, поэтому вы не можете выполнить эту оптимизацию.

Аналогичным образом можно оптимизировать код в часть DONE: по сути, вы просто проверяете, является ли R6 четным или нечетным, и возвращаете 1, если оно нечетное, или 0, если оно четное. Вы можете заменить весь каскад переходов одним тестом:

        TST   R6, #1             ; set the zero flag if R6 is even
        BEQ   RETURN0            ; return 0 if even
        B     RETURN1            ; otherwise return 1

Но тогда поймите, что это в основном то же самое, что и возврат младшего бита R6, поэтому вы можете заменить весь этот код by

        AND   R0, R6, #1         ; set R0 to 1 if R6 is odd, 0 if R6 is even
        POP   {R4}
        B     STOP

Это немного короче, не так ли?

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

        MOVS R0, #0              ; parity accumulator
        SUBS R1, R0, #2          ; mask (clear in bit 0, 1 everywhere else)
LOOP:   LSRS R4, R4, #1          ; shift out one bit from R4 and set flags
        ADCS R0, R0, R1          ; add that bit to R0
        CMP  R4, #0              ; are we done?
        BNE  LOOP                ; loop until we are
        BICS R0, R1              ; isolate parity

Результат можно найти в R0.

Теперь для некоторых улучшений алгоритмов c: ваш код делает свое дело, но действительно довольно медленно, так как выполняет одну итерацию на di git. Более быстрый подход - объединить биты вместе с помощью инструкций XOR. Это позволяет нам вычислять четность всего за 3 шага вместо 8, как это делает ваш код:

        LSR   R3, R6, #4        ; keep a copy of R6 shifted by 4 places
        EOR   R6, R6, R3        ; and xor it into R6
        LSR   R3, R6, #2
        EOR   R6, R6, R3        ; same but shifted by 2 places
        LSR   R3, R6, #1
        EOR   R6, R6, R3        ; same but shifted by 1 place
        AND   R0, R6, #1        ; isolate parity

Тот же код может быть написан в режиме большого пальца, но вам могут потребоваться дополнительные перемещения данных в между.

Это можно улучшить, используя сдвинутые операнды, еще одну особенность ARM c:

        EOR   R6, R6, R6, LSR #4 ; xor R6 with R6 shifted right 4 places
        EOR   R6, R6, R6, LSR #2 ; xor R6 with R6 shifted right 2 places
        EOR   R6, R6, R6, LSR #1 ; xor R6 with R6 shifted right 1 place
        AND   R0, R6, #1         ; isolate parity

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

1 голос
/ 06 мая 2020

в следующий раз используйте CODE (фигурные скобки в редакторе) вместо экрана печати (например, вы не можете копировать и вставлять из prtscn). Я никогда не использовал язык ассемблера ARM, но я бы использовал такой подход:

  1. И-верхние 24 бита (если вы не уверены, что они всегда будут нулями) вашего ввода
  2. Переместить ввод в любой георадар (скажем, R5)
  3. Переместить R5 в любой другой георадар (скажем, R6)
  4. И-вывести все биты R6, кроме наименее значащего
  5. Проверить R6 на ноль (если не ноль, увеличить счетчик (GPR))
  6. Логический сдвиг R5 вправо
  7. Go до 3. (повторить восемь раз)
  8. У вас на счетчике единицы

Это был бы мой подход. Но я не уверен, что это лучший вариант. Это должно быть проще. Если бы у ARM был какой-либо способ поворота посредством переноса, это было бы еще проще (вы пропустите увеличение счетчика, если бит переноса равен нулю).

...