为什么 gcc -O1 优化会破坏在 Gameboy Advance ROM 的循环中修改 VRAM 的代码?

问题描述 投票:0回答:1

我正在开发一个简单的 Gameboy Advance ROM,并试图理解为什么以下代码适用于 gcc

-O0
选项,但使用
-O1
或以上选项会崩溃(白色模拟器屏幕):

int main () {
    // Set video mode 3 and background 2
    *(unsigned int*)0x04000000 = 0x0403;

    int x;
    for(x = 0; x < 1; x++){
        // Set a single pixel at position (120, 80) in VRAM to red
        ((unsigned short*)0x06000000)[120+80*240] = 0x001F;
    };

    while(1);

    return 0;
}

循环显然是不必要的,但我用它来创建崩溃行为的最小示例。如果没有循环,无论优化级别如何,代码都可以正常工作。通过循环,它在

-O1
及更高版本上崩溃,但在
-O0
上工作。

无论我是否在循环体中实际使用

x
(例如,使用
x
来计算像素位置),都会发生相同的行为。据我所知,每当我尝试在循环中进行这种类型的直接内存修改时,我都会在更高的优化级别上遇到损坏。

这是怎么回事?什么优化破坏了代码?这是否表明我做事的方式存在问题?感谢您的帮助!

更多细节:

  • 我正在使用 devkitpro/devkitarm
  • 我使用 NanoBoyAdvance 作为模拟器
  • 我在 Ubuntu 上运行
  • 完整命令:
arm-none-eabi-gcc -MMD -MP -MF /path/to/myfile.d  -g -Wall -O1 -mcpu=arm7tdmi -mtune=arm7tdmi -mthumb -mthumb-interwork -iquote /path/to/include -I/opt/devkitpro/libgba/include -I/path/to/build -c /path/to/source/myfile.c -o myfile.o
c gcc arm devkitpro game-boy-advance
1个回答
0
投票

写入内存映射硬件始终需要通过指向

volatile
类型的指针来完成。否则,编译器可以自由地假设它们没有副作用,并且可以以意想不到的方式进行优化。在这里,这既适用于设置视频模式的商店,也适用于放置像素的商店。

(实际上,当写入视频内存时,您可能可以允许一些优化;例如,您不关心所有写入是否完全按照指定的顺序发生,只要它们最终都发生即可。在这种情况下,您通常可以摆脱非

volatile
存储以及某种特定于编译器的内存屏障,就好像它可以观察它们一样,例如gcc中的
asm("" : : : "memory");
)。

在这种情况下发生的事情是编译器优化了第一个存储,它设置了视频模式:https://godbolt.org/z/4dn9dhh6j

main:
        ldr     r3, .L3
        mov     r2, #31
        strh    r2, [r3, #240]  @ movhi
.L2:
        b       .L2
.L3:
        .word   100701696

我不是 gcc 优化器细节方面的专家,但我的假设是编译器认为在存储和无限循环之间的任何地方都没有从内存中读取数据。因此,它假设程序中的任何代码都无法读回您写入地址

0x04000000
的值,因为无限循环后面的任何内容(包括
main
之外的代码)都无法执行。如果该值永远无法被读取,并且写入本身没有副作用(假设因为它不是
volatile
),那么它是一个死存储,并且首先不需要写入它。

但是,按照这种逻辑,人们会认为它会优化了放置像素的存储,但事实并非如此。所以也许我还缺少其他事情。

© www.soinside.com 2019 - 2024. All rights reserved.