Ошибка с умножением степени двойки в компиляторе 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):

BGB version 1.5.1: 1 * 32 = 64

И это на VBA-M (версия SVN1149):

VBA-M version SVN1149: 1 * 32 = 64


Что касается того, почему 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:

Выход на WinAPE 2.0. Альфа 18

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