Подготовка в lcc-1.28 для E2K к будущим несовместимым изменениям при работе с аргументами ассемблерных вставок, имеющих тип long double или 16-байтный GNU-vector

Вводное описание

Для процессоров elbrus-v5 и выше хранить 128-битное (16-байтное) значение на регистрах можно одним из двух способов:

  • В виде пары регистров, в каждом их которых хранится по 64 бита. Такой формат регистра называется quadro-регистр. Слово quadro может сбивать с толку. В реальности в качестве одной “единицы” хранения для E2K используется понятие “слово”, имеющее размер в 32 бита. Поэтому термин quadro-регистр для E2K означает “четыре слова”, но не “четыре регистра”. Quadro-регистр подразумевает две штуки аппаратных регистров с соседними номерами, каждый регистр условно можно считать 64-битным

  • В одном extended регистре, в котором хранятся все 128 бит значения. Такой формат регистра называется quadro-packed регистр (с тем же смыслом слова “quadro”). Quadro-packed регистр подразумевает одну штуку аппаратного регистра размером 128 бит.

Полный 128-битный регистр на процессорах elbrus-v5 и выше мы будем называть extended регистром. В то время как каждый из условно 64-битных регистров quadro-регистра мы будем называть не-extended регистром. Весь quadro-регистр тоже можно считать не-extended регистром

Для процессоров elbrus-v4 и ниже номинально тоже имелись extended регистры, но они имели размер 80 бит. Таким образом extended регистр на elbrus-v4 можно рассматривать как 64-битный не-extended регистр с дополнительным 16-битным довеском. Хранить 128-битное значение на таком регистре было нельзя, но можно было хранить 80-битное значение. Поэтому на процессорах elbrus-v4 и ниже имеются симметричные два способа хранения, но уже для 80-битного значения (а не 128-битного, как для elbrus-v5 и выше):

  • В виде пары регистров. В первом регистре хранится 64 бита, во втором - 16 бит. Такой формат регистра является quadro-регистром, просто во втором регистре используются не все биты. Формат подразумевает две штуки аппаратных регистров

  • В одном extended регистре, в котором хранятся все 80 бит значения. Такой формат регистра называется fx-регистр и подразумевает одну штуку аппаратного регистра

Для всех процессоров elbrus в рамках данной статьи понятия extended регистров и не-extended регистров можно рассматривать как одно и то же: не-extended регистр имеет размер 64 бита, extended регистр представляет собой не-extended регистр плюс довесок. Различие только в том, что для процессоров elbrus-v4 и ниже довесок имеет размер 16 бит, а для процессоров elbrus-v5 и выше - 64 бита. В остальном регистры и работу с ними можно считать одинаковыми

Quadro-packed регистры и fx-регистры в рамках данной статьи мы будем называть общим термином extended регистр. Чтобы каждый раз не писать фразы типа “для процессоров elbrus-v4 и ниже речь идёт об fx-регистре, а для процессоров elbrus-v5 и выше речь идёт о quadro-packed регистре”

Старое поведение

В компиляторах версии lcc-1.27 и ниже имелось несимметричное поведение при работе с ассемблерными вставками, которое можно продемонстрировать на следующем примере:

typedef long long __v2di __attribute__ ((__vector_size__(16)));

__int128_t i;
__float128 f;
void *p;
__v2di v;
long double ld;

void foo (void)
{
  /* Для 16-байтного целочисленного типа в качестве аргумента ассемблерной вставки
   * выделялся quadro-регистр.
   * Поведение lcc симметрично поведению gcc на прочих архитектурах,
   * где имеется поддержка 16-байтного целочисленного типа */
  asm ("" : : "r"(i));

  /* Для 16-байтного вещественного типа в качестве аргумента ассемблерной вставки
   * выделялся quadro-регистр.
   * Поведение lcc симметрично поведению gcc на прочих архитектурах,
   * где имеется поддержка 16-байтного вещественного типа */
  asm ("" : : "r"(f));

  /* В режиме -mptr128
   * Для 16-байтного указателя в качестве аргумента ассемблерной вставки
   * выделялся quadro-регистр
   * Поведение lcc симметрично поведению gcc на прочих архитектурах,
   * если бы имелись архитектуры с поддержкой 16-байтных указателей */
  asm ("" : : "r"(p));

  /* Допустимо только в режиме -march=elbrus-v5 и выше
   * Для 16-байтного векторного типа в качестве аргумента ассемблерной вставки
   * выделялся не quadro-регистр, а extended регистр */
  asm ("" : : "r"(v));

  /* Реальный битовый размер long double равен 80 битам, но с точки зрения
   * языка его размер равен 128 битам (16 байтам).
   * Для 16-байтного long double в качестве аргумента ассемблерной вставки
   * выделялся не quadro-регистр, а extended регистр */
  asm ("" : : "r"(ld));
}

