Arm Assebly для RPI3 b+ зачем делать xor в регистре для счетчика?

я пытался сделать программу, которая мерцает RPI3 b+ с Armv7 Assembly и заметил, что она не работает, используя этот код для функции задержки

delay:
    b loop

loop:
    add r10, r10, #1
    cmp r10, r4
    bne loop
    beq return

return:
    mov r10, #0
    bx lr

r10 — это регистр, используемый для счетчика, а r4 содержит, что r10 должен достичь, чтобы остановить и вернуться к основному коду. Посмотрев учебник, я обнаружил, что они выполняют операцию xor для регистра счетчика, я добавил исправление, и теперь код выглядит так.

delay:
    eor r10, r10, r10
    b loop

loop:
    add r10, r10, #1
    cmp r10, r4
    bne loop
    beq return

return:
    mov r10, #0
    bx lr

Я скомпилировал, загрузил его в rpi3, и теперь он работает, но зачем мне было добавлять эту строку, я знаю, что такое xor, но если два входа равны, он вернет точно такое же значение. В чем смысл этой операции?

Тогда вы неправильно поняли xor. Если входные данные одинаковы, он дает ноль. Это обычная практика для обнуления регистра. Поскольку вы хотите, чтобы r10 считалось с нуля, вы должны обнулить его. Вам не нужно использовать xor для этого, конечно, вы можете с таким же успехом mov r10, #0. Также b loop бесполезен (в любом случае это следующая инструкция). Непонятно, почему r10 обнуляется в конце перед возвратом, но это, вероятно, плохая идея.

Jester 18.08.2023 17:14

Обычной практикой является использование xor для обнуления регистра на x86 из-за различных особенностей и истории, которые привели к тому, что это было более эффективно, чем очевидная инструкция mov. Ничто из этого не относится к ARM, поэтому вы можете и должны использовать mov r10, #0, который более понятен и, по крайней мере, столь же эффективен, возможно, даже больше, потому что нет зависимости от ложного чтения от r10.

Nate Eldredge 18.08.2023 17:19

Чтобы добавить к просмотру кода, две условные ветки подряд не нужны и поэтому являются плохой практикой; условный переход к следующей инструкции также не имеет смысла.

Erik Eidt 18.08.2023 18:01
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
50
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

TL:DR: XOR то же самое, то же самое похоже на sub same,same, производя ноль.

Этот учебник не очень хорош, как и обнуление XOR на ARM или любой RISC ISA. Используйте его только в x86 asm (и 8080), а не в asm для других ISA, и не в языках высокого уровня.


но если два входа равны, он вернет точно такое же значение.

Нет, это будет обычное неисключающее ИЛИ. XOR дает вам биты, которые были другими. Когда оба входа одинаковы, результат равен 0.

XOR-обнуление хорошо только на x86. (См. Как лучше всего обнулить регистр в сборке x86: xor, mov или and? подробнее почему). Ни одна из этих причин не применима к ARM: mov reg, #0 имеет тот же размер в машинном коде, что и eor reg,reg,reg, поэтому не было исторической причины поддерживать EOR как «обнуляющую идиому», которая особенно характерна для современных процессоров.

