Пример разбора листинга простой программы на Си

 

Простая программа на Си

 

#include<stdio.h>

float power(float x,int n)

{ int i;

  float s;

  s=1;

  for (i=0;i<n;i++) s=s*x;

  return s;

}

 

int main()

{ float y;

  y=power(2.125,10);

  printf("Result: %f\n",y);

  return 0;

}

 

Ассемблерный листинг, сгенерированный командой: icc -O3 -march=pentiumiii -S test.c

 

# -- Machine type PA

# mark_description "Intel(R) C++ Compiler for 32-bit applications, Version 7.1   Build 20030307Z";

# mark_description "-long_double -Xlinker -rpath -Xlinker /opt/intel/compiler70/ia32/lib -O3 -march=pentiumiii -S";

     .ident "Intel(R) C++ Compiler for 32-bit applications, Version 7.1   Build 20030307Z"

     .ident "-long_double -Xlinker -rpath -Xlinker /opt/intel/compiler70/ia32/lib -O3 -march=pentiumiii -S"

     .file "test.c"

     .text

     .data

     .align 4

     .bss

     .align 4

     .data

     .align 16

_2il0floatpacket.3:

     .long 0x3f800000,0x3f800000,0x3f800000,0x3f800000    # ms128

     .type     _2il0floatpacket.3,@object

     .size     _2il0floatpacket.3,16

_2il0floatpacket.1:

     .long 0x3f800000   # xf32

     .type     _2il0floatpacket.1,@object

     .size     _2il0floatpacket.1,4

     .data

     .text

С символа # видимо начинаются комментарии.

# -- Begin  power

# mark_begin;

       .align    4,0x90                                         # Это директива выравнивания. Она указывает, чтобы следующая команда в памяти была выровнена по 4 байтам.

     .globl power

power:

# parameter 1: 8 + %ebx

# parameter 2: 12 + %ebx

..B1.1:                         # Preds ..B1.0               

        pushl     %ebx                                          #3.1     Начинаются всякие операции с указателями стека и кадра

        movl      %esp, %ebx                                    #3.1

        andl      $-16, %esp                                    #3.1

        subl      $16, %esp                                     #3.1

        movl      12(%ebx), %ecx                                #2.7     Записать с регистр ecx значение по адресу ebx+12 (похоже, это параметр n)

        testl     %ecx, %ecx                                    #6.3     Проверить значение ecx (установить соответствующие флаги)

        flds      8(%ebx)                                       #2.7     Загрузить в вершину FPU-стека число по адресу ebx+8 (видимо, это переменная x)

        flds      _2il0floatpacket.1                            #5.3     Загрузить в вершину FPU-стека число по указанному адресу (какая-то константа)

        flds      _2il0floatpacket.1                            #5.3     Загрузить в вершину FPU-стека число по указанному адресу (та же константа)

        jle       ..B1.12       # Prob 2%                       #6.3     Если "меньше или равно" (ecx<=0), то переходим в конец функции. Т.е. если n<=0, то ничего делать не надо. Заметим, что здесь установка флагов (test) и проверка (jle) разделены командами сопроцессора, которые могут выполняться независимо и параллельно (одновременно).

                                # LOE ecx ebp esi edi f1 f2 f3

..B1.2:                         # Preds ..B1.1

        xorl      %edx, %edx                                    #6.3     Обнуляем edx. Видимо, это i - счетчик цикла.

        cmpl      $8, %ecx                                      #6.3     Сравниваем ecx со значением 8

        jl        ..B1.6        # Prob 50%                      #6.3     Переход, если меньше (ecx<8)

                                # LOE edx ecx ebp esi edi f1 f2 f3