Как мы видим, работа с разными типами, имеющими номинальный размер в 16 байт (128 бит), была построена несимметрично. Поведение зависело от конкретного типа аргумента, что само по себе уже противоречило общим правилам построения ассемблерных вставок в gcc. К тому же такой вариант работы предполагал, что пользователь напрямую был лишён простой возможности выбора между quadro-регистром и extended регистром. При необходимости использования конкретного типа регистра, отличного от того, какой использует компилятор, требовалось применение технических трюков

Типы long double и 16-байтный векторный тип вываливались из общего подхода работы с параметрами ассемблерных вставок - из-за их несимметричной обработки компилятором lcc. Начиная с lcc-1.28 в качестве параметров ассемблерных вставок поддержаны агрегатные типы (struct, union, class). При сохранении старого подхода ещё больше усиливалась бы несимметрия в отношении двух указанных типов, либо появилась новая несимметрия в отношении агрегатных типов

Новое поведение

Начиная с lcc-1.28 для описания аргументов ассемблерной вставки в компилятор введён E2K-зависимый constraint “x”. Теперь при работе с регистровыми constraint’ами имеется следующее поведение, которое однозначно описывается constraint’ом:

  • Constraint “r” всегда описывает не-extended регистр. Constraint “r” симметричен для всех режимов -march. Constraint “r” симметричен для всех типов. Для значений размером от 1 до 8 байт будет выделяться один 64-битный регистр. Для значений размером от 9 до 16 байт будет выделяться два 64-битных регистра, первый из которых имеет чётный номер. Значения размером 17 и более байт на текущий момент не поддерживаются, но если в будущем будут поддерживаться, то будет выделяться нужное количество 64-битных регистров, предположительно с выравниванием номера первого регистра и выравниванием количества регистров

  • Constraint “x” всегда описывает extended регистр. При использовании такого constraint значение аргумента должно иметь sizeof равным 16 байт (включая тип long double). Использование типа long double предполагает, что такое значение имеет реальный размер в 80 бит и поддерживается при любом режиме -march. Прочие типы предполагают, что значение имеет реальный размер в 128 бит и поддерживается только для режимов -march=elbrus-v5 и выше. Теоретически constraint “x” можно поддержать для любого типа размером от 1 до 10 байт во всех режимах -march и для любого типа размером от 1 до 16 байт в режимах -march=elbrus-v5 и выше. Такое поведение не противоречит логике работы gcc. Но на сегодняшний день практической необходимости в таком поведении мы не видим

Переходный период

Новое поведение компилятора противоречит старому поведению только в тех случаях, когда используется constraint “r” для типов long double и 16-байтных векторных типов. Поэтому в компиляторе начиная с версии lcc-1.28 на промежуточный период введена опция -mold-asm-constraint-r, по которой использование constraint “r” для указанных двух типов будет рассматриваться как constraint “x” с выдачей дополнительного предупреждения. Это означает повторение старого поведения плюс предупреждение о проблемном месте, которое необходимо исправить для компиляторов версии lcc-1.28 и выше. Использование опции -mold-asm-constraint-r никак не затрагивает исходники, явно использующие новый constraint “x”

В компиляторе lcc-1.28 режим -mold-asm-constraint-r включен по умолчанию. Однако такое поведение компилятора объявлено устаревшим (DEPRECATED). В будущем по умолчанию будет включен инвертированный режим -mno-old-asm-constraint-r, который уже будет не совместим со старыми исходниками. В режиме -mno-old-asm-constraint-r никаких предупреждений уже не будет выдаваться, т.к. constraint “r” в отношении типов long double и 16-байтного векторного типа будет иметь новую трактовку. При этом для возможности компиляции старых исходников по прежнему будет возможно использовать опцию -mold-asm-constraint-r (включая выдачу предупреждения), но опцию нужно будет явно указывать в строке компиляции. В совсем далёком будущем опции -mold-asm-constraint-r и -mno-old-asm-constraint-r будут удалены, и останется только режим работы с новым поведением без возможности повторения старого поведения

Если в компиляторе lcc-1.28 и выше нужно собрать программу, в которой constraint “r” используется для аргументов типа long double или 16-байтного векторного типа в новой трактовке, то придётся подавать инвертированную опцию -mno-old-asm-constraint-r. В будущем, когда инвертированный режим -mno-old-asm-constraint-r будет включен по умолчанию, необходимость в этом отпадёт