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

gre*_*rep 5 c gcc for-loop 68000 volatile

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

volatile u32 * vdp_ctrl = (u32 *) 0x00C00004;
Run Code Online (Sandbox Code Playgroud)

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

const u8 initial_vdp_vals[24] = {
  0x20,
  0x74,
  0x30,
  ..etc
};
Run Code Online (Sandbox Code Playgroud)

有一个循环:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

问题是 gcc(至少使用 O2)优化了它并且只将最后一个值写入*vdp_ctrl指针。我已经成功通过设置属性之一来解决这个bitset结构来volatile,但是这似乎并不像一个直观的解决方案,并装配它产生的结果是非常漫长的。

所以我的问题有两个:

  1. 向 gcc 建议我的vdp_ctrl指针需要随着时间的推移接受多次写入的“正确”方法是什么。我将经常使用这种模式(将常量/静态数据写入循环中的控制/数据地址),并将随机字段标记为 volatile 似乎不直观。
  2. 我用于生成的 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
Run Code Online (Sandbox Code Playgroud)

这是非常好的和简洁的。所以我的另一个问题可能是(作为一个完整的 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(;;) {
  }
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)
gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)
Run Code Online (Sandbox Code Playgroud)

注意:看起来我的数组的大小决定了它是否得到优化。当我只制作两个元素时,它没有优化。

P__*_*J__ 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(;;) {
  }
}
Run Code Online (Sandbox Code Playgroud)

它会生成您需要的代码。

IMO 最好用宏而不是真实的物体。对于这个简单的代码来说,这可能不会产生任何影响,但如果代码变得更复杂,就会产生任何影响。

  • 不要使用包装的。这是完全没有必要的,它告诉编译器该对象不需要对齐,这可能会使其使用更大/更差的代码来访问并扰乱易失性访问的原子性。 (4认同)