..B1.3:                         # Preds ..B1.2

        fstp      %st(0)                                        #  Удаляем значение из вершины FPU-стека

        movl      %ecx, %eax                                    #6.3     Записать значение ecx в eax (присваивание)

        andl      $7, %eax                                      #6.3    В eax биты 0,1,2 без изменений, более старшие биты обнуляются (7=00000111b). Получаем в eax остаток от деления его на 8.

        negl      %eax                                          #6.3     Меняем знак: eax = -eax

        addl      %ecx, %eax                                    #6.3     Прибавляем: eax = eax + ecx

# Результат: eax - это теперь округленный до 8 ecx (см. комментарии после листинга).

        movaps    _2il0floatpacket.3, %xmm2                     #6.21     Записываем в регистр xmm2 значения из памяти (похоже, четыре единицы). Ух ты, используются регистры xmm! Неужели он что-то векторизовал?

        fxch      %st(1)                                        #6.25     Меняем местами значения st(1) и st(0) (st(0) - вершина FPU-стека)

        fsts      (%esp)                                        #6.25     Записываем в память по адресу esp значение из вершины FPU-стека

        movss     (%esp), %xmm0                                 #6.25     Записать float значение из памяти по адресу esp в регистр xmm0

        shufps    $0, %xmm0, %xmm0                              #6.25     Как-то перераспределяем значения в регистре xmm0 (наверно, первое float-число из регистра xmm0 размножаем по остальным позициям в регистра).

        .align    4,0x90

# Вот такой векторизованный цикл получился:

                                # LOE eax edx ecx ebp esi edi f1 f3 xmm0 xmm2

..B1.4:                         # Preds ..B1.4 ..B1.3

        mulps     %xmm0, %xmm2                                  #6.25     Векторное умножение: xmm2 *= xmm0 (4 числа сразу!)

        mulps     %xmm0, %xmm2                                  #6.25     Векторное умножение: xmm2 *= xmm0 (4 числа сразу!)

        addl      $8, %edx                                      #6.3    edx += 8 ( i+=8, т.к. сразу 8 умножений за одну итерацию)

        cmpl      %eax, %edx                                    #6.3     Сравниваем eax и edx

        jl        ..B1.4        # Prob 97%                      #6.3     Если меньше (edx<eax), то переход

                                # LOE eax edx ecx ebp esi edi f1 f3 xmm0 xmm2

..B1.5:                         # Preds ..B1.4

        movaps    %xmm2, %xmm0                                  #6.3     Векторное присваивание: xmm0 = xmm2

        movhlps   %xmm0, %xmm0                                  #6.3     Перемещение двух нижних float-чисел регистра xmm0 в верхние

        mulps     %xmm0, %xmm2                                  #6.21     Векторное умножение: xmm2 *= xmm1

        movaps    %xmm2, %xmm1                                  #6.3     Векторное присваивание: xmm1 = xmm2

        shufps    $1, %xmm1, %xmm1                              #6.3     Как-то перераспределяем значения в регистре xmm1

        mulss     %xmm1, %xmm2                                  #6.21     Скалярное умножение (одного самого младшего float-числа): xmm2* = xmm1

        cmpl      %ecx, %edx                                    #6.3     Сравниваем ecx и edx (опять заметим: сравнение делается намного раньше, чем понадобится его результат; это потому, что эту и последующие команды он может делать независимо и, возможно, параллельно; такая вот оптимизация).

        movss     %xmm2, 8(%esp)                                #6.3     Записываем младшее число float из регистра xmm2 в память по адресу esp+8

        flds      8(%esp)                                       #6.3    А потом его заносим в вершину FPU-стека

        fmulp     %st, %st(2)                                   #6.3     Умножаем: st(0)*st(2) (они при этом выталкиваются из стека). Результат помещаем в вершину стека.

        fxch      %st(1)                                        #6.3     Меняем местами st(1) и вершину FPU-стека st(0)

        fsts      8(%esp)                                       #6.3     Записываем значение из вершины FPU-стека в память по адресу esp+8

        flds      8(%esp)                                       #6.3    И помещаем его же в FPU-стек (теперь это новая вершина)

        jl        ..B1.6        # Prob 50%                      #6.3     Переход, если меньше (edx<ecx)

                                # LOE edx ecx ebp esi edi f1 f2 f3

