Просматривал код в отношении цикла.
loopinner ....
SUBS R2,R2,#1 ; j--
BGT loopinner ;in this case, loop should continue when j>1
В этом случае я не уверен, как BGT снова переходит к loopinner. Разве мне не нужно указывать, что это больше? Поскольку SUBS вызывает флаги, скажем, если j-- становится значением 1. Как ветвь узнает, какое значение больше?
Из условий ARM вы можете легко обнаружить, что инструкция проверяет флаги состояния Z, N и V и переходит, когда Z=0 и N=V. Поскольку он проверяет флаг состояния V, а не флаг состояния C, это явно предназначено как подписанный тест. ( Для меня это означает, что это бесполезно для управления беззнаковым циклом - к вашему сведению. )
Я написал это не так давно, с достаточным количеством информации, чтобы понять, что происходит. Но я могу обобщить это здесь.
Давайте использовать более простые 4-битные слова, где всего 16 символов:
Word Signed Subtrahend
0000 0 1111
0001 1 1110
0010 2 1101
0011 3 1100
0100 4 1011
0101 5 1010
0110 6 1001
0111 7 1000
1000 -8 0111
1001 -7 0110
1010 -6 0101
1011 -5 0100
1100 -4 0011
1101 -3 0010
1110 -2 0001
1111 -1 0000
Выше, третий столбец — это то, что фактически использует АЛУ при вычитании этого значения. Он просто инвертирует каждый бит перед добавлением. (АЛУ никогда ничего не вычитает. Оно даже не знает, как это сделать.) Итак, инструкция SUB фактически выполняет сложение, используя при сложении вычитаемую форму значения. (Если вы хотите понять семантику битов состояния, очень важно, чтобы вы овладели этой концепцией, поскольку она поможет вам, когда в противном случае вы бы запутались.)
Отпечатайте его себе на лоб -
А ЦП ТОЛЬКО ДОБАВЛЯЕТ. ВЫЧИТАТЬ НЕ МОЖЕТ .
Если вы когда-нибудь почувствуете искушение пойти по пути первоцвета, полагая, что любая инструкция вычитания на самом деле вычитает, и это включает в себя все инструкции сравнения, которые устанавливают биты состояния, но не изменяют значения регистров, просто пните себя очень сильно, очень быстро. Этого не происходит.
А ЦП ТОЛЬКО ДОБАВЛЯЕТ. ВЫЧИТАТЬ НЕ МОЖЕТ .
Все должно быть приведено к семантике сложения. Все.
SUBS R2, R2, #1 в этой 4-битной вселенной, которую я только что создал, также добавили бы 1110 плюс перенос 1. Всего 16 вариантов:
Actual Operation Operation Result Operation Comparison
R2 SUBS OP Z N V C ALU Semantics Semantics Z=0 & N=V?
0000 + 1110 + 1 0 1 0 0 1111 0 - 1 = -1 0 > 1 ? False
0001 + 1110 + 1 1 0 0 1 0000 1 - 1 = 0 1 > 1 ? False
0010 + 1110 + 1 0 0 0 1 0001 2 - 1 = 1 2 > 1 ? True
0011 + 1110 + 1 0 0 0 1 0010 3 - 1 = 2 3 > 1 ? True
0100 + 1110 + 1 0 0 0 1 0011 4 - 1 = 3 4 > 1 ? True
0101 + 1110 + 1 0 0 0 1 0100 5 - 1 = 4 5 > 1 ? True
0110 + 1110 + 1 0 0 0 1 0101 6 - 1 = 5 6 > 1 ? True
0111 + 1110 + 1 0 0 0 1 0110 7 - 1 = 6 7 > 1 ? True
1000 + 1110 + 1 0 0 1 0 0111 -8 - 1 = -9 E -8 > 1 ? False
1001 + 1110 + 1 0 1 0 1 1000 -7 - 1 = -8 -7 > 1 ? False
1010 + 1110 + 1 0 1 0 1 1001 -6 - 1 = -7 -6 > 1 ? False
1011 + 1110 + 1 0 1 0 1 1010 -5 - 1 = -6 -5 > 1 ? False
1100 + 1110 + 1 0 1 0 1 1011 -4 - 1 = -5 -4 > 1 ? False
1101 + 1110 + 1 0 1 0 1 1100 -3 - 1 = -4 -3 > 1 ? False
1110 + 1110 + 1 0 1 0 1 1101 -2 - 1 = -3 -2 > 1 ? False
1111 + 1110 + 1 0 1 0 1 1110 -1 - 1 = -2 -1 > 1 ? False
В разделе «Результат операции» у меня есть столбец для ALU . Поле ALU — это то , что возвращается в R2 после завершения инструкции SUBS. ( Флаг состояния V генерируется XOR переноса следующего за старшим значащим бита во время операции и самого бита переноса.) Также обратите внимание, что есть единственный случай, отмеченный E, когда произошло переполнение со знаком . .
Теперь вы можете легко понять, почему инструкция BGT применяет эти конкретные биты состояния именно так, как она это делает. По общему признанию, это использует 4-битные слова. Но точно такая же идея применима к гораздо более широким размерам слов без каких-либо изменений.
Оглядываясь назад на таблицу, вы можете видеть, что условие истинно тогда и только тогда, когда перед вычитанием R2 было равно 2 или больше , а не 1 или 0 или меньше.
Ваш вопрос:
Разве мне не нужно указывать, что это больше? Поскольку SUBS вызывает флаги, скажем, если j-- становится значением 1. Как ветвь узнает, какое значение больше?
Начнем со следующей таблицы из Справочного руководства по архитектуре ARMv6-M , стр. A6-99:
Условие GT описывается как « Подписано больше, чем ». Причина, по которой в документации не указана константа, заключается в том, что этот тест выполняется после некоторой предыдущей инструкции. Эта предыдущая инструкция определяет контекст. Но без этого контекста все, что можно сказать, это общий знак > .
Итак, если предыдущей инструкцией была CMP:
Тогда контекстом будет сравнение двух значений со знаком, и инструкция BGT будет означать «ветвь, когда знаковый операнд 1 больше, чем знаковый операнд 2».
Но в вашем случае с «SUBS R2, R2, # 1» контекст меняется, и инструкция BGT будет означать «ветвь, в то время как подписанный R2 все еще остается больше 0».
Сама инструкция условного перехода фактически не знает , какой была предыдущая инструкция. Он также даже не знает, какие регистры задействованы. Эти знания остаются у человека (или компилятора), который генерирует поток инструкций. Таким образом, инструкция ветвления на самом деле не имеет фиксированного постоянного значения и не имеет регистра для сравнения. Это полностью зависит от того, что более ранние инструкции делали с битами состояния. Он просто проверяет полученный статус, а затем делает то, что делает. Вам решать, знать контекст и правильно его использовать.
(Кстати, комментарий к исходному коду может вводить в заблуждение или быть неверным.)
Эллиот возражает (см. обсуждение ниже) без доказательств. Он пишет: «Я мог бы также утверждать, что ЦП может только вычитать». Он может привести этот аргумент, но он чисто академический. На самом деле дело в том, что процессоры не вычитают. Они добавляют.
Так что, хотя это отчасти и мой ответ, предоставляющий четкие, недвусмысленные доказательства в поддержку, чтобы даже Эллиот мог понять ситуацию на местах, сегодня это также отличный переход. Поэтому я очень рад возможности, которую Эллиот предоставил мне для расширения дискуссии.
Мой первый процессор был собран из 7400 деталей, которые я построил и успешно завершил в 1974 году. К моему удивлению, появились газетные репортеры и написали об этом статью. Это мой первый опыт. С тех пор я профессионально работал в Intel, занимаясь тестированием чипсета для чипсета BX, и, поскольку это имело отношение к преподаванию этого предмета, я вел классы компьютерной архитектуры в качестве адъюнкт-профессора в Портлендском государственном университете в 1990-х годах, в классах было примерно 65-75 студентов. Это крупнейший 4-летний университет в штате Орегон.
Я чувствую двусмысленность (выражая двойственное отношение к тому, как могут быть выполнены вычисления) о том, как процессоры генерируют свои биты состояния и как они вычисляют, только приводит студентов к ненужной неопределенности, путанице и трудностям, на исправление которых могут уйти часы, недели, месяцы, а иногда даже годы. Точно так же, как преподавание теоретико-групповой абстрактной алгебры до того, как познакомить с основами, могло бы сбить с толку большинство студентов-первокурсников, так же и преподавание академических абстракций о том, как компьютеры могут что-то делать. Больше студентов пострадает, чем поможет.
Простая истина заключается в том, что декодирование инструкции выдает ADD, даже когда текст инструкции (в конце концов, это просто текст — это не то, что на самом деле происходит) говорит SUB. Декодирование по-прежнему выдает ADD. Он просто изменяет некоторые детали операнда по пути.
Точно так же, как и в случае с процессором ARM, приведенная выше теория — это все, что вам нужно, чтобы понять, как все устроено на самом деле.
Пожалуйста, не путайте себя! Компьютеры доп. Они не вычитают. Они просто немного возятся, чтобы это выглядело так, как будто они вычитают.
Хорошо это или плохо, важно понимать, что на самом деле делает компьютер, чтобы понимать определенные биты состояния; что они делают и почему они это делают. Другого пути нет. Вышеупомянутая теоретическая модель - это то, как все работает в современных процессорах, и как правильно работать и понимать биты состояния. Есть веская причина, почему все так , как есть.
Я надеюсь, что эти детали, приведенные выше, и те, что я напишу ниже, будут полезны. Любая ошибка, связанная с сообщением здесь, лежит на мне, и я с радостью постараюсь исправить, исправить и улучшить этот документ, где смогу.
Чтобы продолжить, я буду использовать Справочное руководство по архитектуре ARMv6-M в качестве справочного материала.
Начнем со страницы A6-187 (зарегистрировать регистр):
Здесь вы можете видеть, что они четко документируют это поведение:
AddWithCarry(R[n], NOT(shifted), '1')
Это сложение с инвертированным операндом 2 (вычитаемое) и переносом, установленным на «1». Так же, как я писал выше, бывает. (Просто так это делается.)
В случае расширений из нескольких слов перейдите на страницу A6-173 и найдите SBCS:
Здесь обратите внимание, что они снова используют дополнение:
AddWithCarry(R[n], NOT(shifted), APSR.C)
Вместо жестко запрограммированной «1» переноса, как для инструкции SUBS, теперь используется последнее сохраненное значение переноса. В этом случае обычно ожидается, что это будет выполнение предыдущей инструкции SUBS (или SBCS).
Для операций с несколькими словами каждый начинает с SUBS (или ADDS), а затем продолжает процесс с последующими SBCS (или ADCS), которые используют выполнение более ранних инструкций для поддержки операции с несколькими словами.
В сложении из нескольких слов этот перенос можно рассматривать как перенос , которым он и является. «1» означает, что перенос произошел и его необходимо устранить. «0» означает, что переноса не произошло.
В случае вычитания из нескольких слов этот перенос лучше рассматривать как перевернутое заимствование из . «1» означает, что не было необходимости заимствовать слова более высокого порядка. «0» указывает на необходимость заимствования. Поскольку инструкция SUBS всегда устанавливает это значение равным «1», это означает, что заимствования нет (результат вычитания требует «приращения», чтобы компенсировать инвертированный операнд 2). Но для инструкции SBCS, если APSR.C является « 0", то никакого "приращения" не происходит, и это то же самое, что и заимствование (поскольку требуется приращение, если заимствования нет).
Инструкция ADCS, найденная на странице A6-106, но не показанная здесь, также использует выполнение предыдущих инструкций. Он не инвертирует значение переноса или иным образом не делает что-то странное или необычное только потому, что это инструкция ADCS. Она делает то же самое, что и инструкция SBCS, за исключением одной незначительной детали — инструкция SBCS инвертирует операнд 2, а ADCS — нет. Вот и все.
Это один из действительно крутых аспектов работы этих деталей. Требуется совсем немного дополнительной логики, чтобы превратить сложение в вычитание и/или сложение из нескольких слов в вычитание из нескольких слов.
И, наконец, чтобы завершить историю, см. стр. A2-35:
В соответствии с моим описанием того, как все работает на самом деле, выше.
Очень приятно видеть, как все это работает. Стоит некоторое время поиграть с различными знаковыми и беззнаковыми значениями и вручную установить и использовать флаги состояния. Это действительно углубляет эти идеи. И они очень хорошие!
Все вышеизложенное касается понимания битов состояния, того, как они генерируются и почему они генерируются именно так, как они есть. Если вы сосредоточитесь на том, что на самом деле происходит в процессоре, остальное просто выпадет как необходимые последствия, и тогда это очень легко понять.
А процессор только добавляет. Он не может вычесть.
SUBS
как AddWithCarry(R[n], NOT(imm32), '1')
(я могу расширить это, если это будет полезно), что охватывает этот ответ.В вашем примере кода SUB
инструкция имеет S
суффикс, это означает, что подинструкция будет устанавливать флаги условий, которые будут BGT
оцениваться. Чтобы ветка была принята, Z
флаг должен быть равен 0, а N
флаг должен быть равенV
В документации по руке четко указано, что GT является знаковым больше чем, он будет разветвляться, когда Z==0,N==V.
Когда r2 = 2. Помните из начальной школы, что x - y = x + (-y), и с первого дня (или вскоре после этого) в компьютерной инженерии/науке/любом дополнительном отрицании до двух инвертируется и добавляется единица, поэтому x - y = x + (~y) + 1. Это экономит логику и позволяет выполнять вычитание.
1 add one
0010
+ 1110 invert
==========
четырех бит более чем достаточно, чтобы увидеть, что происходит, результат такой же, как и 32 бита.
11101
0010
+ 1110
==========
0001
Таким образом, N = 0 и Z = 0 из результата. Перенос и перенос мсбита одинаковы, поэтому V = 0 (исключающее ИЛИ переноса и переноса мсбита также может быть выполнено путем проверки старших битов операндов и результата).
Нам нужны Z == 0 и N == V, чтобы выполнить ветвление, и они есть, поэтому ветвление происходит.
Вы обнаружите, что это относится к положительным числам, так как это знаковое больше, чем, если вы хотите, чтобы беззнаковое было больше, чем тогда, используйте bcs/bhs, логика работает так же, как она просто оптимизируется для использования только выполнения (это также можно увидеть, если вы смотрите на сгенерированный table jonk или создаете его сами)
Когда г2 = 1
11111
0001
+ 1110
==========
0000
Z = 1, N = 0, V = 0
N == V, но Z != 0, поэтому ветвления не происходит.
Эллиот Алдерсон
Мип
Питер Беннет
джкарон
Старожил
придурок
Мип