Пытаюсь понять ассемблерный код 65c816 — какова цель такого порядка инструкций?
Я новичок в сборке и пытаюсь понять, что делает этот фрагмент кода. Вот эталон Super NES 65c816, который я использую.
01: $A4917E AD E5 05 LDA $05E5 [$A4:05E5]
02: $A49181 29 3F 00 AND #$003F
03: $A49184 AA TAX
04: $A49185 AD E5 05 LDA $05E5 [$A4:05E5]
05: $A49188 89 02 00 BIT #$0002
06: $A4918B D0 05 BNE $05 [$9192]
07: $A4918D 8A TXA
08: $A4918E 49 FF FF EOR #$FFFF
09: $A49191 AA TAX
10: $A49192 8A TXA
11: $A49193 18 CLC
12: $A49194 6D 7A 0F ADC $0F7A [$A4:0F7A]
13: $A49197 85 12 STA $12 [$00:0012]
14: $A49199 AD E5 05 LDA $05E5 [$A4:05E5]
15: $A4919C 29 00 1F AND #$1F00
16: $A4919F EB XBA
17: $A491A0 85 14 STA $14 [$00:0014]
Я уже знаю, что $05E5 — это место хранения игрового генератора случайных чисел (ГСЧ). Это 16-битное значение. Я пытаюсь выяснить, какие математические операции пытается выполнить игра с этим случайным числом.
Вот более тривиальный пример для начала:
$86AE1C AD E5 05 LDA $05E5 [$86:05E5]
$86AE1F 29 01 00 AND #$0001
$86AE22 F0 05 BEQ $05 [$AE29]
Здесь явно просто используется случайное число для подбрасывания монеты — самый высокий бит энтропии равен 0 или 1 — чтобы решить, переходить или нет.
Возвращаясь к исходному примеру, позвольте мне сказать, что (я думаю) я понимаю, по частям:
01: $A4917E AD E5 05 LDA $05E5 [$A4:05E5]
02: $A49181 29 3F 00 AND #$003F
03: $A49184 AA TAX
Это загрузка случайного числа в аккумулятор, скажем, и его И0000000000111111
, уступая0000000000100010
в аккумуляторе, который затем переносится в регистр X.
04: $A49185 AD E5 05 LDA $05E5 [$A4:05E5]
Затем он загружает то же самое случайное число в аккумулятор — наверное, потому, что мы потеряли его во время операции И? (Случайное число генерируется в другом месте, выполняясь примерно раз в 1/60 секунды. Но здесь оно гарантированно будет таким же.)
05: $A49188 89 02 00 BIT #$0002
06: $A4918B D0 05 BNE $05 [$9192]
Вот где это становится нечетким для меня. Я прочитал несколько ресурсов в Интернете (хотя и не конкретно для 65816), в которых говорилось, что BIT похож на AND, но без изменения ни одного операнда. Но, похоже, есть некоторые нюансы, связанные с флагами.
Продолжая мой пример, правильно ли я считаю, что
BIT #$0002
на значение аккумулятора11010101001100010
будет 1, потому что второй младший бит равен 1?Правильно ли я считаю, что в результате проверки битов значение флага z будет установлено на 0, что является полной противоположностью предыдущему результату?
Правильно ли я считаю, что тогда он разветвится (к инструкциям по адресу $05), поскольку Z=0?
Хорошо, если я понял эти основы, то вот мои настоящие вопросы:
Почему разработчики (которые в то время все оптимизировали ) не поменяли местами строки 01-03 на 04-06?То есть, если есть шанс, что вы в конечном итоге перейдете к какому-то совершенно другому фрагменту кода, почему бы не протестировать и не выполнить сначала переход, и только если нет ветвления, выполнить операцию И? (Подождите, возможно ли, что регистр X может использоваться везде, куда переходит код?)
Может ли кто-нибудь помочь мне понять следующие несколько инструкций? В частности, какой смысл выполнять TAX, а затем TXA? Разве это не просто замена, а затем замена значений?
Извините, это много вопросов в одном, но, надеюсь, все в порядке, потому что я имею в виду один и тот же набор инструкций. Спасибо заранее за любую помощь.
PS Вот страница о BIT в учебнике, который у меня есть о 65816, на случай, если это поможет.
Я не мог понять абзац, начинающийся со слов «BIT обычно используется непосредственно перед инструкцией условного перехода:» , хотя это кажется уместным...
3 ответа
Отвечая на один из вопросов об этих строках
06: $A4918B D0 05 BNE $05 [$9192]
07: $A4918D 8A TXA
08: $A4918E 49 FF FF EOR #$FFFF
09: $A49191 AA TAX
10: $A49192 8A TXA
Почему следует ?
Это из-за ветки в строке 6, которая, если ее взять, будет выполняться из строки 10.
The TAX
показанное соответствует предыдущему TXA
(не следующий) как в
07: $A4918D 8A TXA
08: $A4918E 49 FF FF EOR #$FFFF
09: $A49191 AA TAX
Некоторые пробелы или метки в коде сделали бы эту «фразировку» более понятной, хотя ветвь действительно указывает адрес назначения.
Вопросы 1-3 о флаге: вы правы, говоря, чтоZ
флаг устанавливается , если тестируемый результат0
.
- Правильно ли я считаю, что тогда он разветвится (к инструкциям по адресу $05), поскольку Z=0?
Да, но$05
это не адрес, это смещение со знаком (из инструкции, которая должна была следовать, потому чтоPC
счетчик программ (он жеIP
указатель инструкции) уже был бы продвинут к моменту принятия решения).
Может ли кто-нибудь помочь мне понять следующие несколько инструкций? В частности, какой смысл выполнять TAX, а затем TXA? Разве это не просто замена, а затем замена значений?
- Это вообще не обмен ! Эти
Txx
инструкции копируют значение из одного регистра в другой, оставляя оба регистра равными друг другу. - Инструкция в строке 6 вызвала два разных пути выполнения, которые в конечном итоге должны были снова сойтись. Программист мог бы использовать для этого безусловный переход, но в конечном итоге этого не сделал, потому что предпочел размер кода скорости выполнения:
The BNE
инструкция либо проваливается по первому пути, либо переходит ко второму пути. В конце первого пути,BRA
прыгает в точку схода. Это решение требует на 1 байт больше (3 вместо 2), но первый путь выполняется на 1 цикл меньше (3 вместо 4, если нет пересечения границы страницы, тогда это также будет 4 цикла):
06: $A4918B D0 06 BNE $06 [$9193]
*** first path
07: $A4918D 8A TXA
08: $A4918E 49 FF FF EOR #$FFFF
09: $A49191 .. 01 BRA $01 [$9194]
*** second path
10: $A49193 8A TXA
*** come together point
11: $A49194 18 CLC
The TAX
Инструкция используется, чтобы избежать использования более дорогостоящей инструкции перехода. Он устанавливает регистр X, чтобы код мог провалиться вTXA
инструкция по второму пути. Это решение требует на 1 байт меньше (2 вместо 3), но первый путь выполняется на 1 такт больше (4 вместо 3):
06: $A4918B D0 05 BNE $05 [$9192]
*** first path
07: $A4918D 8A TXA
08: $A4918E 49 FF FF EOR #$FFFF
09: $A49191 AA TAX
*** second path
10: $A49192 8A TXA
*** come together point
11: $A49193 18 CLC
то же самое случайное число в аккумулятор... Но здесь гарантированно будет то же самое.
Почему этот код такой сложный?
Исходя из того, что случайное число из 05E5 остается прежним, и зная, чтоBIT
не изменяет ни одного из своих операндов, и что бит, который мы проверяем, находится среди битов, оставшихся отAND
, мы можем написать этот код, даже не касаясь X:
01: $A4917E AD E5 05 LDA $05E5 [$A4:05E5]
02: $A49181 29 3F 00 AND #$003F
05: $A49184 89 02 00 BIT #$0002
06: $A49187 D0 03 BNE $03 [$918C]
08: $A49189 49 FF FF EOR #$FFFF
11: $A4918C 18 CLC
Что касается этого вопроса и ответа Weather Vane, я думаю, в зависимости от значения RNG (0x05E5) вы можете догадаться, почему это делается именно так:
- Если значение равно 0x3F (важные биты взяты здесь для демонстрационных целей)
01: $A4917E AD E5 05 LDA $05E5 [$A4:05E5] ; A=0x3F, X=?
02: $A49181 29 3F 00 AND #$003F ; A=0x3F, X=?, Y=?, flags: Z=0
03: $A49184 AA TAX ; A=0x3F X=0x3F
04: $A49185 AD E5 05 LDA $05E5 [$A4:05E5] ; A=0x3F, X=0x3F
05: $A49188 89 02 00 BIT #$0002; A=0x3F, X=0x3F, flags: Z=0
06: $A4918B D0 05 BNE $05 [$9192] ; Branch !
10: $A49192 8A TXA A=0x3F, X=0x3F
11: $A49193 18 CLC ; Continue code
- Если значение В
RNG & 0x3F == 0 && RNG & 0x2 == 0
, например 0x40:
01: $A4917E AD E5 05 LDA $05E5 [$A4:05E5] ; A=0x40
02: $A49181 29 3F 00 AND #$003F ; Z=0
03: $A49184 AA TAX ; A=0x40, X=0x40
04: $A49185 AD E5 05 LDA $05E5 [$A4:05E5] ; A=0x40
05: $A49188 89 02 00 BIT #$0002 ; A=0x40 Z=1
06: $A4918B D0 05 BNE $05 [$9192] ; No branching here
07: $A4918D 8A TXA ; A=0x40, X=0x40
08: $A4918E 49 FF FF EOR #$FFFF ; A=0xBF, X=0x40 , assuming 8 bits mode but I'll talk about that later
09: $A49191 AA TAX ; A=0xBF, X=0xBF
10: $A49192 8A TXA ; A=0xBF, X=0xBF
11: $A49193 18 CLC ; c=0
Глядя на это, да, возникает вопрос: почему разработчик не поменял местами и не сделал TAX / TXA?
Мое лучшее предположение по первому вопросу такое же, как у флюгера, потому что ветка. Я думаю, что у него есть еще одна цель: очистить флаги.
До сих пор мы не говорили о 8/16-битном режиме (флаг m) или других флагах. Я думаю, что это точка CLC после того, как также очистить флаги.
При использовании инструкций передачи, в зависимости от флага m (который определяет режим ширины памяти), вы копируете флаги, некоторые очищаете.
В моем источнике http://6502.org/tutorials/65c816opcodes.html#6.10.1 они приводят следующий пример:
Пример: если аккумулятор равен $1234, регистр X равен $ABCD, а флаг m равен 1, то после TXA:
- аккумулятор будет $12CD
- флаг n будет равен 1 (поскольку фактически был передан только $CD)
- флаг z будет равен 0
Я предполагаю, что это только сбрасывает флаги до известного состояния.
О вашем последнем вопросе:
Я не мог понять начало абзаца: «БИТ обычно используется непосредственно перед инструкцией условного перехода:», хотя это кажется уместным...
Возможность выполнить побитовую проверку без изменения ваших значений позволяет выполнять дополнительные инструкции. В противном случае вам снова понадобится LDA, как это сделано в строке 4. Вот как я бы это использовал.