..B1.13:                        # Preds ..B1.5

        fstp      %st(2)                                        #     Выталкиваем из FPU-стека значение в st(2)

        fstp      %st(0)                                        #     Выталкиваем из FPU-стека вершину

        jmp       ..B1.9        # Prob 100%                     #  Переход на ..B1.9

                                # LOE ebp esi edi f2

..B1.6:                         # Preds ..B1.5 ..B1.2

        fstp      %st(1)                                        #     Выталкиваем из FPU-стека значение в st(1)

        .align    4,0x90

# В этом цикле доделываются умножения:

                                # LOE edx ecx ebp esi edi f1 f2

..B1.7:                         # Preds ..B1.6 ..B1.7

        fmul      %st(1), %st                                   #6.25     Умножение: st(1)*st(0). Результат - в вершину FPU-стека.

        incl      %edx                                          #6.3     Увеличение edx на 1

        fstps     8(%esp)                                       #6.21     Выталкиваем значение из вершины FPU-стека в память по адресу esp+8

        cmpl      %ecx, %edx                                    #6.3     Сравнение ecx и edx

        flds      8(%esp)                                       #6.21     Заносим в FPU-стек значение по адресу esp+8

        jl        ..B1.7        # Prob 50%                      #6.3     Переход, если ecx<edx

                                # LOE edx ecx ebp esi edi f1 f2

..B1.8:                         # Preds ..B1.7

        fstp      %st(1)                                        #     Выталкиваем из FPU-стека значение st(1)

        jmp       ..B1.9        # Prob 100%                     #  Переход на ..B1.9

                                # LOE ebp esi edi f2

..B1.12:                        # Preds ..B1.1

        fstp      %st(2)                                        #     Выталкиваем из FPU-стека значение st(2)

        fstp      %st(0)                                        #     Выталкиваем из FPU-стека вершину

                                # LOE ebp esi edi f2

..B1.9:                         # Preds ..B1.13 ..B1.8 ..B1.12

        movl      %ebx, %esp                                    #7.10     Восстанавливаем указатель вершины стека

        popl      %ebx                                          #7.10     Восстанавливаем указатель кадра

        ret                                                     #7.10

        .align    4,0x90

                                # LOE

# mark_end;

     .type     power,@function

     .size     power,.-power

     .data

# -- End  power

     .data

     .align 16

__STRING.0:

     .byte 82  # s8

     .byte 101 # s8

     .byte 115 # s8

     .byte 117 # s8

     .byte 108 # s8

     .byte 116 # s8

     .byte 58  # s8

     .byte 32  # s8

     .byte 37  # s8

     .byte 102 # s8

     .byte 10  # s8

     .byte 0   # s8

     .type     __STRING.0,@object

     .size     __STRING.0,12

_2il0floatpacket.5:

     .long 0x40080000   # xf32

     .type     _2il0floatpacket.5,@object

     .size     _2il0floatpacket.5,4

     .data

     .text

# -- Begin  main

# mark_begin;

       .align    4,0x90

     .globl main

main:

..B2.1:                         # Preds ..B2.0

        pushl     %ebp                                          #11.1

        movl      %esp, %ebp                                    #11.1

        subl      $3, %esp                                      #11.1

        andl      $-8, %esp                                     #11.1

        addl      $4, %esp                                      #11.1

        subl      $16, %esp                                     #11.1

        pushl     %eax                                          #11.1

        pushl     %eax                                          #11.1

        stmxcsr   (%esp)                                        #11.1

        popl      %eax                                          #11.1

        orl       $32768, %eax                                  #11.1

        pushl     %eax                                          #11.1

        ldmxcsr   (%esp)                                        #11.1

        popl      %eax                                          #11.1

        popl      %eax                                          #11.1

        push      $10                                           #12.5

        push      $1074266112                                   #12.5

        call      power                                         #12.5

                                # LOE ebx esi edi f1

