Запретить G CC оптимизировать циклическую запись в отображенный в память адрес - PullRequest
5 голосов
/ 29 февраля 2020

У меня есть адрес, который указывает на порт управления следующим образом (для контекста я работаю над игрой Sega / Megadrive):

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

И массив начальных значений, которые я хочу set:

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

С al oop:

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;
  }
}

Проблема в том, что g cc (по крайней мере, с O2) оптимизирует это и записывает только последнее значение в указатель *vdp_ctrl. Мне удалось обойти это, установив одно из свойств в структуре bitset на volatile, но это не похоже на интуитивное решение, и сборка, которую он генерирует в результате, очень longy.

Итак, мой вопрос состоит из двух частей:

  1. Каков «правильный» способ предложить g cc, что мой указатель vdp_ctrl должен принимать несколько записей через некоторое время. Я собираюсь часто использовать этот шаблон (запись данных const / stati c для адресов control / data в al oop), а пометка случайных полей как volatile кажется не интуитивно понятным.
  2. «Стандарт» «качество», которое я использую для сгенерированного asm, выглядит следующим образом (не сгенерированный g cc):
    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)

Примечание : Похоже, размер моего массива определяет, будет ли он оптимизирован или нет. Когда я сделал только два элемента, он не оптимизировался.

1 Ответ

1 голос
/ 29 февраля 2020

В этом коде вы должны использовать целые числа точного размера. Я настоятельно советую также упаковать структуры и объединения.

#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 лучше иметь макрос вместо реального объекта. Это может не иметь никакого значения в этом тривиальном коде, но будет, если код станет более сложным.

...