Ошибка с умножением степени двойки в компиляторе GBDK
В настоящее время я разрабатываю эмулятор gameboy и для проверки правильности своего эмулятора я использую GBDK для компиляции c-программ для моего эмулятора.
Я заметил, что компилятор (как и ожидалось) оптимизирует умножения с константами, которые имеют степень двойки, выполняя вращения. Однако кажется, что он не генерирует правильное количество вращений для данной мощности.
Например, следующая очень простая программа:
#include <gb/gb.h>
unsigned char one() { return 1; }
void main()
{
unsigned char r;
// Force compiler to generate muliplication by deferring the '1'
r = one() * 32;
// Store result somewhere
*((unsigned char*)(0xFFFE)) = r;
}
Создает следующую сборку:
___main_start:
_main:
push bc
; reproduce.c 14
; genCall
call _one
ld c,e
; genLeftShift
ld a,c
rr a
rr a
rr a
and a,#0xE0
ld c,a
; reproduce.c 16
; genAssign
ld de,#0xFFFE
; genAssign (pointer)
ld a,c
ld (de),a
; genLabel
00101$:
; genEndFunction
pop bc
ret
___main_end:
.area _CODE
Что мне кажется неправильным, так как инструкция RR фактически вращается через флаг переноса, фактически превращая его в 9-битное вращение. Это означает, что должен быть дополнительный поворот для получения правильного результата вместо текущего (0x40) неправильного результата.
Визуализация:
Start: A = 00000001 Carry = 0
RR A: A = 00000000 Carry = 1
RR A: A = 10000000 Carry = 0
RR A: A = 01000000 Carry = 0 <- WRONG!
Может кто-нибудь проверить, что это действительно ошибка в компиляторе SDCC, который поставляется с GBDK? Я также заинтересован в использовании инструкций и после поворотов.
Использование последней (3-2.93) версии GBDK для окон от sourceforge.
2 ответа
Это не ошибка в вашем эмуляторе - другие эмуляторы, которые я тестировал, также дают 64
для следующего кода:
#include <stdio.h>
unsigned char one() { return 1; }
void main()
{
unsigned int r;
// Force compiler to generate multiplication by deferring the '1'
r = one() * 32;
printf("1 * 32 = %u", r);
}
Вот это на BGB (версия 1.5.1):
И это на VBA-M (версия SVN1149):
Что касается того, почему and a,#0xE0
Включено? Это на самом деле просто. Это просто гарантирует, что переполнение не испортит значение.
Во-первых, предположим, что умножение действительно работает правильно, а 1 * 32 по-прежнему равняется 32. (Я просто добавлю дополнительный RR A
).
Для 1 * 32 это выглядит так:
Start ; A = 00000001 Carry = 0
RR A ; A = 00000000 Carry = 1
RR A ; A = 10000000 Carry = 0
RR A ; A = 01000000 Carry = 0
RR A ; A = 00100000 Carry = 0
AND A,0xE0 ; A = 00100000 Carry = 0
Здесь AND не имеет никакого эффекта. Но предположим, что мы умножили на что-то, что могло бы вызвать переполнение, например 17 * 32:
Start ; A = 00010001 Carry = 0
RR A ; A = 00001000 Carry = 1
RR A ; A = 10000100 Carry = 0
RR A ; A = 01000010 Carry = 0
RR A ; A = 00100001 Carry = 1
AND A,0xE0 ; A = 00100000 Carry = 0
Без и мы получили бы 17 * 32 = 33, а не правильный ответ на 1 байт (32
). Хотя ни один из этих ответов не является истинным (544), 32
правильное значение для первого байта этого.
Я протестировал его с помощью CPCtelera (который включает SDCC 3.4.3), и результат кажется правильным. Я использовал этот код:
#include <stdio.h>
unsigned char one() { return 1; }
void main() {
unsigned char r;
r = one() * 32;
printf("1 * 32 = %d\n\r", r);
while (1);
}
И это был сгенерированный код (только соответствующая часть кода):
_main::
;src/main.c:28: r = one() * 32;
call _one
ld a,l
rrca
rrca
rrca
and a,#0xE0
Мне кажется, что вы правы, и версия SDCC, которую вы используете, неправильно использует rr
вместо rrca
,
Наконец, это вывод программы на эмуляторе WinAPE Amstrad CPC: