.. _work_platform:

Работа с платформой
===================

Основные принципы
-----------------

Система программирования и все сборочные инструменты ориентированы на максимальную совместимость с существующей экосистемой.
Во многих случаях программа для сборки и корректной работы вообще не потребует какой-либо адаптации.
В случае правок зачастую они сводятся к тому, что поведение архитектуры описывается тем же кодом,
что используется для :program:`gcc` и x86_64. В этом случае достаточно распознать архитектуру.

При нативной сборке можно указывать :program:`gcc` и :program:`g++` в качестве компиляторов C и C++.
Это синонимы для компилятора :program:`lcc` в режимах C и C++.

Этапы сборки open source программ те же, что и для других архитектур:

* конфигурация (:program:`./configure`, :program:`cmake`, :program:`mvn`, :program:`scons` ...)
* компиляция (пример - :program:`make`)
* установка (пример - :program:`make install`)

Распознать архитектуру
~~~~~~~~~~~~~~~~~~~~~~

Если пакет собирается с помощью :program:`autotools`, вам потребуется вариант файлов
:file:`config.guess` и :file:`config.sub`, в которых есть поддержка e2k.
Вы можете взять их здесь:

* :file:`/usr/share/automake/config.sub`
* :file:`/usr/share/automake/config.guess`

Замените :file:`config.guess` и :file:`config.sub` из ваших исходников на экземпляры выше.

Для платформы Эльбрус предусмотрен макрос языка C:

.. code-block:: c

  __e2k__

Компилятор lcc взводит свой макрос (для архитектур Эльбрус и Спарк):

.. code-block:: c

  __LCC__

Примеры использования:

.. code-block:: c

  #if (defined __LCC__) && (! defined __OPTIMIZE__)
    #define MY_MACROS MY_MACROS_LCC
  #endif

.. code-block:: c

  #ifdef __e2k__
    #include "implementation_elbrus.h"
  #endif

Макрос ``__LCC__`` хранит значение мажорной версии компилятора.
Те или иные действия можно задавать только для определённых версий.

.. code-block:: c

  #if (defined __LCC__) && (__LCC__ <= 123)
    # error "Not supported for lcc-1.23 or earlier."
  #endif

Справочные данные
~~~~~~~~~~~~~~~~~~~~~~

Платформа поддерживает 64-битную и 32-битную адресацию. Основной режим системного ПО 64 бита.

Организация байтов — little endian.

Локальный стек в процедурах растёт от больших адресов к меньшим.

Размеры данных подробно описаны в разделе :ref:`reference_api`.

..
 Доступные системные вызовы перечислены в разделе :ref:`reference_linux_syscalls`.


Демонстрация ассемблера
-----------------------

Основные операции
~~~~~~~~~~~~~~~~~~~~~~

Рассмотрим код с простыми числовыми расчётами, который сможет продемонстрировать
основные операции в ассемблере.

Пример 1. арифметические операции

.. code-block:: c
  :linenos:

  int f(int a, int b, int c)
  {
        int s1, s2, s4;
        double s3;
        s1 = a + b;
        s2 = b - c;
        s3 = s1 / s2;
        s4 = s3 * s1;
        return (int) (s1 * s4);
  }



Для просмотра ассемблера подадим компилятору опцию :option:`-S`:

.. code-block:: console

  gcc -O3 -S t.c

Листинг 1. Ассемблер примера 1:

::

    .text
    .global f
    .type   f, #function
    .align  8
    f:
    {
      setwd wsz = 0x4, nfx = 0x1
      return        %ctpr3; ipd 2
      subs,0        %r1, %r2, %g16
      adds,3        %r0, %r1, %g17
    }
    {
      nop 2
      istofd,3      %g17, %g18
    }
    {
      nop 7
      sdivs,5       %g17, %g16, %g16
    }
    {
      nop 2
    }
    {
      nop 3
      istofd,3      %g16, %g16
    }
    {
      nop 3
      fmuld,3       %g16, %g18, %g16
    }
    {
      nop 3
      fdtoistr,3    %g16, %g16
    }
    {
      nop 5
      muls,3        %g17, %g16, %g16
    }
    {
      ct    %ctpr3
      sxt,3 0x2, %g16, %r0
    }


Обратим внимание, что каждая широкая команда в ассемблере помещена в пару фигурных скобок.

:term:`Широкая команда`
  — набор операций, которые запускаются процессором параллельно в одном такте.

Большинство операций имеют формат
  <мнемоника>,<канал>    <аргумент>, <аргумент>, ... , <результат>

В качестве аргументов и результатов операций чаще всего выступают регистры. 
Приведённый пример содержит распространённые виды регистров:

%r0, %r1, %r<N>
  рабочие регистры, доступные только в текущей процедуре.
%g0, %g1, %g<N>
  глобальные регистры, доступные всей программе. В примере выступают в качестве временных регистров.
%ctpr1, %ctpr2, %ctpr3
  особые регистры, используемые для передачи управления. С их помощью устанавливаются адреса переходов.
  
  Про все виды регистров, правила работы с ними, а также использование регистров в процедурном механизме,
  подробно рассказывается в описании программных соглашений, раздел :ref:`register_description`.

  Про регистры передачи управления более подробно рассказывается в описании архитектурных решений,
  раздел :ref:`foundational_properties_arch`, и наиболее детально в разделе :ref:`operations_ctpr`.

В начале некоторых широких команд можно видеть служебное слово ``nop`` с параметром.
Оно является подсказкой процессору выдержать задержку в несколько тактов до следующей широкой команды.
Без таких подсказок код может исполняться с блокировками, и итоговая производительность будет ниже,
чем у кода с расставленными ``nop``-ами.


В приведенном примере встречаются операции:

setwd
  задает размер и конфигурацию регистрового окна процедуры; присутствует в первом такте
  подавляющего большинства процедур.
add
  арифметическое сложение.
return
  подготовка возврата из процедуры.
sub
  арифметическое вычитание.
sdivs
  целочисленное деление.
fmuld
  вещественное умножение.
istofd
  преобразование формата из int в double.
fdtoistr
  преобразование формата из double в int c обрубанием точности (truncate).
muls
  целочисленное умножение.
ct
  передача управления. В данном примере работает совместно с return.
sxt
  расширение знаком или нулем 32-битного значения до 64-битного.

Арифметические операции имеют разные мнемоники для типов и разрядностей аргументов.
Мнемоника модифицируется префиксами и суффиксами. Рассмотрим их на примере команды сложения add:

adds
  32-разрядные целочисленные аргументы.
addd
  64-разрядные целочисленные аргументы.
fadds
  32-разрядные вещественные аргументы.
faddd
  64-разрядные вещественные аргументы.

Более подробное описание ассемблера находится в разделе :ref:`reference_iset`.


Дизассемблер
~~~~~~~~~~~~~~~~~~~~~~

Для просмотра дизассемблера объектного или исполняемого файла можно использовать команду :program:`ldis`.

.. code-block:: console

  ldis ./t.o

Возможно включить привязку строк ассемблера к строкам исходного кода.
Эту функцию выполняет опция :option:`-gline`.

.. code-block:: console

  gcc -O3 -gline t.c -c
  ldis ./t.o

Листинг 2. Использование :program:`ldis` с :option:`-gline`, соответствует ассемблеру в Листинге 1

::

  ! function 'f', entry = 9, value = 0x000000, size = 0x070, sect = ELF_TEXT num = 1

    0000<000000000000> f:
                        ipd 2
                        subs,0 %r1, %r2, %g16                         ! t.c : 6
                        adds,3 %r0, %r1, %g17                         ! t.c : 5
                        return %ctpr3                                 ! t.c : 9
                        setwd wsz = 0x4, nfx = 0x1
    0001<000000000020> :nop 2
                        istofd,3 %g17, %dg18                          ! t.c : 8
    0004<000000000028> :nop 7
                        sdivs,5 %g17, %g16, %g16                      ! t.c : 7
    0012<000000000030> :nop 2
    0015<000000000038> :nop 3
                        istofd,3 %g16, %dg16                          ! t.c : 7
    0019<000000000040> :nop 3
                        fmuld,3 %dg16, %dg18, %dg16                   ! t.c : 8
    0023<000000000048> :nop 3
                        fdtoistr,3 %dg16, %g16                        ! t.c : 8
    0027<000000000050> :nop 5
                        muls,3 %g17, %g16, %g16                       ! t.c : 9
    0033<000000000060> :
                        ct %ctpr3                                     ! t.c : 9
                        ipd 3                                         ! t.c : 9
                        sxt,3 0x2, %g16, %dr0                         ! t.c : 9


Дизассемблер перед каждой командой показывает дополнительно:

  - номер такта от начала процедуры (десятичное число слева);
  - IP-адрес команды (шестнадцатеричное число в угловых скобках):

    - в случае объектного файла - относительно начала модуля;
    - в случае исполняемого файла - абсолютный адрес;
    - в случае динамической библиотеки - относительно точки связывания библиотеки.


Вызов функций
~~~~~~~~~~~~~~~~~~~~~~

Пример 2. Использование функций


.. code-block:: c
   :linenos:

        int func_mul(int, int);

        int main(){
            int a=2,b=11,s=0;
            s=func_mul(a,b);
            return s;
        }

        int func_mul(int x, int y){
            return x*y;
        }

Ниже приведён ассемблер примера 2 для оптимизаций :option:`-O0` и :option:`-O3`.

Листинг 3. Ассемблер примера 2 с оптимизацией :option:`-O0`:

::

	.file	"t.c"
	.ignore	ld_st_style
	.ignore	strict_delay
	.text
	.global	main
	.type	main, #function
	.align	8
	main:
	{
	  setwd	wsz = 0x8, nfx = 0x1, dbl = 0x0
	  setbn	rsz = 0x3, rbs = 0x4, rcur = 0x0
	  getsp,0	_f16s,_lts1hi 0xfff0, %r2
	}
	{
	  adds,0,sm	0x0, 0x2, %r3
	  adds,1,sm	0x0, 0xb, %r4
	  adds,2,sm	0x0, 0x0, %r5
	}
	{
	  sxt,0,sm	0x2, %r3, %b[0]
	  sxt,1,sm	0x2, %r4, %b[1]
	}
	.LCS.1:
	{
	  nop 4
	  disp	%ctpr1, func_mul
	}
	{
	  call	%ctpr1, wbs = 0x4
	}
	.LCS.2:
	{
	  adds,0,sm	0x0, %b[0], %r6
	  return	%ctpr3
	}
	{
	  adds,0,sm	0x0, %r6, %r5
	}
	{
	  nop 3
	  sxt,0,sm	0x2, %r5, %r0
	}
	{
	  ct	%ctpr3
	}
	.size	main, .- main
	.global	func_mul
	.type	func_mul, #function
	.align	8
	func_mul:
	{
	  setwd	wsz = 0x4, nfx = 0x1, dbl = 0x0
	}
	{
	  nop 5
	  muls,0	%r0, %r1, %r4
	  return	%ctpr3
	}
	{
	  sxt,0,sm	0x2, %r4, %r0
	  ct	%ctpr3
	}


Листинг 4. Ассемблер примера 2 с оптимизацией :option:`-O3`:

::

	.file	"t.c"
	.ignore	ld_st_style
	.ignore	strict_delay
	.text
	.global	main
	.type	main, #function
	.align	8
	main:
	{
	  nop 5
	  setwd	wsz = 0x4, nfx = 0x1, dbl = 0x0
	  return	%ctpr3
	  addd,0	0x16, 0x0, %r0
	}
	{
	  ct	%ctpr3
	}
	.size	main, .- main
	.global	func_mul
	.type	func_mul, #function
	.align	8
	func_mul:
	{
	  nop 5
	  setwd	wsz = 0x4, nfx = 0x1, dbl = 0x0
	  return	%ctpr3
	  muls,0	%r0, %r1, %g16
	}
	{
	  ct	%ctpr3
	  sxt,0	0x2, %g16, %r0
	}

Листинг 5. Использование :program:`ldis`. Пример 2, оптимизация :option:`-O3`.

.. code-block:: console

  ldis ./a.out

::

    ! function 'main', entry = 56, value = 0x0104e8, size = 0x020, sect = ELF_TEXT num = 12 

    0000<0000000104e8> main: nop 5
                    addd,0 0x16, 0x0, %dr0                    ! t.c : 6
                    return %ctpr3                             ! t.c : 6
                    setwd wsz = 0x4, nfx = 0x1, dbl = 0x0
    0006<000000010500> :
                    ct %ctpr3                                 ! t.c : 6
                    ipd 3                                     ! t.c : 6
    
    ! function 'func_mul', entry = 44, value = 0x010508, size = 0x028, sect = ELF_TEXT num = 12 
    
    0000<000000010508> func_mul: nop 5
                    muls,0 %r0, %r1, %g16                     ! t.c : 10
                    return %ctpr3                             ! t.c : 10
                    setwd wsz = 0x4, nfx = 0x1, dbl = 0x0
                  
    0006<000000010520> :
                    ct %ctpr3                                 ! t.c : 10
                    ipd 3                                     ! t.c : 10
                    sxt,0 0x2, %g16, %dr0                     ! t.c : 10



В листингах ассемблера примера 2 появились новые команды:

setbn
  установить базу вращения числовых регистров. Эта команда дополняет setwd.

  | Механизм вращаемых регистров описан в разделе
  | :ref:`BaseRegisters`.
  | Использование вращаемых регистров в процедурном механизме описано в разделе
  | :ref:`procedure-mech`.
getsp
  выделить или вернуть область в стеке пользователя локального фрейма.

  | Пользовательский стек и работа с ним описаны в разделе :ref:`local_stack`.
disp
  подготовка адреса перехода, в данном случае для операции call.
call
  выполнить вызов функции.

Вызов функции легко заметить по подготовке перехода disp:

.. code-block:: asm

  disp  %ctpr1, func_mul

и последующему вызову функции через call:

.. code-block:: asm

  call  %ctpr1, wbs = 0x4

Обратите внимание, что в результате оптимизаций в режиме :option:`-O3` вызов был заменён на:

.. code-block:: asm

  addd,0 0x16, 0x0, %r0

Конечный возвращаемый результат определился статически как `0x16`, или 22 в десятичной системе.



Чтение и запись в память
~~~~~~~~~~~~~~~~~~~~~~~~

Пример 3. Чтение и запись в память

.. code-block:: c
  :linenos:
  
  long int global_var;
  extern void g(int *);
  
  void f(short int *p)
  {
      int local_var = 14;
      
      g(&local_var);
      local_var++;
      (*p)++;
      g(&local_var);
      global_var++;
  
      return;
  }


Покажем на примере, как выглядят обращения в память по глобальным переменным, локальным переменным в стеке
и по косвенности.

Для этого примера ассемблер с оптимизацией :option:`-O3` и :option:`-O0` отличаются несущественно,
и будет приведен листинг с оптимизацией  :option:`-O3`.
 
Листинг 6. Чтение и запись в память.

::

  f:
    {
      nop 1
      setwd	wsz = 0x8, nfx = 0x1
      setbn	rsz = 0x3, rbs = 0x4, rcur = 0x0
      disp	%ctpr1, g; ipd 2
      getsp,0	_f32s,_lts1 0xffffffe0, %r2
      adds,1	0xe, 0x0, %r3
    }
    {
      addd,0	%r2, _f64,_lts0 0x20, %r1
    }
    {
      subd,0	%r1, 0x4, %r4
    }
    {
      addd,0,sm	0x0, %r4, %b[0]
      stw,2	%r1, _f16s,_lts0lo 0xfffc, %r3
    }
  .LCS.1:
    {
      call	%ctpr1, wbs = 0x4
    }
    {
      nop 2
      disp	%ctpr1, g; ipd 2
      ldh,0	%r0, 0x0, %r3
      addd,1,sm	0x0, %r4, %b[0]
      ldw,2	%r1, _f16s,_lts0lo 0xfffc, %r5
    }
    {
      adds,0	%r3, 0x1, %r3
      adds,1	%r5, 0x1, %r4
    }
    {
      sth,2	%r0, 0x0, %r3
      stw,5	%r1, _f16s,_lts0lo 0xfffc, %r4
    }
    {
      call	%ctpr1, wbs = 0x4
    }
    {
      nop 2
      return	%ctpr3; ipd 2
      ldd,0	0x0, [ _f64,_lts0 global_var ], %r0
    }
    {
      addd,0	%r0, 0x1, %r0
    }
    {
      nop 1
      std,2	0x0, [ _f64,_lts0 global_var ], %r0
    }
    {
      ct	%ctpr3
    }

 
В примере значения всех трех ячеек памяти увеличиваются на 1.
Для этого они считываются операцией ld, увеличиваются с помощью операции add, и записываются операцией st.
Суффикс операций ld и st означает формат данных:

| b - 1 байт
| h - 2 байта
| w - 4 байта
| d - 8 байт

Соответствующие операции:

| ldb, stb - 1 байт
| ldh, sth - 2 байта
| ldw, stw - 4 байта
| ldd, std - 8 байт

В приведенном примере используются переменные формата
`short int` (2 байта), `int` (4 байта) и `long int` (8 байт).
Адрес операций ld и st формируется как сумма двух аргументов, как правило, это база адреса и смещение.
Третий аргумент операции записи - это записываемое по адресу значение.

Адрес глобальной переменной ``global_var`` задается в виде символа:

::

  ldd,0	0x0, [ _f64,_lts0 global_var ], %r0

Кроме имени, в квадратных скобках указаны ключевые слова `_f64,_lts0`.
Они отображают формат и позицию константного значения
(напомним, что адрес глобальной переменной становится константой после линковки).

Локальная переменная ``local_var`` хранится в стеке.
Из-за того, что на нее взят адрес и передан в функцию ``g()``, ее нельзя хранить на регистре.
Адрес в стеке задается как сумма регистра, хранящего указатель на стек, и смещения.

::

  ldw,2	%r1, _f16s,_lts0lo 0xfffc, %r5

В начале процедуры видно. как формируется регистр указателя на локальное окно стека:

::

  getsp,0	_f32s,_lts1 0xffffffe0, %r2
  ...
  addd,0	%r2, _f64,_lts0 0x20, %r1

Здесь операция getsp заказывает новую порцию стека размером `0x20` и размещает указатель
на новую вершину стека в ``%r2``, а в ``%r1`` заносится "дно" локального окна стека.
Подробно механизм описан в разделе :ref:`local_stack`.

Работа с указателем, переданным в качестве параметра, ведется по регистру ``%r0``, содержащему параметр ``p``.

::

  ldh,0	%r0, 0x0, %r3


Циклы
~~~~~~~~~~~~~~~~~~~~~~

Пример 4. Циклы

.. code-block:: c
  :linenos:

  void f(int *v0, int N)
  {
    int i;
    for (i=0; i<N; i++)
      v0[i] += (v0[i] + 3) * v0[i];
  }
   
Листинг 7. Пример 3, оптимизация :option:`-O0`:

::

  f:
    {
      setwd	wsz = 0x5, nfx = 0x1
    }
    {
      adds,0,sm	0x0, 0x0, %r4
    }
  .L4:
    {
      nop 4
      cmplsb,0	%r4, %r1, %pred0
      disp	%ctpr1, .L6; ipd 2
    }
    {
      ct	%ctpr1 ? ~%pred0
    }
  .L9:
    {
      sxt,0,sm	0x2, %r4, %r5
      sxt,1,sm	0x2, %r4, %r6
      sxt,2,sm	0x2, %r4, %r7
      adds,3	%r4, 0x1, %r4
      disp	%ctpr1, .L4; ipd 2
    }
    {
      shld,0	%r5, 0x2, %r5
      shld,1	%r6, 0x2, %r6
      shld,2	%r7, 0x2, %r7
    }
    {
      addd,0	%r0, %r5, %r5
      addd,1	%r0, %r6, %r6
      addd,2	%r0, %r7, %r7
    }
    {
      ldw,0	%r6, 0x0, %r6
      ldw,2	%r7, 0x0, %r8
    }
    {
      nop 2
      ldw,0	%r5, 0x0, %r5
    }
    {
      adds,0	%r5, 0x3, %r5
    }
    {
      nop 5
      muls,0	%r5, %r6, %r5
    }
    {
      adds,0	%r8, %r5, %r5
    }
    {
      stw,2,sm	%r7, 0x0, %r5
      ct	%ctpr1
    }
  .L6:
    {
      nop 5
      return	%ctpr3; ipd 2
    }
    {
      ct	%ctpr3
    }


В метке L4 можно увидеть две широкие команды.
В первой проверяется попадание в цикл и выполняется подготовка перехода.
Во второй команде делается переход по отрицанию условия выхода из цикла в метку L6, находящуюся в голове цикла.
Содержимое между метками L9 и L6 является циклом.


Листинг 8. Пример 3, оптимизация :option:`-O3` в сочетании с опцией :option:`-fmax-iter-for-ovlpeel=0`

::

  f:
    {
      setwd	wsz = 0x4, nfx = 0x1
      return	%ctpr3; ipd 2
    }
    {
      nop 2
      cmplsb,0	0x0, %r1, %pred0
    }
    {
      ct	%ctpr3 ? ~%pred0
    }
    {
      setwd	wsz = 0x10, nfx = 0x1
      setbn	rsz = 0xb, rbs = 0x4, rcur = 0x0
      return	%ctpr3; ipd 2
      sxt,0,sm	0x6, %r1, %g16
      addd,1	0x0, _f64,_lts1 0x2dff2d00000000, %g17
      addd,2,sm	0x1, 0x0, %g18
      addd,3	0x0, 0x0, %g19
    }
    {
      nop 1
      disp	%ctpr1, .L75; ipd 2
      cmplsb,0,sm	0x0, %r1, %pred0
      addd,1,sm	0x0, 0x0, %b[15]
      aaurwd,2	%r0, %aad0
      aaurwd,5	%g19, %aasti1
    }
    {
      insfd,0,sm	%g17, _f32s,_lts0 0x8800, %g16, %g16 ? %pred0
      insfd,1,sm	%g17, _lit32_ref,_lts0 0x8800, %g18, %g16 ? ~%pred0
    }
    {
      nop 3
      rwd,0	%g16, %lsr
    }
  .L75:
    {
      loop_mode
      alc	alcf=1, alct=1
      abn	abnf=1, abnt=1
      ct	%ctpr1 ? %NOT_LOOP_END
      muls,0,sm	%g17, %b[10], %b[1]
      addd,1,sm	0x4, %b[15], %b[13]
      adds,2,sm	%b[8], 0x3, %g17
      ldw,3,sm	%r0, %b[17], %b[0] ? %pcnt12
      adds,4,sm	%b[22], %b[13], %g16
      staaw,5	%g16, %aad0[ %aasti1 ]
      incr,5	%aaincr0
    }
    {
      setwd	wsz = 0x4, nfx = 0x1
      adds,0	0x0, 0x0, %g16
    }
    {
      ct	%ctpr3
      aaurw,2	%g16, %aabf0
    }