..B2.6:                         # Preds ..B2.1

        addl      $8, %esp                                      #12.5

                                # LOE ebx esi edi f1

..B2.2:                         # Preds ..B2.6

        movl      $__STRING.0, (%esp)                           #13.25

        fstpl     4(%esp)                                       #13.25

        call      printf                                        #13.25

                                # LOE ebx esi edi

..B2.3:                         # Preds ..B2.2

        xorl      %eax, %eax                                    #14.3

        addl      $16, %esp                                     #14.3

        movl      %ebp, %esp                                    #14.3

        popl      %ebp                                          #14.3

        ret                                                     #14.3

        .align    4,0x90

                                # LOE

# mark_end;

     .type     main,@function

     .size     main,.-main

     .data

# -- End  main

     .data

# End

 

Комментарии к ассемблерному листингу вычислительной функции:

-     переменная n (параметр функции) хранится в регистре ecx,

-     переменная i (счетчик цикла) хранится в регистре edx,

-     в регистр eax записывается значение eax=n - n%8, оно кратно 8,

-     векторизованный цикл выполняет n/8 итераций по 8 умножений за итерацию, результаты в xmm2,

-     затем перемножаются числа-компоненты регистра xmm2; получается, выполнено n√n%8 умножений,

-     остальные n%8 умножений выполняются на FPU (причем все время лазит в память).

-     иногда для временного хранения значений происходит обращение в память; но если одна и та же ячейка памяти сначала пишется, а потом сразу читается, то задержки не происходит (значение еще находится в буфере записи и в память еще не ушло).

 

Замечания по синтаксису ассемблера AT&T

-        К названиям команд, имеющим операнды, добавляются суффиксы, отражающие размер операндов:

b - байт

w - слово

l - двойное слово

q - учетверенное слово

s - 32-битное число с плавающей точкой

l - 64-битное число с плавающей точкой

t - 80-битное число с плавающей точкой

-        Если команда имеет несколько операндов, операнд-источник записывается первым, а операнд-приемник - последним.

-        Способы адресации:

Регистровый операнд всегда начинается с символа "%":

xorl      %eax, %eax                             // обнулить регистр eax

            Непосредственный операнд всегда начинается с символа "$":

                        movl     $variable, %edx                        // записать в edx адрес переменной variable (variable - переменная в памяти)

            Косвенная адресация использует немодифицированное имя переменной:

                        pushl    variable                                    // записать значение переменной variable в стек (variable - переменная в памяти, стек также занимает область памяти)

            Примеры более сложных способов адресации памяти:

                        addr(%ebx,%edi,4)      -          адрес: addr + ebx + edi * 4

                         (%ebx,%eax,4)           -          адрес: ebx + eax * 4

                        -2(%ebp)                     -          адрес: -2 + ebp

                         (,%edi,2)                     -          адрес: edi * 2

                        (%ebx)                         -          адрес: ebx

 

Следует обратить внимание на такой пример, касающийся обращений в память:

Здесь обращение в память происходит:

            mov      -8(%ebx), %eax           // поместить значение из памяти по адресу ebx-8 в регистр eax

            push     %ebx                           // поместить в стек значение регистра ebx (стек находится в памяти)

            flds       8(%esp)                       // загрузить значение из памяти по адресу esp+8 в регистр сопроцессора

Здесь обращения в память не происходит:

            lea        -8(%ebx), %eax           // загрузить адрес ebx-8 в регистр eax (к значению по этому адресу обращения нет).

С помощью команды lea процессор может выполнять различные арифметические операции, такие как сложение и умножение. Например:

            lea        24(%ebx,%ecx,4), %eax          // eax = 24 + ebx + ecx * 4