(Это верно даже в коде Thumb, хотя в этом случае вы хотите movs reg, #0 для меньшей кодировки, по крайней мере, с r0-r7. r8-r14 требуется 4-байтовая кодировка Thumb2 независимо от установки флагов или нет.)

На самом деле ЦП ARM даже архитектурно не позволяет оптимизировать eor dst, same,same, чтобы сломать ложную зависимость, потому что правила упорядочения зависимостей памяти требуют EOR и других операций для переноса зависимости. (например, для использования результата нагрузки std::memory_order_consume.) Не то чтобы они стали тратить на это транзисторы и питание, поскольку машинному коду ARM нет причин использовать это в первую очередь, когда mov reg, #0 работает отлично.

Так что eor r10, r10, r10 явно хуже, чем mov r10, #0.

Никогда не используйте его, если вам не нужен 0, который зависит от старого значения R10. Если вы не знаете, что это значит, вы этого не хотите; это было бы полезно только в многопоточном коде для результата загрузки, такого как флаг data_ready, или в экспериментах с микробенчмарками для тестирования неупорядоченного планирования или задержки по сравнению с пропускной способностью путем создания постоянного значения с зависимостью данных от некоторого результата.


На x86 он сохранил байт размера машинного кода по сравнению с mov ax, 0 и 3 байта в 32-битном режиме, поэтому реальный код использовал его везде. Более поздние ЦП эволюционировали, чтобы сделать его по-прежнему эффективным даже при неупорядоченном выполнении, где в противном случае чтение старого значения регистра в качестве ввода было бы проблемой. (В отличие от mov reg, 0, который, как мы ожидаем, не будет иметь ложной зависимости даже без какой-либо специальной поддержки. mov всегда нарушает зависимость; специальный регистр xor same,same на x86 просто делает его равным в этом смысле. xor-zeroing лучше в другие способы на x86.)


Этот «учебник» явно был написан другим новичком в качестве учебного упражнения (что характерно для случайных руководств, которые вы найдете в Интернете; написать хороший учебник — большая работа).

Это не пример хорошего эффективного кода, учитывая эту ошибку (отсутствует обнуление счетчика циклов) и две бесполезные b next_instruction инструкции. Выполнение в любом случае переходит к следующей инструкции, даже если вы этого не сделаете b или beq return.

Большинство условных ветвей должны быть просто сравнением и одной ветвью, а другой путь выполнения должен быть проходным. Это что-то вроде анти-шаблона для начинающего кода — помещать другую ветку с противоположным условием одну за другой. Или сделать нижнюю часть цикла while(1) { if (cond)break } вместо просто do{}while(cond); - в вашем цикле по крайней мере бесполезная ветвь находится вне цикла. Но это цикл задержки, который в любом случае существует только для того, чтобы тратить время, поэтому на самом деле он просто тратит впустую размер кода и изменяет коэффициент задержки циклов на счет.

Если вам нужно, чтобы выполнение выполнялось куда-то еще в обоих случаях (т.е. обе возможные цели находятся после другого кода, который должен попасть в него), то вторая ветвь должна быть безусловной b. И вы никогда не должны писать ветку, которая переходит к следующей инструкции в исходном порядке, потому что выполнение все равно пойдет туда, даже если ветки не было.

Спасибо чувак. Изучение ассемблера действительно сбивает с толку после использования только языков программирования более высокого уровня.

jack07Code 19.08.2023 15:58

@ jack07Code, начните с того, что вы уже знаете из других языков, и посмотрите, как то же самое делается в ассемблере: циклы for, while; если-то/если-то-иначе; массивы и индексация; указатели; вызовы функций. Каждый из них имеет относительно прямой и простой перевод на ассемблере.

Erik Eidt 19.08.2023 17:37

@ jack07Code: В продолжение комментария Эрика см. Как удалить «шум» из вывода сборки GCC/clang? о том, как писать простые функции на C, которые компилируются именно в тот ассемблер, который вы хотите видеть.

Peter Cordes 19.08.2023 20:45

Я думаю, что eor vs mov — отвлекающий маневр. Вы очищаете r10 в начале процедуры и в конце процедуры. Согласно ABI, r10 — это регистр сохранения вызываемого абонента. Вы не можете знать, что r10 по возвращении будет равно нулю. Просто переместите mov.

Вот подпрограмма, которую можно вызвать из 'C'.

# Put count in `r0` and count down.
delay:
    ; you can add 'nop' instructions here to increase loop time.
    subs r0, r0, #1   ; subtract and set conditon codes
    bne delay         ; branch if not zero
    bx lr             ; return to caller.

mov Rx, #0 и eor Rx, Rx, Rx функционально эквивалентны, так как в «Rx» впоследствии равно нулю. Сроки, коды состояния и другие вещи могут отличаться. Но вряд ли поэтому у вас не работает задержка.

Его можно назвать от 'C' как delay(20);. Если у вас вся кодовая база на ассемблере, то вполне вероятно, что какой-то регистр где-то затерт и вам нужно показать полный пример (или дать ссылку на туториал).

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

Код Oldtimers, вероятно, лучше читать, чем любой учебник, который вы просматриваете. github.com/dwelch67 DaveSpace тоже хороший ресурс. Эта книга дешевая и очень хорошая. Все разные дейвы.

artless noise 19.08.2023 17:21

Другие вопросы по теме