Важность ассемблера

В C++11 была введена очень важная и полезная вещь: строго-типизированный enum. Я не буду перечислять все полезности, в сети полно статей на эту тему. Тем более, на дворе 2018 год, пора бы уже всем этим пользоваться.

Одна из хороших вещей в enum classах это возможность выбора типа, используемого для хранения данных (зачастую называется enum base). Это выглядит следующим образом:

enum class Foo : std::uint32_t {
    a = 0,
    b,
    c,
};

Это важное дополнениие для некоторых компиляторов, которые не совсем следуют стандарту. Я столкнулся с этой проблемой в ARM Compiler 5. У нас есть специализированная навигационная микросхема с двумя ядрами NeuroMatrix (NMC) и ядром ARM1176JZF-S под названием BBP2 или СНП-ВП. NMC это достаточно хороший цифровой сигнальный процессор (DSP), но с особенностью в виде 32-битного байта.

The byte is a unit of digital information that most commonly consists of eight bits. … it is the smallest addressable unit of memory in many computer architectures.Wiki.

Минимально адресуемая единица в NMC — 32 бита (4 байта в ARM-адресации). Это означает, что если мы собирается записать 8 бит во внутреннюю память, микросхема зависнет, что приводит к очень неприятным багам, которые трудно выловить.

Простой пример: нам нужно осуществить межпроцессорное взаимодействие через разделяемую память. Одним из лучших способов является размещения объекта структуры в некотором месте с фиксированным адресом, чтобы оба процессора имели к ней доступ. Например, рассмотрим функцию, которая будет менять состояние энумератора:

enum class Foo {
    a = 0,
    b,
    c,
};

struct Bar {
    Foo foo = Foo::a;
} bar;

void Meow() {
    bar.foo = Foo::b;
}

Почему-то это не работает. Причину можно понять после беглого взгляда на сгенерированный ассемблер:

_Z4Meowv PROC
    MOV      r0,#1
    LDR      r1,|L0.52|
    STRB     r0,[r1,#0]  ; bar // <-------
    BX       lr
    ENDP

Упс, инструкция STRB. Если вы не знакомы с ассемблером ARM, STRB означает запись байта (doc). Как я и сказал раньше, программа из-за этого упадёт.

Давайте поменяем тип энумератора, в соответствии с кодом в самом начале статьи:

_Z4Meowv PROC
    MOV      r0,#1
    LDR      r1,|L0.52|
    STR      r0,[r1,#0]  ; bar // <-------
    BX       lr
    ENDP

Ура, больше не падает! Однако, что будет, если мы (по ряду причин) застряли на pre-C++11 компиляторе с простыми энумераторами? Насколько я могу судить, есть только один выход: ввести заглушку со значением, которое расширит диапазон значений до 32-битного целого числа:

enum OldFoo {
    a = 0,
    b,
    c,
    dummy = INT_MAX
};

Кстати, это может быть просто баг в ARM Compiler 5, поскольку стандарт довольно чётко говорит по этому поводу:

For a scoped enumeration type, the underlying type is int if it is not explicit ly specified (ISO/ IEC 14882 :2017(E), 10.2.5)

Также следует отметить, что другие копиляторы не страдают от этой проблемы (число 123 используется для большей наглядности):

x86-64 GCC trunk:

_Z4Meowv:
    mov DWORD PTR bar[rip], 123
    nop
    ret

x86-64 clang trunk:

Meow(): # @Meow()
    push rbp
    mov rbp, rsp
    mov dword ptr [bar], 123
    pop rbp
    ret

x86-64 icc 18.0.0:

Meow():
    push rbp #13.13
    mov rbp, rsp #13.13
    mov DWORD PTR bar[rip], 123 #14.2
    leave #15.1
    ret #15.1

Ссылка на compiler explorer тут.

Я надеюсь, что мне удалось убедить вас, что есть ряд багов, которые можно с лёгкостью заметить, прочитав сгенерированный ассемблер.


© 2022. All rights reserved.

Powered by Hydejack v9.1.6