При оптимизации :option:`-O3` для циклов компилятор строит более компактный код.
Дополнительная опция :option:`-fmax-iter-for-ovlpeel=0` применяется при компиляции данного примера,
чтобы получить более наглядный и читаемый листинг. Значение этой опции описано в разделе :ref:`soft_conveyer`.

В листинге появились новые команды и служебные слова:

insf
  команда "вставить битовое поле"; формирует значение из двух регистров,
  заданных в первом и третьем аргументах, управляется значением, заданным во втором аргументе.
  В данном примере формирует значение, составленное из старших 32 бит одного регистра
  и младших 32 бит другого.
rwd
  операции записи в специальные регистры.
  В данном случае происходит запись в регистр управления циклом ``lsr``.
aaurwd
  операции записи в регистры описания массивов данных.
loop_mode
  метка, которая говорит о том, что в пределах цикла работает аппаратная поддержка счетчика цикла.
  Не является самостоятельной операцией.
alc
  продвинуть счетчик цикла (advance loop counter).
abn
  продвинуть вращаемые регистры (advance base numeric).
staa
incr
  записать значение в массив и продвинуть указатель на фиксированную величину. Возможны только в паре.


Специальный регистр ``%lsr`` содержит в себе счётчик цикла, который декрементируется на каждой итерации.
При исчерпании (обнулении) счетчика условие  `%NOT_LOOP_END` становится ложным,
а переход в голову цикла, находящийся под этим условием, не срабатывает.

Тело цикла компактно упаковано в одну широкую команду, и исполняется с темпом 1 итерация за 1 такт.
Примененная техника оптимизации называется *конвейеризацией* и описана в разделе :ref:`soft_conveyer`.

Работа с массивами, или в более общем виде работа с чтениями и записями по регулярно изменяющимся адресам,
описана в разделе :ref:`reference_prefetch`.


Условный код
~~~~~~~~~~~~~~~~~~~~~~

Пример 5. Операции под условием

.. code-block:: c
  :linenos:

  void f(int c, int* p)
  {
  
    if (c>0)
    {
      (*p) = (*p) * (*p);
    }
    else 
    {
      (*p) = -(*p);
    }
    
  }

Условный код существенно изменяется, если применять оптимизацию :option:`-O2` и выше.
Рассмотрим код приведенного примера для :option:`-O1` и :option:`-O3`.


Листинг 9. Пример 5, оптимизация :option:`-O1`

::

  f:
    {
      nop 1
      setwd	wsz = 0x4, nfx = 0x1
      disp	%ctpr1, .L6; ipd 2
    }
    {
      nop 2
      cmplesb,0	%r0, 0x0, %pred0
    }
    {
      ct	%ctpr1 ? ~%pred0
    }
    {
      nop 2
      ldw,0	%r1, 0x0, %g16
    }
    {
      subs,0	0x0, %g16, %g16
    }
    {
      stw,2	%r1, 0x0, %g16
    }
  .L25:
    {
      nop 5
      return	%ctpr3; ipd 2
    }
    {
      ct	%ctpr3
    }
  .L6:
    {
      nop 2
      disp	%ctpr1, .L25; ipd 2
      ldw,0	%r1, 0x0, %g16
    }
    {
      nop 3
      muls,0	%g16, %g16, %g16
    }
    {
      ct	%ctpr1
      stw,2	%r1, 0x0, %g16
    }


В приведенном коде есть новые операции:

cmpl
  операция сравнения двух аргументов.
  Результат операции сравнения записывается в предикатный регистр ``%pred<N>``, где N может быть от 0 до 31.
  Это отличает архитектуру Эльбрус от многих других архитектур,
  в которых сравнение вырабатывает специальный регистр флагов.
  Предикатные регистры хранят значения `0` или `1`, и могут использоваться для:

  - передачи управления, 
  - управления исполнением операции,
  - вычисления других предикатных регистров.
disp
  операция подготовки перехода. Как мы уже видели, операция может подготовить адрес для вызова процедуры,
  но в данном примере с ее помощью подготавливается переход по локальным меткам .L6 и .L25.
ct
  передача управления. С ее помощью производится не только возврат из процедуры,
  но и заранее подготовленный переход по локальной метке.



Листинг 10. Пример 5, оптимизация :option:`-O3`

::

  f:
    {
      nop 2
      setwd	wsz = 0x4, nfx = 0x1
      return	%ctpr3; ipd 2
      ldw,0,sm	%r1, 0x0, %g17
      ldw,2,sm	%r1, 0x0, %g16
    }
    {
      nop 1
      muls,0,sm	%g16, %g16, %g16
      subs,1,sm	0x0, %g17, %g17
    }
    {
      nop 1
      cmplesb,0	%r0, 0x0, %pred0
    }
    {
      ct	%ctpr3
      stw,2	%r1, 0x0, %g16 ? ~%pred0
      stw,5	%r1, 0x0, %g17 ? %pred0
    }


Здесь можно увидеть другой способ использования предикатных регистров - управление исполнением операций. 

::

  stw,2	%r1, 0x0, %g16 ? ~%pred0

После операции записи указывается символ `?`, за которым следует предикатный регистр.
Операция записи будет исполнена, если значение регистра равно `1`, и не будет исполнена,
если значение равно `0`.
Если перед предикатным регистром указать символ отрицания `~`, то значение управляющего
предикатного регистра будет использовано инвертированным образом.
Управляющий предикат также иногда называют *квалифицирующим* (Qualifying Predicate).

