防止GCC优化对内存映射地址的循环写操作

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

我有一个指向控制端口的地址,就像这样(出于上下文原因,我正在开发Sega / Megadrive游戏):

volatile u32 * vdp_ctrl = (u32 *) 0x00C00004;

以及我要设置的一组初始值:

const u8 initial_vdp_vals[24] = {
  0x20,
  0x74,
  0x30,
  ..etc
};

带有循环:

typedef struct {
  u16 upper;
  u8 reg;
  u8 val;
} bitset;

typedef union {
  bitset b;
  u32 as_u32;
} u_bitset;

static inline void init_vdp() {
  u_bitset cmd = {{0x00008000}};
  for(int i = 0; i < 24; i++) {
     cmd.b.val = initial_vdp_vals[i];
     *vdp_ctrl = cmd.as_u32;
     cmd.b.reg += 1;
  }
}

问题在于,gcc(至少带有O2)对此进行了优化,并且仅将最后一个值写入*vdp_ctrl指针。我已经设法通过将bitset结构中的一个属性设置为volatile来解决此问题,但这似乎不是一个直观的解决方案,因此生成的程序集为[[very冗长。

所以我的问题有两个:

    向gcc建议我的vdp_ctrl指针需要接受一段时间的多次写入的“正确”方法是什么?我将经常使用这种模式(将const /静态数据写入循环中的控件/数据地址),并将随机字段标记为volatile似乎不太直观。
  1. 我用于生成的asm的“质量标准”看起来如下(不是生成的gcc):
  • move.l #initial_vdp_vals, a0 move.l #24, d0 move.l #0x00008000, d1 .copy: move.b (a0)+, d1 move.w d1, 0x00C00004 add.w #0x0100, d1 dbra d0, .copy
    哪个很好,很简洁。因此,我的另一个问题可能是(作为一个完整的C新手):是否有更好的C解决方案来使我更接近上面的程序集?说实话,我什至不确定我的代码是否正确,因为我只知道首先要解决该循环优化问题,因为我知道这将是一个持续不断的问题。

    产生我问题的可运行示例:

    volatile unsigned long * vdp_ctrl = (unsigned long *) 0x00C00004; const unsigned char initial_vdp_vals[24] = { 0x20, 0x74, 0x30, 0x40, 0x05, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x81, 0x34, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; typedef struct { unsigned int upper; unsigned char reg; unsigned char val; } bitset; typedef union { bitset b; unsigned long as_u32; } u_bitset; static inline void init_vdp() { u_bitset cmd = {{0x00008000}}; for(int i = 0; i < 24; i++) { cmd.b.val = initial_vdp_vals[i]; *vdp_ctrl = cmd.as_u32; } } void init() { init_vdp(); for(;;) { } }

    使用m68k-linux-gnu-gcc -ffreestanding -O2 -S -c test.c -o test.s。生成以下内容:

    #NO_APP .file "test.c" .text .align 2 .globl init .type init, @function init: link.w %fp,#0 move.l vdp_ctrl,%a0 moveq #24,%d0 move.l #32768,%d1 .L2: move.l %d1,(%a0) subq.l #1,%d0 jne .L2 .L3: jra .L3 .size init, .-init .globl initial_vdp_vals .section .rodata .type initial_vdp_vals, @object .size initial_vdp_vals, 24 initial_vdp_vals: .byte 32 .byte 116 .byte 48 .byte 64 .byte 5 .byte 112 .byte 0 .byte 0 .byte 0 .byte 0 .byte 0 .byte 8 .byte -127 .byte 52 .byte 0 .byte 0 .byte 1 .byte 0 .byte 0 .byte 0 .byte 0 .byte 0 .byte 0 .byte 0 .globl vdp_ctrl .data .align 2 .type vdp_ctrl, @object .size vdp_ctrl, 4 vdp_ctrl: .long 12582916 .ident "GCC: (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0" .section .note.GNU-stack,"",@progbits

    gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)
    

    Note:确实出现了阵列的大小,这决定了阵列是否被优化。当我仅制作两个元素时,它并未进行优化。

  • c for-loop gcc volatile 68000
    1个回答
    1
    投票
    在这种代码中,您应该使用精确的大小整数。我强烈建议也打包结构和联合。

    #include <stdint.h> #define vdp_ctrl ((volatile uint32_t *) 0x00C00004) const unsigned char initial_vdp_vals[24] = { 0x20, 0x74, 0x30, 0x40, 0x05, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x81, 0x34, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; typedef struct { uint16_t upper; uint8_t reg; uint8_t val; } __attribute__((packed)) bitset; typedef union { bitset b; uint32_t as_u32; } __attribute__((packed)) u_bitset ; static inline void init_vdp() { u_bitset cmd = {.b.upper = 0x00008000}; for(int i = 0; i < 24; i++) { cmd.b.val = initial_vdp_vals[i]; *vdp_ctrl = cmd.as_u32; } } void init() { init_vdp(); for(;;) { } }

    它会生成您需要的代码。

    IMO最好使用宏而不是真实对象。这琐碎的代码可能没有什么区别,但是如果代码变得更复杂,它将有所不同。

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