`` continue`` прерывает размещение меток

Это прекрасно работает:

#include <stdio.h>

int main(){
    volatile int abort_counter = 0;
    volatile int i = 0;
    while (i < 100000000) {
        __asm__ ("xbegin ABORT");
        i++;
        __asm__ ("xend");
        __asm__ ("ABORT:");
        ++abort_counter;
    }

    printf("%d\n", i);
    printf("nof abort-retries: %d\n",abort_counter-i);
    return 0;
}

Однако то, что я изначально написал, было

#include <stdio.h>

int main(){
    volatile int abort_counter = 0;
    volatile int i = 0;
    while (i < 100000000) {
        __asm__ ("xbegin ABORT");
        i++;
        __asm__ ("xend");
        continue;
        __asm__ ("ABORT:");
        ++abort_counter;
    }

    printf("%d\n", i);
    printf("nof abort-retries: %d\n",abort_counter);
    return 0;
}

но это привело к

/tmp/cchmn6a6.o: In function `main':
rtm_simple.c:(.text+0x1a): undefined reference to `ABORT'
collect2: error: ld returned 1 exit status

Почему?

(Скомпилировано с gcc rtm_simple.c -o rtm_simple.)

Ответ 1

Возможно, вы сможете обмануть его:

        continue;
        reachable:
        __asm__ ("ABORT:");
        ++abort_counter;
    }

    printf("%d\n", i);
    printf("nof abort-retries: %d\n",abort_counter);
    if (abort_counter < 0) goto reachable;
    return 0;
}

goto с меткой сообщает gcc, что код доступен, а abort_counter является volatile, чтобы gcc не смог оптимизировать goto.

Ответ 2

Причина, по которой вы получаете ошибку в этом коде:

    __asm__ ("xbegin ABORT");
    i++;
    __asm__ ("xend");
    continue;
    __asm__ ("ABORT:");
    ++abort_counter;

Это потому, что компилятор видел все после инструкции continue до конца блока (цикл while) как мертвый код. GCC не понимает, что делает конкретный блок asm, поэтому он не знает, что метка ABORT использовалась в __asm__ ("xbegin ABORT");. Исключив мертвый код, цель перехода была устранена, и когда компоновщик попытался разрешить метку, она исчезла (undefined).


В качестве альтернативы другому ответу - по GCC 4.5 (все еще не поддерживается в CLANG) вы можете использовать расширенную сборку с asm goto утверждение:

Перейти к ярлыкам

asm goto позволяет ассемблеру перейти на одну или несколько меток С. Раздел GotoLabels в инструкции asm goto содержит список разделенных запятыми всех меток C, к которым может перейти код ассемблера. GCC предполагает, что выполнение asm попадает в следующий оператор (если это не так, рассмотрите возможность использования __builtin_unreachable intrinsic после оператора asm). Оптимизация asm goto может быть улучшена с использованием атрибутов горячих и холодных меток (см. Атрибуты ярлыков).

Код мог быть написан следующим образом:

while (i < 100000000) {
    __asm__ goto("xbegin %l0"
                 : /* no outputs  */
                 : /* no inputs   */
                 : "eax"   /* EAX clobbered with status if an abort were to occur */
                 : ABORT); /* List of C Goto labels used */
    i++;
    __asm__ ("xend");
    continue;
ABORT:
    ++abort_counter;
}

Поскольку компилятор теперь знает, что встроенная сборка может использовать метку ABORT как цель перехода, она не может просто ее оптимизировать. Кроме того, используя этот метод, нам не нужно размещать метку ABORT внутри блока сборки, ее можно определить с помощью обычной метки C., которая является желательной.

Быть разборчивым с приведенным выше кодом: хотя __asm__ ("xend"); является volatile, потому что это базовый оператор asm, компилятор может изменить его порядок и поместить его перед i++, и это не то, что вы хотите. Вы можете использовать фиктивное ограничение, которое заставляет компилятор думать, что он имеет зависимость от значения в переменной i с чем-то вроде:

__asm__ ("xend" :: "rm"(i));

Это гарантирует, что i++; будет размещен перед этим блоком сборки, так как компилятор теперь подумает, что наш блок asm зависит от значения в i. документация GCC имеет это, чтобы сказать:

Обратите внимание, что даже летучая инструкция asm может перемещаться относительно другого кода, включая команды перехода. [snip] Чтобы заставить его работать, вам нужно добавить искусственную зависимость для asm, ссылающегося на переменную в коде, который вам не нужен.


Существует еще одна альтернатива, которая должна работать на GCC/ICC/CLANG, и это должно переработать логику. Вы можете увеличивать abort_counter внутри шаблона сборки, если транзакция прерывается. Вы передадите его как ограничение ввода и вывода. Вы также можете использовать локальные метки GCC для определения уникальных меток:

Локальные метки

Локальные метки отличаются от локальных символов. Локальные метки помогают компиляторам и программистам временно использовать имена. Они создают символы, которые гарантированно уникальны во всей области входного исходного кода и на которые можно ссылаться простой записью. Чтобы определить локальную метку, напишите метку формы "N: (где N представляет любое неотрицательное целое число). Чтобы ссылаться на самое последнее предыдущее определение этого ярлыка, напишите 'Nb, используя тот же номер, что и при определении метки. Чтобы обратиться к следующему определению локального ярлыка, напишите 'Nf." B означает "назад", а "f означает" вперед".

Код цикла может выглядеть так:

while (i < 100000000) {
    __asm__ __volatile__ ("xbegin 1f" : "+rm"(i) ::
                          : "eax");   
                          /* EAX is a clobber since aborted transaction will set status */
                          /* 1f is the local label 1: in the second asm block below */
                          /* The "+rm"(i) constraint is a false dependency to ensure 
                             this asm block will always appear before the i++ statement */
    i++;
    __asm__ __volatile__ ("xend\n\t"
             "jmp 2f\n"   /* jump to end of asm block, didn't abort */
             "1:\n\t"     /* This is the abort label that xbegin points at */
             "incl %0\n"  /* Increment the abort counter */
             "2:"         /* Label for the bottom of the asm block */
             : "+rm"(abort_counter)
             : "rm"(i));   /* The "rm"(i) constraint is a false dependency to ensure 
                              this asm block will always appear after the i++ statement */
}

Если ваш компилятор поддерживает его (GCC 4.8.x +), используйте GCC транзакционные свойства. Это помогает полностью исключить использование встроенной сборки и является одним из меньших векторов, которые могут ошибиться в вашем коде.