Отсюда вытекает логика оптимизированного листинга:
в зависимости от условия ``c>0`` выполнится операция записи с необходимым значением,
сформированном в регистрах ``%g16`` либо ``%g17`` соответственно.

Код, в котором передача управления заменена на управляющие предикаты, называется *предикатным*.
Техника преобразования кода от условного к предикатному описана в разделе :ref:`merge_code`.


Переходы и вызовы по косвенности
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Пример 6. Конструкция switch

.. code-block:: c
    :linenos:

    void f( int v, float *arr)
    {
    
      switch(v)  {
      case 10:
      case 20:
              arr[0] *= 2.0;
              break;
      case 11:
      case 21:
              arr[1] *= 3.0;
              break;
      
      case 12:
      case 22:
              arr[2] *= 4.0;
              break;
      
      case 13:
      case 23:
              arr[3] *= 5.0;
              break;
    
      case 14:
      case 24:
              arr[4] *= 6.0;
              break;
    
      case 15:
      case 25:
              arr[5] /= 7.0;
              break;
      
      default:
    	  arr[6] += 1.0;
      }
      return;
    }


Для этого примера код на :option:`-O1` и :option:`-O3` не будет существенно отличаться.
Приведем ассемблер для уровня оптимизации :option:`-O3`.

Листинг 10. Пример 6, оптимизация :option:`-O3`

::

  f:
    {
      setwd	wsz = 0x4, nfx = 0x1
      disp	%ctpr3, .L72; ipd 2
      subs,3	%r0, 0xa, %g16
    }
    {
      cmpbesb,3	%g16, 0xf, %pred0
      sxt,4,sm	0x2, %g16, %g16
    }
    {
      shld,3,sm	%g16, 0x3, %g16
    }
    {
      ldd,3,sm	%g16, [ _f64,_lts0 .T.1 ], %r0
    }
    {
      ct	%ctpr3 ? ~%pred0
    }
    {
      nop 2
    }
    {
      nop 7
      movtd,0,sm	%r0, %ctpr1; ipd 2
    }
    nop
    {
      ct	%ctpr1
    }
  .LSC.1:
    {
      nop 2
      return	%ctpr3; ipd 2
      ldw,0	%r1, 0x0, %g16
    }
    {
      nop 3
      fmuls,0	%g16, _f32s,_lts0 0x40000000, %g16
    }
    {
      ct	%ctpr3
      stw,2	%r1, 0x0, %g16
    }
  .LSC.2:
    {
      nop 2
      return	%ctpr3; ipd 2
      ldw,0	%r1, 0x4, %g16
    }
    {
      nop 3
      fmuls,0	%g16, _f32s,_lts0 0x40400000, %g16
    }
    {
      ct	%ctpr3
      stw,2	%r1, 0x4, %g16
    }
  .LSC.3:
    {
      nop 2
      return	%ctpr3; ipd 2
      ldw,0	%r1, 0x8, %g16
    }
    {
      nop 3
      fmuls,0	%g16, _f32s,_lts0 0x40800000, %g16
    }
    {
      ct	%ctpr3
      stw,2	%r1, 0x8, %g16
    }
  .LSC.4:
    {
      nop 2
      return	%ctpr3; ipd 2
      ldw,0	%r1, 0xc, %g16
    }
    {
      nop 3
      fmuls,0	%g16, _f32s,_lts0 0x40a00000, %g16
    }
    {
      ct	%ctpr3
      stw,2	%r1, 0xc, %g16
    }
  .LSC.5:
    {
      nop 2
      return	%ctpr3; ipd 2
      ldw,0	0x10, %r1, %g16
    }
    {
      nop 3
      fmuls,0	%g16, _f32s,_lts0 0x40c00000, %g16
    }
    {
      ct	%ctpr3
      stw,2	0x10, %r1, %g16
    }
  .LSC.6:
    {
      nop 2
      return	%ctpr3; ipd 2
      ldw,3	0x14, %r1, %g16
    }
    {
      nop 3
      fstofd,3	%g16, %g16
    }
    {
      nop 7
      fdivd,5	%g16, _f64,_lts0 0x401c000000000000, %g16
    }
    {
      nop 5
    }
    {
      nop 3
      fdtofs,3	%g16, %g16
    }
    {
      ct	%ctpr3
      stw,5	0x14, %r1, %g16
    }
  .L72:
  .LSC.7:
    {
      nop 2
      return	%ctpr3; ipd 2
      ldw,0	0x18, %r1, %g16
    }
    {
      nop 3
      fstofd,0	%g16, %g16
    }
    {
      nop 3
      faddd,0	%g16, _f64,_lts0 0x3ff0000000000000, %g16
    }
    {
      nop 3
      fdtofs,0	%g16, %g16
    }
    {
      ct	%ctpr3
      stw,2	0x18, %r1, %g16
    }
  .T.1:
    .dword	.LSC.1
    .dword	.LSC.2
    .dword	.LSC.3
    .dword	.LSC.4
    .dword	.LSC.5
    .dword	.LSC.6
    .dword	.LSC.7
    .dword	.LSC.7
    .dword	.LSC.7
    .dword	.LSC.7
    .dword	.LSC.1
    .dword	.LSC.2
    .dword	.LSC.3
    .dword	.LSC.4
    .dword	.LSC.5
    .dword	.LSC.6

В ассемблере можно видеть следующее:

- различные альтернативы конструкции switch начинаются с меток `.LSC.<N>`
- метки собраны в секции данных в таблицу `.T.1`
- в начале процедуры из таблицы производится чтение по смещению:

::

  ldd,3,sm	%g16, [ _f64,_lts0 .T.1 ], %r0

- результат чтения преобразуется в регистр подготовленного перехода с помощью команды:

::

  movtd,0,sm	%r0, %ctpr1; ipd 2

- по регистру ``%ctpr1`` выполняется переход:

::

  ct	%ctpr1

Этот переход осуществит передачу управления на метку, соответствующую требуемой альтернативе
конструкции switch.

Команда movtd выполняет передачу управления по косвенности: она подготавливает переход по значению регистра.
Данная операция может также использоваться для вызовов функций.
Продемонстрируем это на примере с вызовом виртуального метода C++.

Пример 7. Вызов виртуального метода

.. code-block:: c++
  :linenos:

  class base {
  protected: 
    int val;

  public:
    virtual int getval();
  };

  int f(base *p)
  {
    return p->getval();
  }


Листинг 11. Пример 7, оптимизация :option:`-O3`

::

  _Z1fP4base:
    .cfi_startproc
    {
      nop 2
      setwd	wsz = 0x8, nfx = 0x1
      setbn	rsz = 0x3, rbs = 0x4, rcur = 0x0
      getsp,0	_f32s,_lts1 0xfffffff0, %r2
      ldd,3	%r0, 0x0, %r3
    }
    {
      nop 1
      ldd,3	%r3, 0x0, %r3
    }
    {
      nop 1
      addd,0,sm	0x0, %r0, %b[0]
    }
    {
      nop 7
      movtd,0,sm	%r3, %ctpr1; ipd 2
    }
    nop
  .LCS.1:
    {
      call	%ctpr1, wbs = 0x4
    }
    {
      nop 5
      return	%ctpr3; ipd 2
      sxt,3	0x2, %b[0], %r0
    }
    {
      ct	%ctpr3
    }

В функции ``f`` (манглированное имя `_Z1fP4base`) в нулевом такте производится чтение
с нулевым смещением по указателю ``p``:

::

  ldd,3	%r0, 0x0, %r3

По этому адресу лежит указатель на таблицу виртуальных методов класса `base`.
Далее из полученного указателя на таблицу производится еще одно чтение:

::

  ldd,3	%r3, 0x0, %r3

Полученный адрес является адресом входа в виртуальный метод ``getval()``.
Этот адрес записывается в регистр подготовки перехода:

::

  movtd,0,sm	%r3, %ctpr1; ipd 2

И наконец, по полученному регистру ``%ctpr1`` производится вызов:

::

  call	%ctpr1, wbs = 0x4

Необходимо заметить, что процесс подготовки перехода по числовому регистру является весьма
длительной операцией: она требует 9 тактов.
По этой причине конструкции switch и вызовы по косвенности являются для архитектуры Эльбрус
не эффективными с точки зрения производительности.
Этот вопрос дополнительно рассматривается в разделе :ref:`perf_recommend`.



Работа в gdb
-------------------------

Рассмотрим особенности отладки для платформы Эльбрус в :program:`gdb` на примере.

SIGILL как сигнал об ошибках
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code-block:: c
   :linenos:

        #include <stdlib.h>
        
        int *p;
        int *q;
        int g;

        int main(int argc, char* argv[])
        {
          p=&g;
          q=&g;
          
          q=q+0x100000;
        
          if (argc>1)
            if (atoi(argv[1])==1)
              *p = *q;
          return 0;
        }

В данном коде происходит попытка чтения по некорректному адресу, хранящемуся в `q`.
Пример скомпилируем с опциями ``-O0 -g`` и ``-O2 -g``.

.. code-block:: console

  gcc ./t.c -o t_O0 -O0 -g
  gcc ./t.c -o t_O2 -O2 -g

При выполнении без параметров падения нет.
При запуске с параметром "1" и оптимизации :option:`-O0` увидим "Segmentation fault" после некорректного чтения из `\*q`.
При запуске с оптимизацией :option:`-O2` будет выведена ошибка "Illegal instruction".
Она вызвана тем же некорректным чтением, однако приложение получило сигнал SIGILL вместо SIGSEGV.

Это произошло потому, что при оптимизациях чтение из `\*q` было в спекулятивном режиме.
Вместо падения исполнение программы продолжилось. При попытке записи некорректного (диагностического) значения в `\*p`
происходит слом, который по системе команд вызывает сигнал SIGILL.
Данный сигнал говорит о попытке работы с диагностическим значением в неспекулятивной операции,
в данном случае в записи в память.

SIGILL необходимо трактовать как сигнал о программной ошибке, наряду с SIGSEGV или SIGBUS.

Отладка оптимизированного кода
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Отображение исходного кода при исполнении программы в :program:`gdb` возможно только при её сборке с опциями `-O0 -g`.
В этом режиме работа отладчика ничем не отличается от поведения на других архитектурах.

В режиме с оптимизациями (:option:`-O1` или выше) отображение исходного кода при исполнении не поддержано.
Чтобы пошагово выполнять программу, нужно пользоваться командами `nexti`, `stepi` вместо их аналогов `next`, `step`.

Чтобы видеть пошагово исполняемые широкие команды Эльбруса, можно выполнить:

.. code-block:: console

    (gdb) display /i $pc

После этого в каждой точке останова будет печататься следующая широкая команда.

Операции внутри одной широкой команды выполняются параллельно на уровне аппаратуры,
поэтому в отладчике нет возможности выполнить эти операции пошагово.

Для просмотра данных выполняются команды `info registers` и печать содержимого памяти `x /x 0x<adress>`.

.. code-block:: console

    (gdb) info registers r0 r1

Выведет содержимое регистров r0, r1.

.. code-block:: console

    (gdb) x /4xw 0x1009a

Вывести в 16-ричном формате 4 одинарных слова (32-битных) из локальной памяти по адресу *`0x1009a`*

Пример сессии gdb
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Запустим в gdb программу, приведённую выше в первой секции раздела об отладке.

.. code-block:: console

    gcc ./t.c -o t_O2 -O2 -g
    gdb ./t_O2

Запуск программы с параметром, чтобы получить ошибку.

.. code-block:: console

    (gdb) set args 1
    (gdb) r

::

    Starting program: /home/test/t_O2 1
    
    Program received signal SIGILL, Illegal instruction
    
    exc_diag_operand at 0x10738 ALS2
    
    0x0000000000010738 in main ()

В диагностическом выводе указан адрес падения и слог в широкой команде, на котором произошёл слом.

Просмотрим дизассемблер всей функции:

.. code-block:: console
        
    (gdb) disassemble
        
::

        Dump of assembler code for function main:
           0x0000000000010628 <+0>:	
         :
          ipd 2
          getsp,0 _f32s,_lts1 0xffffffe0, %dr3
          addd,1 0x0, _f64,_lts2 0x11c28, %dr4
          disp %ctpr2, M_10428
          setwd wsz = 0x8, nfx = 0x1
          setbn rsz = 0x3, rbs = 0x4, rcur = 0x0                     
        
           0x0000000000010658 <+48>:	
         :
          ipd 2
          cmplesb,0 %r0, 0x1, %pred0
          addd,1 0x0, _f64,_lts0 0x411c28, %dr5
          std,2 %dr4, [ _f64,_lts2 0x11c20 ]
          return %ctpr3
        
           0x0000000000010680 <+88>:	
         :
          std,2 %dr5, [ _f64,_lts0 0x11c30 ]
          ldd,5,sm [ %dr1 + 0x8 ], %dr1

           0x0000000000010698 <+112>:	
         :
          addd,0 0x0, 0x0, %dr0 ? %pred0
          addd,1,sm 0xa, 0x0, %db[2] ? ~ %pred0
          addd,2,sm 0x0, 0x0, %db[1] ? ~ %pred0
          addd,3 0x0, 0x0, %dr0 ? ~ %pred0
          rlp,cd00 %pred0, ~>alc2, ~>alc1, >alc0
          rlp,cd01 %pred0, ~>alc3
        
           0x00000000000106b0 <+136>:	
         :
          ct %ctpr3 ? %pred0
          ipd 3
        
           0x00000000000106b8 <+144>:	
         :
          ldd,0,sm [ _f64,_lts0 0x11c30 ], mas = 0x4, %dr1
          addd,4,sm 0x0, %dr1, %db[0] ? ~ %pred0
          
          rlp,cd00 %pred0, ~>alc4
        
           0x00000000000106d8 <+176>:	
         :
          ipd 3
          call %ctpr2, wbs = 0x4  ? ~ %pred0
        
           0x00000000000106e8 <+192>:	
         :      
                ---Type <return> to continue, or q <return> to quit---
          ipd 2
          ldd,0,sm [ _f64,_lts0 0x11c20 ], %dg17
          addd,1,sm 0x0, %db[0], %dg16 ? ~ %pred0
          return %ctpr3  
          
          rlp,cd00 %pred0, ~>alc1
        
           0x0000000000010708 <+224>:	
         :
          cmpesb,0,sm %g16, 0x1, %pred1
        
           0x0000000000010710 <+232>:	
         :nop 1
          pass %pred0, @p0
          pass %pred1, @p1
          landp ~@p0, @p1, @p4
          pass @p4, %pred1
        
           0x0000000000010718 <+240>:	
         :
          ldd,2 [ _f64,_lts0 0x11c30 ], mas = 0x3, %dr1 ? %pred1
          
          rlp,cd00 %pred1, >alc2
        
           0x0000000000010730 <+264>:	
         :nop 3
          ldw,0,sm [ %dr1 + 0x0 ], %g16
        
         => 0x0000000000010738 <+272>:	
         :
          ct %ctpr3 ? ~ %pred0
          ipd 3
          stw,2 %g16, [ %dg17 + 0x0 ] ? %pred1
          rlp,cd00 %pred1, >alc2
                  
         End of assembler dump.

В листинге команда с адресом `0x0000000000010738 <+272>` отмечена стрелочкой ``=>``.
Это говорит о том, что данная команда вызвала ошибку.

Чтобы подробнее разобрать, в чём она заключается, распечатаем значения регистров:

.. code-block:: console

    (gdb) info all-registers g16 g17

::

    g16                           <11> 0x4afafafa4afafafa	5402906655891061498
    g17                           <00> 0x11c28	72744


g16 содержит странное значение `0x4afafafa4afafafa`. 
Слева от значения регистра расположены теги диагностики, выставленные в две единицы. 

Таким образом инициализируется регистр при ошибочной операции, которая произошла в спекулятивном режиме.

g17 при этом содержит корректное значение.


Прочее
-------------------------


Отладка ядра
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Чтобы сбросить стек ядра, можно нажать комбинацию клавиш:

.. code-block:: console

        crtl-prtscr-?
        crtl-prtscr-'t'
        crtl-prtscr-'l'
        crtl-prtscr-'r'


Расширенные возможности для отладки ОС выведены в :file:`/proc/sys/debug/`:

.. code-block:: console

        echo 1 > /proc/sys/debug/sigdebug
        echo 1 > /proc/sys/debug/datastack
        echo 1 > /proc/sys/debug/userstack
        echo 1 > /proc/sys/debug/coredump
        echo 1 > /proc/sys/debug/pagefault

Чтобы сохранять дампы памяти в пользовательской директории:

.. code-block:: console

        mkdir -p /export/mycore
        sysctl -w kernel.core_pattern=/export/mycore/core-%e-%s-%u-%g-%p-%t

Модификация запуска задач
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

В случае, если требуется привязать выполнение задачи к какому-то конкретному ядру или набору ядер,
следует использовать команду :program:`taskset`. Пример:

.. code-block:: console

    taskset -c 1,2,3,4 <команда с аргументами>



